Compare commits

...

638 Commits

Author SHA1 Message Date
snipe c3d834cb9d Re-adding scope
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:16:40 +01:00
snipe 4e52aa7e12 Removed the wrong method lol
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:15:40 +01:00
snipe 4e97eac13c Dev assets
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:12:10 +01:00
snipe b7ba48c589 Remove unused method
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe 9a2403b4a1 Refactor for more logging, switch to use the components
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe 14f95033a8 Consumables history table
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe cca52a1e70 Use target *or* item
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe 93a809ecaf Added history count badge, use blade component
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe 28051d82c2 Create history table component
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe 91265184a7 Added translations
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe b91971961b Find targetlogs
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe 52d0fcc802 Fixed user file uploads > 1
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe 2f1b7ec8c1 Added created by
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe 3f74b6cc72 Fixed doofy export filename
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe a455f82474 Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:07:12 +01:00
snipe d450847754 Comment out location scoping option for now
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:06:48 +01:00
snipe 737ddb173f Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 04:06:47 +01:00
snipe 54f2b62294 Fixed consumable item_no import field
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 00:55:56 +01:00
snipe 1a1120220c Merge pull request #16765 from grokability/#15035-add-company_name_to_slack
Fixed #15035 - adds company to slack message
2025-04-18 00:36:25 +01:00
snipe ad6352adc4 Fixed #15035 - adds company to slack message
Signed-off-by: snipe <snipe@snipe.net>
2025-04-18 00:28:37 +01:00
snipe 4170397094 Merge pull request #16731 from CloCkWeRX/patch-4
Fixed: Editing > Email input > Utilise HTML5 controls
2025-04-17 23:16:23 +01:00
snipe 1e15aca809 Merge pull request #16733 from CloCkWeRX/patch-6
Fixed: Suppliers > Edit > Mark URL inputs as HTML5 URL inputs
2025-04-17 23:15:53 +01:00
snipe 8234c3eb0f Merge pull request #16734 from CloCkWeRX/patch-7
Fixed: Users > Edit > Mark website as a URL field
2025-04-17 23:14:59 +01:00
snipe 80eea7b064 Merge pull request #16757 from CloCkWeRX/fields
Fixed: Admin > Custom Fields > Ensure name field is marked required
2025-04-17 23:14:26 +01:00
snipe 779c28661e Merge pull request #16753 from CloCkWeRX/admin-slack-hook
Fixed: Admin > Webhooks > Swap to url input
2025-04-17 23:13:58 +01:00
snipe 516f59f0fc Added @BeatSpark as contributor
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 23:12:51 +01:00
snipe bfb55da1a3 Repeat #16571 without conflicts, correctly targeted
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 23:10:20 +01:00
snipe 79e2e5c272 Merge pull request #16729 from CloCkWeRX/patch-2
Fixed #16727: Bulk Audit > Mark Asset Tag input required
2025-04-17 23:06:26 +01:00
snipe db1af98992 Merge pull request #16764 from grokability/fixed_flaky_user_create_tests
Fixed flaky user creation tests
2025-04-17 22:46:42 +01:00
snipe c8ea3ba79a Merge pull request #16763 from grokability/add-linux-mint
Fixed: #16715 - Added LinuxMint support to snipeit.sh
2025-04-17 22:40:33 +01:00
snipe 96d5e072fe Fixed flaky tests on user creation
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 22:39:04 +01:00
snipe d25ba74123 Merge pull request #16762 from grokability/fixes/#15702-min_amt_and_termination_date_on_license_import
Fixed #15702 - Added termination_date, reordered fields for clarity
2025-04-17 21:37:27 +01:00
snipe f05dce8960 Added min_amt explicitly
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 21:31:20 +01:00
snipe 0758e73c5f Added termination_date, reordered fields for clarity
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 21:29:27 +01:00
Jeremy Price 617ee026c0 Add LinuxMint support to snipeit.sh
Fixes: https://github.com/grokability/snipe-it/issues/16715

@synergy34 requested LinuxMint support in our installer script. Mint is
Ubuntu-based, so i figured this wouldn't be too big of an issue, and
indeed it wasn't.

It mostly involved just supporting the OS version strings, and removing
the ondrej ppa because 22.1 ships with php8.3 already.

We only support 22.1 for the moment, as that's the most recent version
and the only one I tested on.

If you're on 22/Wilma and want to try, change the 221 on line 579 to 22 and
give it a whirl... If 22/Wilma also has php8.3, then it will probably be
fine
2025-04-17 12:22:42 -07:00
snipe 915c730dae Fixed #16714 - moved custom validation out of array
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 16:42:25 +01:00
snipe 0451f1219a Merge pull request #16732 from CloCkWeRX/patch-5
Fixed: Manufacturers > Edit > Mark URL inputs as HTML5 url inputs
2025-04-17 16:27:03 +01:00
Daniel O'Connor 5290a95b6b Update resources/views/hardware/quickscan.blade.php 2025-04-18 00:53:37 +09:30
snipe e407695ff9 Merge pull request #16741 from CloCkWeRX/patch-10
Fixed: Change Password > Mark password fields required for change password
2025-04-17 16:22:01 +01:00
snipe c5b53b00c1 Merge pull request #16742 from CloCkWeRX/security-url
Fixed: Admin > Security - Mark url fields as URL type
2025-04-17 16:21:26 +01:00
snipe d2ac9b9610 Added @CloCkWeRX as contributor
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 16:18:29 +01:00
snipe 537e695ae9 Merge pull request #16730 from CloCkWeRX/patch-3
Fixed: Depreciations > Create/Edit > Change controls to various number inputs
2025-04-17 16:15:24 +01:00
snipe b4b158da9e Merge pull request #16747 from CloCkWeRX/admin-oauth-required
Fixed: Admin > OAuth > Mark fields required
2025-04-17 16:09:42 +01:00
snipe ad072c0546 Moved validation email_array
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 16:04:56 +01:00
snipe 713e1671d4 Merge pull request #16749 from CloCkWeRX/admin-ldap-change-password-url
Fixed: Admin > LDAP > Use HTML5 inputs
2025-04-17 15:58:45 +01:00
snipe 087f9756a2 Merge pull request #16744 from CloCkWeRX/api-token-required
Fixed: Manage API Keys > New name field not marked required
2025-04-17 15:58:07 +01:00
snipe eb0408703e Merge pull request #16725 from CloCkWeRX/patch-1
Fixed #16723: Mark category name as required in modals
2025-04-17 15:47:07 +01:00
Daniel O'Connor 7e961b690a Mark required 2025-04-17 11:05:03 +00:00
snipe 81f3730d84 Merge pull request #16754 from grokability/fixes/ambiguous_clause_for_company_id
Fixed ambiguous clause using company_id
2025-04-17 11:58:15 +01:00
snipe 141fad8393 Fixed ambiguous clause using company_id
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 11:56:39 +01:00
Daniel O'Connor 58ff63845e Admin > Slack > Swap to url input 2025-04-17 10:47:37 +00:00
Daniel O'Connor a8150b7864 Update resources/views/settings/ldap.blade.php 2025-04-17 19:57:52 +09:30
Daniel O'Connor ce6724f788 Admin > LDAP > Swap to URL input 2025-04-17 10:25:23 +00:00
Daniel O'Connor 13faa8ab00 Admin > LDAP > Swap to URL input 2025-04-17 10:23:43 +00:00
Daniel O'Connor 89beb73836 Swap to URL input 2025-04-17 19:49:28 +09:30
Daniel O'Connor 8b7e36a697 Mark fields required 2025-04-17 10:17:32 +00:00
Daniel O'Connor c0f3d89b0b Fixed: Manage API Keys > New name field not marked required 2025-04-17 10:00:26 +00:00
Daniel O'Connor ec62a59e57 Update resources/views/depreciations/edit.blade.php 2025-04-17 19:20:44 +09:30
Daniel O'Connor f9269cfc63 Mark required 2025-04-17 19:19:19 +09:30
Daniel O'Connor 56bc06746c Set max limit 2025-04-17 19:18:31 +09:30
Daniel O'Connor 497e94d8a0 Admin > Security - Mark url fields as URL type 2025-04-17 09:42:45 +00:00
Daniel O'Connor aa77c8c528 Mark password fields required for change password 2025-04-17 09:40:27 +00:00
Daniel O'Connor 6cd2a5d1a5 Users > Edit > Mark website as a URL field 2025-04-17 09:35:35 +00:00
Daniel O'Connor e4a3a1a35f Suppliers > Edit > Mark URL inputs as HTML5 URL inputs 2025-04-17 09:34:45 +00:00
Daniel O'Connor 06f3cc1345 Manufacturers > Edit > Opt into HTML fields 2025-04-17 09:33:24 +00:00
Daniel O'Connor 3a6832ea58 Editing > Email input > Utilise HTML5 controls
Should be safe as this is validated by the UI as a singular email.
2025-04-17 09:32:02 +00:00
Daniel O'Connor d2bb7fc926 Deprecations > Create/Edit > Mark number of months as a numeric control 2025-04-17 09:30:56 +00:00
Daniel O'Connor 4b93f329c2 Bulk Audit > Mark Asset Tag input required
Fix https://github.com/grokability/snipe-it/issues/16727
2025-04-17 09:29:43 +00:00
Daniel O'Connor f03da92152 Mark category name as required in modals
Fix #16723
2025-04-17 09:27:04 +00:00
snipe 88acdbcc28 Move array return
Signed-off-by: snipe <snipe@snipe.net>
2025-04-17 01:00:09 +01:00
snipe 23623cca2c Merge pull request #16719 from grokability/fixes_weird_layout_datepicker
Use x-icon blade component, nicer small-screen form size for datepicker on assets checkout
2025-04-16 21:13:21 +01:00
snipe 93e66aae54 Nicer form sizing on smaller screens
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 21:05:56 +01:00
snipe 02b831c174 Use x-icon blade component
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 21:05:43 +01:00
snipe cd20486fe2 Merge pull request #16667 from Godmartinz/user-redirect-checkin-option
Adds option to redirect back to checkedInBy user for assets/licenses/accessories
2025-04-16 20:54:47 +01:00
snipe 1464f80425 Merge pull request #16717 from Godmartinz/fix_checkin_mail_test
Adds a check for category email alert boolean, bolster Check in Test
2025-04-16 18:10:29 +01:00
Godfrey M 8f673a7e3e default checkin_email to true in category factory 2025-04-16 10:05:37 -07:00
Godfrey M c44d037933 removed some words 2025-04-16 09:57:40 -07:00
Godfrey M c8b5b3f176 adds a check for category checkin/out emails, also updates our test 2025-04-16 09:51:44 -07:00
snipe ce94470a10 Added comments
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 16:41:25 +01:00
snipe e7592eeeb9 Use disable admin CC
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 16:41:14 +01:00
snipe 46253b421e Added disableAdminCC to settings test
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 16:41:04 +01:00
snipe 7d4e77a7c8 Updated string
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 15:52:05 +01:00
snipe 9a4f21e0cb Merge pull request #16716 from grokability/log_deprecations
Allow toggle for logging deprecation warnings
2025-04-16 14:28:07 +01:00
snipe 934aa3da7f Allow toggle for logging deprecation warnings
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 14:25:51 +01:00
snipe e4244d60f1 Check for username
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 09:47:06 +01:00
snipe 0b6c6bf1df Send email to CC addresses even if the target doesn’t have an email
Signed-off-by: snipe <snipe@snipe.net>
2025-04-16 09:18:50 +01:00
snipe b35181c289 Removed deleted accessories (for now) from transformer
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 21:15:43 +01:00
snipe 277564436b Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 20:45:13 +01:00
snipe 87a03ec1ed Fixed test
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 20:41:43 +01:00
snipe f8833241ef Added @36864 as contributor
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 20:28:36 +01:00
snipe 7f62c5cbb6 Add @fvollmer as a contributor 2025-04-15 20:22:23 +01:00
snipe 93b4749993 Update @ntaylor-86 as a contributor 2025-04-15 20:20:36 +01:00
snipe b2dac291da Merge pull request #16712 from grokability/fixes/snipe-api-token
Removed deprecation resulting in `Creation of dynamic property` error
2025-04-15 20:16:56 +01:00
Jermops bec83d4343 Merge pull request #16713 from grokability/fix-docker-arm-buildname
Fix name of ARM docker container workflow
2025-04-15 12:16:40 -07:00
snipe 4f3b3721c4 Remove comments
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 20:15:44 +01:00
Jeremy Price e5cf296b79 Fix name of ARM docker container workflow 2025-04-15 12:15:44 -07:00
snipe e1abdd1c7b Removed deprecation resulting in Creation of dynamic property
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 20:11:26 +01:00
snipe 71d8f1eb89 Merge pull request #16701 from grokability/repo-move
Update references to the repo to reflect move to Grokability org
2025-04-15 19:54:08 +01:00
snipe 68c1568345 Merge pull request #16711 from snipe/publish_assets
Updated dev CSS/LESS/JS assets
2025-04-15 19:40:48 +01:00
snipe b5be0844ec Updated dev assets
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 19:39:58 +01:00
snipe f76e80ba68 Merge pull request #16710 from snipe/localizations/2025-04-15
Updated localization strings
2025-04-15 19:32:12 +01:00
snipe ffbab554be Updated strings
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 19:30:19 +01:00
snipe 0be50e803e Merge pull request #16709 from snipe/#16699-fix-email-locales-when-none-set-on-user
Fixed #16699 - Better handle user locales in mailables
2025-04-15 16:44:38 +01:00
snipe 7133a1b262 Fixed test
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 16:41:09 +01:00
snipe 5876418eed Search on email partial
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 16:31:03 +01:00
snipe 950472b935 Fixed typos
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 16:30:54 +01:00
snipe c0c5699e38 Added public property name
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 16:21:02 +01:00
snipe 49fee3a211 Removed manually setting locale on mailable
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 16:10:54 +01:00
snipe a21ca92c90 Added boot method to set name property for mailable
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 16:08:21 +01:00
snipe 260174dfd9 Added test
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 15:46:12 +01:00
snipe afc5e08716 Added email as searchable field in select list
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 15:45:25 +01:00
snipe 89616727a1 Remove english as default
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 15:45:11 +01:00
snipe ba55dfb841 Fixed #16699 - added mutator for user locale
Signed-off-by: snipe <snipe@snipe.net>
2025-04-15 13:41:24 +01:00
snipe 65b956143c Merge pull request #16700 from snipe/docker-arm-size-fix
[Docker] Don't cache composer deps, remove any .git repos that creep in
2025-04-14 22:15:10 +01:00
snipe 42e1987147 Merge pull request #16702 from snipe/separate-docker-architecture-builds
Separate docker builds into Intel/ARM builds
2025-04-14 22:14:48 +01:00
Jeremy Price 545e07455b Separate docker builds into Intel/ARM builds
Now that we're moving to a paid org, we have native ARM github action
runners available, which means we can streamline our ARM-based docker
image builds by not having them run emulated.

I've switched our worker split from ubuntu/alpine to Intel/ARM...  if we
hate this we can make it 4 separate workflows, but i don't see an issue
with this one.
2025-04-14 12:55:36 -07:00
Jeremy Price b00594052c [Docker] Don't cache composer deps, remove any .git repos that creep in
There is a failure mode in composer where if it has a connection trying to get
a dependency tarball from the github API, it will fall-back to
downloading the entire repo for the dependency and use that... and it
will cache it outside of vendor/, using a whopping 1.5G of space

that full 1.5G is _then_ copied into vendor/

```
98M     league
100M	tecnickcom
133M	laravel
323M	sebastian
681M	aws
```

for a total of a 1.8G vendor/ directory

vs the trimmed-down version with a 552M vendor/ directory

```
53M	league
30M	tecnickcom
31M	laravel
70M	sebastian
241M	aws
```

This is still a far cry from the proper 150M version when everything
works as it should, but it's still a vast improvement

```
3.2M    league
14M     laravel
30M     tecnickcom
260K	sebastian
52M     aws
```

Ideally this never happens, but it'd be great tp avoid the bloat if/when
it does..

To wit: Our ARM/Ubuntu Docker images are currently bloated because of this
issue due builds happening on an emulated ARM environment, and the resultant
performance penalties causing composer issues

All that to say, this change sets OMPOSER_CACHE_DIR to /dev/null to
avoid the caching, and then removes vendor/*/*/.git to remove that
needless bloat if it occurs. It's a no-op in general practice.
2025-04-14 11:55:20 -07:00
snipe 1c387795fe Merge pull request #16683 from marcusmoore/bug/sc-28755
Create default label when importing assets if none exists
2025-04-14 09:52:48 +01:00
snipe 102f26cac1 Merge pull request #16676 from marcusmoore/fixes/acceptance-logging
Store accepted_at and declined_at in action log when accepting/declining assets
2025-04-14 09:18:54 +01:00
snipe c7e89ff879 Merge pull request #16682 from akemidx/bug/sc-28860
FIXED: Location Being Overwritten By Default Location
2025-04-14 09:17:52 +01:00
Marcus Moore b82d835f4f Remove created_by since it is not available 2025-04-10 13:59:28 -07:00
Marcus Moore d593365c9c Create pending default status label if none exists 2025-04-10 13:38:30 -07:00
Marcus Moore ea6a903d8a Implement tests including test failure 2025-04-10 13:29:05 -07:00
Marcus Moore 9086e5dba7 Scaffold some asset importer tests 2025-04-10 13:03:50 -07:00
akemidx cd10cd34f4 location update bug fix and test 2025-04-10 14:46:52 -04:00
snipe 0d6a83197a Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2025-04-09 21:29:31 +01:00
snipe 6d784e36d7 Updated language
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 21:29:27 +01:00
snipe 9c88aa6974 Merge pull request #16637 from akemidx/lastnameemail
Fixed #8188 - Added Last Name as an email format
2025-04-09 21:27:10 +01:00
akemidx 1307146831 changing where translations live. coalescing 2025-04-09 16:21:48 -04:00
Marcus Moore e5d7bcb629 Use declinded at for action date in log 2025-04-09 11:20:46 -07:00
Marcus Moore c2123e307a Add failing test 2025-04-09 11:20:07 -07:00
Marcus Moore 7a9d5bfc07 Add test for declining assets 2025-04-09 11:18:52 -07:00
Marcus Moore aed798800c Improve assertions 2025-04-09 11:17:57 -07:00
Marcus Moore dbfa952a69 Improve test name 2025-04-09 11:15:25 -07:00
Marcus Moore aa58f08b3d Use accepted at for action date in log 2025-04-09 11:13:46 -07:00
Marcus Moore d76871760c Add failing test 2025-04-09 11:11:30 -07:00
Marcus Moore d29f5fa13e Implement tests 2025-04-09 11:07:47 -07:00
Marcus Moore 7275299165 Scaffold some tests 2025-04-09 11:02:56 -07:00
Godfrey M 9824f43780 added other target options to accessories and assets 2025-04-09 10:50:42 -07:00
snipe 1b7486c342 Merge pull request #16674 from spencerrlongg/bug/allow-avif-restore-and-logo
Add Support for Uploading AVIF Logo Images
2025-04-09 17:46:53 +01:00
spencerrlongg cd9ee8af90 Add support for AVIF logo uploads and restoring avifs from public uploads
Extended the list of allowed file extensions and MIME types to include AVIF format in both backend file validation and the file input field for logo uploads. This update ensures compatibility with modern image formats.
2025-04-09 11:36:16 -05:00
snipe cbf4fef45b Merge pull request #16671 from snipe/fixed_list_view_of_asset_files
Partial fix for #16135 - normalized asset file listing at API endpoint
2025-04-09 06:45:32 +01:00
snipe 8892a11e7e Updated tests
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 06:40:14 +01:00
snipe fc390dd107 Merge pull request #16672 from snipe/added_missing_gate_to_some_location_methods
Added gates to printing locations
2025-04-09 06:28:38 +01:00
snipe 99dfb51d70 Fixed test
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 06:23:40 +01:00
snipe 04f8ebb4d8 Added tests
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 06:17:08 +01:00
snipe 41fb058adb Added gates around printing location assets
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 06:06:44 +01:00
snipe f29146b319 Fixed asset file listing display at API endpoint
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 05:53:24 +01:00
snipe ce0bd68716 RMB for file routes
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 05:52:59 +01:00
snipe f3f26b3824 Added UploadedFilesTransformer
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 05:52:48 +01:00
snipe 7be3d6072f Removed erroneous comment
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 05:52:35 +01:00
snipe 1d030b59df Merge pull request #16592 from fvollmer/anonymous-ldap
Improve Settings: Remove username and password requirement for ldap
2025-04-09 05:19:09 +01:00
snipe 07d2d8c549 Merge pull request #16669 from snipe/translations_for_skins
Fixed  #16130 - Added translations for skins, other settings
2025-04-09 02:37:17 +01:00
snipe 691ccbbebc Additional branding placeholders
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 02:32:22 +01:00
snipe 9daa09277d Added translations for skins
Signed-off-by: snipe <snipe@snipe.net>
2025-04-09 02:23:18 +01:00
snipe 79eaf62c9e Merge pull request #16668 from spencerrlongg/bug/sc-28768
Handle Potentially Unsafe File Output Better During Restore
2025-04-09 01:01:59 +01:00
spencerrlongg dd078785ac always show unsafe files 2025-04-08 18:06:18 -05:00
spencerrlongg 12a8c54331 Handle and log skipped unsafe files during restore
Introduced a mechanism to track and log potentially unsafe files skipped during the restore process. These files are collected in an array and displayed as warnings before exiting, improving transparency and debugging capability.
2025-04-08 16:43:36 -05:00
Godfrey M 715fc2de59 more cleaning 2025-04-08 11:58:23 -07:00
Godfrey M af53559ca3 cleaned up code 2025-04-08 11:54:50 -07:00
Godfrey M 17b8ea9c86 removed unnecessary code 2025-04-08 11:51:29 -07:00
Godfrey M 1ad96e891b added option logic to licenses 2025-04-08 11:50:45 -07:00
Jeremy Price 4e2b4195b4 Grokability-ize .well-known/security.txt 2025-04-08 11:50:03 -07:00
Godfrey M b7492928ad reworked checkedInBy, added option logic to accessories 2025-04-08 11:43:02 -07:00
snipe 9f04254963 Merge pull request #16666 from snipe/print_view_tweaks
Added signature to licenses in print view, misc other fixes
2025-04-08 19:34:02 +01:00
snipe 10b8055b29 Added monospace class
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 19:10:16 +01:00
snipe 2b85ddeb74 Use correct date
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 19:10:06 +01:00
snipe 9017ae8545 Added category to consumable checkout
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 19:09:58 +01:00
snipe d0359a42fb Added signature to licenses
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 18:56:34 +01:00
akemidx 7701c6097f adding username split 2025-04-08 13:21:32 -04:00
akemidx 275e1beda2 updating for conflicts and adding test 2025-04-08 13:15:28 -04:00
Godfrey M 85b67dbb71 adds redirect to user option from checkin blades 2025-04-08 10:13:27 -07:00
akemidx f659b7631d Merge branch 'develop' into lastnameemail 2025-04-08 13:10:26 -04:00
Godfrey M a602b2fd47 adds option to redirect back to checkedInBy user for assets 2025-04-08 10:09:43 -07:00
snipe e0c6483b43 Merge pull request #16660 from snipe/cleanup_for_scoped_locations
Small improvements to location-by-company scoping
2025-04-08 17:57:50 +01:00
snipe c2e12f69d8 Merge pull request #16664 from 36864/patch-1
Fixes #16661: Empty signatures in print page
2025-04-08 17:57:11 +01:00
snipe 890702f66d A few more display tweaks
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 17:47:39 +01:00
36864 dfdbc95b5c Fix empty signatures in print page
Fixes snipe#16661 

Assuming that whatever happens between the user accepting and signing for the asset and the print page being accessed isn't another user somehow signing for the same asset and it not being assigned to that second user.
2025-04-08 15:50:59 +00:00
snipe ea365e5645 More fields visible
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 16:17:57 +01:00
snipe 548ae49c69 Small tweaks to table
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 15:30:07 +01:00
snipe 34f8f50a4a Use localization, turned display into table
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 15:13:57 +01:00
snipe da2c760227 Renamed variables, skipped breaking out of loop
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 15:13:19 +01:00
snipe a96abeac5f Use localization w/trans_choice
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 15:12:58 +01:00
snipe 420278c63b Nicer layout for settings
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 15:12:36 +01:00
snipe e0a04fe1ce Localized message
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 15:12:19 +01:00
snipe c42f53e846 Use table layout
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 12:34:11 +01:00
snipe 44ee287cc0 Nicer formatting
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 12:34:00 +01:00
fvollmer a56f6148fc Improve Settings: Remove username and password requirement for ldap
Since 9d62793 anonymous LDAP login is available. Remove username and password requirement in settings dialog.
2025-04-08 11:04:37 +02:00
snipe 5b1d6dbe14 Merge pull request #12577 from Toreg87/feature/locations_with_companies
Added #2353: Add ability to tie locations to companies - 2023 edition
2025-04-08 09:54:37 +01:00
snipe 43c15ef134 Merge branch 'develop' into feature/locations_with_companies 2025-04-08 09:18:48 +01:00
snipe a7203b0bbf Updated links, added mobile apps
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 08:32:15 +01:00
snipe a14d3ad856 Merge pull request #16493 from snipe/min_qty_in_bulk_model_edit
Add min_qty to asset model bulk edit
2025-04-08 08:22:03 +01:00
snipe fcd0ca3b94 Merge branch 'develop' into min_qty_in_bulk_model_edit 2025-04-08 07:14:15 +01:00
snipe 90329a2b2f Merge pull request #16529 from Godmartinz/location_tab_active-n-scroll_bar_fix
Fix active table tab and double scroll bar under locations
2025-04-08 07:12:33 +01:00
snipe 7448f67e12 Merge pull request #16658 from snipe/fix_bad_data_on_permission_groups
Set empty array if group permission is a string or null
2025-04-08 07:10:02 +01:00
snipe 22be89fbea Set empty array if group permission is a string or null
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 07:06:56 +01:00
snipe fe65ffc384 Merge pull request #16527 from Godmartinz/license_seat_notes_fix
add notes as fillable to license seat model
2025-04-08 06:49:26 +01:00
snipe d44553c6dd Merge pull request #16553 from marcusmoore/tests/user-show
Added tests around emailing and printing assigned assets
2025-04-08 06:48:39 +01:00
snipe cfd845aefa Merge pull request #16500 from marcusmoore/bug/sc-28644-command
Added command to fix bulk checkin action log entries
2025-04-08 06:44:25 +01:00
snipe a98b277fa9 Merge pull request #16560 from spencerrlongg/bug/meta_status_multi_comp
Meta Status Fix for Multi Company
2025-04-08 06:43:57 +01:00
snipe 237975577d Merge pull request #16636 from marcusmoore/tests/delete-asset-tests
Added tests around deleting assets
2025-04-08 06:32:40 +01:00
snipe 2275186222 Merge pull request #16652 from akemidx/feature/sc-28820
Username dropdown to show Usernames and not Emails as Examples
2025-04-08 06:32:19 +01:00
snipe 5a1c81954f Merge pull request #16651 from Godmartinz/qr_code_location_option
Fixes #9660 - Adds location qr code option to labels
2025-04-08 05:53:48 +01:00
snipe 68e7d172a4 Merge pull request #16655 from snipe/adds_more_searchable_relations_to_activity
Fixed #13274 - Adds more searchable relations to activity report
2025-04-08 05:53:18 +01:00
snipe b2e0f48ed9 Added purchase date
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 05:50:03 +01:00
snipe 733ef9e23b Few more
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 05:44:49 +01:00
snipe 83562cfa83 Added additional searchable relations to activity report
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 05:23:39 +01:00
snipe de426c2d2c Merge pull request #16653 from snipe/add_audit_custom_fields
Fixed #13475 - Add custom fields to audit screen
2025-04-08 05:16:23 +01:00
snipe 05e66c33ee Added audits string
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 05:09:08 +01:00
snipe 744e844291 Added audit tab
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 05:08:58 +01:00
snipe 31c9ffa32b Added audits method
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 05:08:42 +01:00
snipe c46a9a773d Fixed admin string
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 05:08:08 +01:00
snipe 849da2fb63 Use correct audit icon
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 04:15:59 +01:00
snipe b51939ae76 Derp. Use correct model info
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 04:07:31 +01:00
snipe e95d7076b9 Added action_date
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 04:07:04 +01:00
snipe d2c7385197 Updated class for error text
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 04:06:54 +01:00
snipe 5beb0bf534 Added assertion for success in test
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:39:17 +01:00
snipe c59e9770b7 Removed unusued property
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:39:05 +01:00
snipe 908bb35792 Use upload file request
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:38:35 +01:00
snipe c4d0afb8d4 Added comments
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:24:33 +01:00
snipe 4b21f0d00b Removed stray character
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:03:45 +01:00
snipe 4aeba2a96b Try to fix tests
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:02:37 +01:00
snipe 74f8cb5298 Updated url in bootstrap partial
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:02:28 +01:00
snipe 62e863a0fa Removed tooltip code
This throws an error currently

Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:02:10 +01:00
snipe 5bbba56b0e Added orginal values for logging
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:01:39 +01:00
snipe e4180c2194 Removed duplicated code
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:01:29 +01:00
snipe f4e3e6ceb6 Added display_audit to custom fields transformer
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:01:08 +01:00
snipe f37ed3e055 Add display_audit to custom fields controller
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:00:51 +01:00
snipe 9bb349d34b Try to get the asset from the route if there is RMB
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 02:00:37 +01:00
snipe 362f14a01d Manually invoke a validator
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 01:59:33 +01:00
snipe 226ad52f07 Better UI route
Signed-off-by: snipe <snipe@snipe.net>
2025-04-08 01:59:08 +01:00
akemidx 443a5c2348 chaning username dropdown to be usernames and not emails 2025-04-07 19:25:32 -04:00
snipe ce460c9ab0 Updated route
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:15:09 +01:00
snipe c344c40310 Added auditAssets() to user factory
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:10:36 +01:00
snipe 95fef9682f Added migration
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:10:18 +01:00
snipe 241777c1fd Added translation string
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:10:12 +01:00
snipe acdbf452e2 Added checkbox to custom field form
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:10:01 +01:00
snipe b8a9db2faf Added display_audit to custom fields list
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:09:51 +01:00
snipe dfdc24936d Added custom fields partial
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:09:29 +01:00
snipe 6a1bb06c13 Added tests
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:09:14 +01:00
snipe cfa8ddffc0 Keep legacy URL for audit
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:08:05 +01:00
snipe 5b524399d9 Use RMB for asset audit API
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 22:07:51 +01:00
Godfrey M bc3b3cf86e add psuedo example location_id for preview 2025-04-07 12:53:17 -07:00
Godfrey M a69133e2ae adds location qr code option to labels 2025-04-07 12:36:05 -07:00
snipe b66618ff3f Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2025-04-07 13:54:10 +01:00
snipe b5ef856d9e Translate request cancel string
Signed-off-by: snipe <snipe@snipe.net>
2025-04-05 17:24:06 +01:00
snipe a302cc145a Merge pull request #16643 from snipe/small_requestable_improvements
Text tweaks and nicer buttons for requestable items
2025-04-05 16:07:57 +01:00
snipe d203c4e49c Text tweaks and nicer buttons
Signed-off-by: snipe <snipe@snipe.net>
2025-04-05 15:58:03 +01:00
snipe b5c7e60408 Merge pull request #16642 from snipe/fixed_#16640-FIFO-for-requestable
Fixed #16640 - FIFO for requestable assets
2025-04-05 15:39:29 +01:00
snipe 3684e9c1e8 Fixed #16640 - FIFO for requestable assets
Signed-off-by: snipe <snipe@snipe.net>
2025-04-05 15:38:13 +01:00
snipe f2c730bd57 Fix wrapping on #16628
Signed-off-by: snipe <snipe@snipe.net>
2025-04-05 14:10:23 +01:00
akemidx f0bf77735d adding last name only as a email choice 2025-04-03 23:34:04 -04:00
Marcus Moore 1b95b29832 Add tests for deleting asset via ui 2025-04-03 16:12:13 -07:00
Marcus Moore 9a8e5bf61e Backup before running updates 2025-04-03 14:39:40 -07:00
Marcus Moore 18ef88bd67 Improve comment 2025-04-03 13:13:52 -07:00
Marcus Moore 4ba58b2546 Constrain query 2025-04-03 13:10:45 -07:00
Marcus Moore 6dd3ab2ec9 Exit early if no logs found 2025-04-03 12:56:46 -07:00
Marcus Moore faee50c222 Use dry-run instead of dryrun 2025-04-03 12:56:07 -07:00
Jeremy Price afd9282785 Update remaining comments from snipe/snipe-it to grokability/snipe-it 2025-04-03 12:07:05 -07:00
Jeremy Price 1344ed1d16 Update app & composer files from snipe/snipe-it to grokability/snipe-it 2025-04-03 12:04:12 -07:00
Jeremy Price cbea096403 Update Vagrantfile files from snipe/snipe-it to grokability/snipe-it 2025-04-03 12:02:42 -07:00
Jeremy Price 094edbd114 Update css and less files from snipe/snipe-it to grokability/snipe-it 2025-04-03 12:00:51 -07:00
Jeremy Price f016b6b988 Update relevant README sections from snipe/snipe-it to grokability/snipe-it 2025-04-03 11:54:39 -07:00
Jeremy Price 782b35e0f1 Update scripts from snipe/snipe-it to grokability/snipe-it 2025-04-03 11:51:12 -07:00
Jeremy Price 70e9c6b947 Update relevant snipe/snipe-it references to grokability/snipe-it in Docker files 2025-04-03 11:44:27 -07:00
Jeremy Price 4a457c96e8 Update Contributors links from snipe/snipe-it to grokability/snipe-it 2025-04-03 11:39:21 -07:00
Jeremy Price 2e2516825e Update github workflows from snipe/snipe-it to grokability/snipe-it 2025-04-03 11:36:57 -07:00
snipe ba621cb1f2 Merge pull request #16632 from uberbrady/fix_paveit_command
Paveit had old Doctrine code to list tables; use the new method
2025-04-03 15:39:38 +01:00
snipe b1e2b5ab4a Merge pull request #16623 from Godmartinz/accessories_history_view_fix
Fixes Accessories history table color contrast
2025-04-03 15:37:55 +01:00
snipe 7c1438c370 Merge pull request #16446 from marcusmoore/feature/improve-checkout-asset-mail-wording
Improved wording in asset checkout emails
2025-04-03 15:37:30 +01:00
Brady Wetherington 760768f42d Paveit had old Doctrine code to list tables; use the new method 2025-04-03 15:35:07 +01:00
snipe cc8c2064c1 Merge pull request #16630 from snipe/license_key_formatting
Fixed #16628 - added formatting for license keys
2025-04-03 15:26:14 +01:00
snipe 33a921119c Merge pull request #16631 from snipe/add_webp_to_inline
Added webp as inline-able images
2025-04-03 15:25:48 +01:00
snipe 0e65498799 Added webp as inline-able images
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 15:24:03 +01:00
snipe 274c5fe4d3 Added max-width
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 15:07:10 +01:00
snipe b7c011dd2d Use new one line code style
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 15:03:31 +01:00
snipe a4a99adf80 Added one-line code style
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 15:03:16 +01:00
snipe c7890f4c3b Use id as copy key
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 13:52:55 +01:00
snipe 6d7f061a1d Larger key box
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 13:39:31 +01:00
snipe 45d3c0444b Added copy to license keys
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 12:29:30 +01:00
snipe 9204309d67 Checkin/Checkout note added to language file
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 12:28:47 +01:00
snipe a2035693e6 Added licenseKeyFormatter
Signed-off-by: snipe <snipe@snipe.net>
2025-04-03 12:28:30 +01:00
snipe abfea8e349 Merge pull request #16624 from snipe/bug/sc-28682
Fixed [SC-28682] - Consumable import not importing supplier and item number
2025-04-03 01:45:52 +01:00
snipe ef20bd4aa8 Fixed tests
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 21:41:12 +01:00
snipe 2d9be4e9e0 Added supplier_id as fillable
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 21:10:25 +01:00
snipe 36767c0a5c Added more common fields for mapping
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 21:10:16 +01:00
snipe 0d099263e3 Removed unneeded lines
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 21:09:28 +01:00
snipe 8f50e01d18 Shrink sample CSVs
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 21:09:04 +01:00
snipe d898288397 Remove email user for comonent import type
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 21:08:54 +01:00
Marcus Moore af88ce5801 Merge branch 'develop' into feature/improve-checkout-asset-mail-wording 2025-04-02 12:01:53 -07:00
Godfrey M b1b248f03d removed duplicate class from accessories history table 2025-04-02 11:29:01 -07:00
snipe df1c7c4f95 Merge pull request #16609 from snipe/login_throttle_update
Updated login attempts and throttle duration
2025-04-02 18:28:34 +01:00
snipe 7120b19d3b Fixed namespace
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 18:24:27 +01:00
snipe 2197b46658 Link to admin user
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 14:03:09 +01:00
snipe 785f576b19 Merge pull request #16621 from snipe/add_notes_to_location_sidebar
Fixed #16618 - added notes to location sidebar
2025-04-02 13:27:48 +01:00
snipe 31e337255a Use adminuser instead of created_by
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 13:26:12 +01:00
snipe f12d173581 Fixed #16618 - added notes to location sidebar
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 13:23:33 +01:00
snipe eb3dbb8c7a Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 12:49:53 +01:00
snipe ed908be2eb Merge pull request #16620 from snipe/#16619_fix_accessory_clone_population
Fixed #16619 - cloning accessory was not populating fields
2025-04-02 12:48:25 +01:00
snipe e215c5f9ee Fixed #16619 - accesory clone
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 12:43:42 +01:00
snipe 78f3c879ff Merge pull request #16617 from snipe/add_pdf_logo_upload
Fixed #16257 - Added acceptance PDF logo upload
2025-04-02 12:22:43 +01:00
snipe cb59c23f0b Added pdf logo to the blade
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 12:13:34 +01:00
snipe d0f750edea Added pdf logo to the controller
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 12:13:28 +01:00
snipe 56ae9d0ba9 Nicer upload logo labels
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 12:10:27 +01:00
snipe 15917d5f99 Use new logo
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 12:10:14 +01:00
snipe a59d69cb0d Updated strings
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 12:10:05 +01:00
snipe 97cba45509 Added migration
Signed-off-by: snipe <snipe@snipe.net>
2025-04-02 12:09:57 +01:00
snipe 365ce34940 Merge pull request #16613 from marcusmoore/chore/update-rollbar
Update rollbar-laravel to 8.1
2025-04-02 10:48:51 +01:00
Marcus Moore 7048eceb9d Update rollbar/rollbar-laravel 2025-04-01 16:05:32 -07:00
snipe 9d61234f0c Merge pull request #16612 from snipe/re-fix_db_dump_ssl_fix
Fixes #16610, regression in #16543
2025-04-01 21:32:15 +01:00
snipe bef650757d Fixes #16610, regression in #16543
Signed-off-by: snipe <snipe@snipe.net>
2025-04-01 21:25:35 +01:00
snipe cb6e7f7b6e Merge pull request #16561 from spencerrlongg/bug/api_get_by_serial_add_pagination
Adds Pagination to Hardware By Serial API Request
2025-04-01 19:26:28 +01:00
snipe c305284930 Merge pull request #15922 from spencerrlongg/feature/sc-24347
Requestable/Request Item API Endpoints
2025-04-01 18:15:42 +01:00
snipe 6c47f1c07f Added test
Signed-off-by: snipe <snipe@snipe.net>
2025-04-01 17:44:44 +01:00
snipe 5d265f5bfd Change to minutes
Signed-off-by: snipe <snipe@snipe.net>
2025-04-01 15:01:34 +01:00
snipe daaad4fe9d Removed unneeded strings
Signed-off-by: snipe <snipe@snipe.net>
2025-04-01 14:36:53 +01:00
snipe 40ae5d421b Updated maxLoginAttempts and throttle duration
Signed-off-by: snipe <snipe@snipe.net>
2025-04-01 14:36:45 +01:00
spencerrlongg bfd827e529 added more eager loading 2025-03-31 12:28:03 -05:00
snipe d95549bbcf Merge pull request #16594 from uberbrady/fix_consumable_model_copy
Fixes [FD-47675 ] Fix consumable model number copy-to-clipboard button
2025-03-31 18:26:46 +01:00
snipe e10071a68e Merge pull request #16595 from uberbrady/fix_whitespace_encrypted_custom_fields_display
Fix whitespace encrypted custom fields display [FD-46570]
2025-03-31 18:25:13 +01:00
Brady Wetherington 92a77afd0b Fixes the clipboard button copying whitespace at the end of custom fields 2025-03-31 13:55:38 +01:00
Brady Wetherington 1369b993a4 Fix JS copy-to-clipboard for consumable model_num 2025-03-31 13:13:37 +01:00
snipe 382414df98 Merge pull request #16559 from Godmartinz/StoreLabelSettings_fix
Fix Form save error when using old label engine
2025-03-31 12:39:08 +01:00
snipe 6846c7d510 Merge pull request #16536 from akemidx/moving_warranty_info
Moving warranty/depreciation to be with the other cost/eol values
2025-03-31 12:35:32 +01:00
snipe 1d3069fe84 Merge pull request #16540 from marcusmoore/bug/sc-28607
Early return null from location transformer for missing accessory
2025-03-31 12:34:22 +01:00
snipe 13940071a9 Merge pull request #16546 from marcusmoore/bug/sc-28024
Fixed potential bad method call and premature email sending in bulk asset checkout
2025-03-31 12:32:50 +01:00
snipe 6870698cdc Add @ntaylor-86 as a contributor 2025-03-31 12:32:25 +01:00
snipe 4aa13c8dd4 Merge pull request #16543 from ntaylor-86/fixes/laravel-backup-ssl-error
Fixed #16217: database config, added option to skip ssl on the database dump
2025-03-31 12:32:00 +01:00
snipe 44db2dc78e Merge pull request #16520 from marcusmoore/bug/sc-28673
Remove unneeded eager loading for user show page
2025-03-31 12:31:12 +01:00
snipe 2e17e80ea9 [Snyk] Upgrade acorn from 8.14.0 to 8.14.1 #16587
Signed-off-by: snipe <snipe@snipe.net>
2025-03-31 12:30:09 +01:00
snipe 836d7ca8f9 [Snyk] Upgrade bootstrap-table from 1.24.0 to 1.24.1 #16588
Signed-off-by: snipe <snipe@snipe.net>
2025-03-31 12:29:00 +01:00
snipe 18e6a18389 Merge pull request #16576 from marcusmoore/fixes/api_company_note
Fixed notes not being saved and update for companies via api
2025-03-31 12:20:36 +01:00
snipe 1dd050ac0f Merge pull request #16577 from marcusmoore/tests/login-logging
Added tests around login attempt logging
2025-03-29 23:30:05 -01:00
Marcus Moore b8b0e3200e Add tests around loggin login attempts 2025-03-27 17:27:28 -07:00
Marcus Moore bc77c8c885 Add notes to $fillable 2025-03-27 10:57:34 -07:00
Marcus Moore c095f330e1 Add failing tests 2025-03-27 10:56:33 -07:00
Marcus Moore e0e08f284e Add tests for creating and patching companies via api 2025-03-27 10:54:42 -07:00
spencerrlongg e273c7cbc5 Refactor asset retrieval to support pagination.
Introduced offset and limit handling for API asset queries to enable proper pagination. Adjusted the total count logic to maintain consistency in responses and ensure accurate transformation of assets.
2025-03-25 15:21:18 -05:00
Godfrey M d28cc024cf add DefaultLabel if no label2_template is selected 2025-03-25 12:40:23 -07:00
spencerrlongg 168a3df157 Fix condition in statusMeta for correct property check
Updated the conditional check to use 'assigned_to' instead of 'assigned' for determining the deployment status. This ensures the code references the correct property on the model.
2025-03-25 14:39:51 -05:00
Marcus Moore ac597b517b Remove blank line 2025-03-24 12:39:17 -07:00
Marcus Moore ac56640d40 Spilt test scenarios 2025-03-20 16:21:42 -07:00
Marcus Moore ce585539aa Improve test name 2025-03-20 16:13:13 -07:00
Marcus Moore 2cfff8e07c Formatting 2025-03-20 16:12:31 -07:00
Marcus Moore 0ceda098ff Spilt test scenarios 2025-03-20 16:10:01 -07:00
Marcus Moore db81209fe1 Organize existing tests 2025-03-20 16:01:28 -07:00
Marcus Moore 02f109c3b5 Include input when redirecting back 2025-03-20 14:04:19 -07:00
Marcus Moore 0ed0a7f9f3 Avoid sending emails for asset checkouts that failed 2025-03-20 14:01:48 -07:00
Marcus Moore b721b7d9c9 Add tests 2025-03-20 13:57:42 -07:00
Marcus Moore 976b3dc5ae Improve test case 2025-03-20 13:38:12 -07:00
snipe 2d3514bbf8 Merge pull request #16545 from Godmartinz/activity_report_column_name_fix
changes translation from Admin Settings to Created By on the Activity Report
2025-03-20 19:37:42 -01:00
Marcus Moore 5dcd4b2942 Return valid error message 2025-03-20 13:26:27 -07:00
Marcus Moore d645b42e12 Add failing test 2025-03-20 13:26:06 -07:00
Godfrey M 0ad985cbcd change translation 2025-03-20 12:40:46 -07:00
ntaylor-86 ad5099fac9 added .env variable to the other .env files 2025-03-20 23:27:29 +10:00
ntaylor-86 32736e2f74 added .env variable and database config to skip ssl for DB DUMP 2025-03-20 23:06:15 +10:00
Marcus Moore cef83ad652 Avoid hard failure on missing accessory in location transformer 2025-03-19 14:11:47 -07:00
akemidx 5944034b8b moving warranty/depreciation to be with the other cost/eol values 2025-03-19 15:22:09 -04:00
Godfrey M 881cde4d98 change namesspaces and use create instead of make 2025-03-18 14:18:49 -07:00
akemidx a398c4ab84 reset 2025-03-18 16:37:27 -04:00
akemidx 27417cdec7 removing depreciation logic (unneeded for purchase cost) 2025-03-18 16:36:36 -04:00
Godfrey M bf3837c49d add checkin test for api 2025-03-18 12:54:03 -07:00
Godfrey M 66fd46fe75 add checkout test for api 2025-03-18 12:46:56 -07:00
Godfrey M 29cbf43d68 remove duplicate table class div 2025-03-18 11:48:09 -07:00
Godfrey M 07096c8a31 rename note to notes 2025-03-18 10:37:00 -07:00
Godfrey M cbc6c2754c add notes as fillable to license seat model 2025-03-18 10:27:56 -07:00
Jermops 711235f49c Merge pull request #16519 from marcusmoore/bug/sc-28696
Fixed issue with bad email addresses in expiration alerts and upcoming audits
2025-03-18 09:15:41 -07:00
Marcus Moore 9aede45918 Remove unneeded eager loads 2025-03-17 14:43:23 -07:00
Marcus Moore b26a73e385 Filter out empty email address in alerts 2025-03-17 12:30:15 -07:00
Marcus Moore 650839b68a Update command name 2025-03-12 17:05:16 -07:00
Marcus Moore 388dc23241 Add comments 2025-03-12 17:02:05 -07:00
Marcus Moore 32b194ddc7 Update description 2025-03-12 16:44:37 -07:00
Marcus Moore 9cea6cee26 Docblock 2025-03-12 16:42:50 -07:00
Marcus Moore 73f64c53b1 Skip logs where created_by cannot be set 2025-03-12 16:42:39 -07:00
Marcus Moore 2091f66f5b Reorganize 2025-03-12 16:30:36 -07:00
snipe c711278b8b Merge pull request #16486 from marcusmoore/bug/sc-28639
Fixed location being automatically populated on asset checkin screen
2025-03-12 23:26:08 +00:00
snipe ff637f1926 Merge pull request #16497 from marcusmoore/fixes/diff-in
Fixed various carbon displays
2025-03-12 23:17:48 +00:00
Marcus Moore 4d978e0fc6 Fix eol rate in upcoming audit notification 2025-03-12 16:04:16 -07:00
Marcus Moore 167001ed54 Fix eol rate in tests 2025-03-12 16:01:32 -07:00
Marcus Moore 25fb1abc95 Fix eol rate in AssetObserver 2025-03-12 15:58:11 -07:00
Marcus Moore 658bef447d Fix eol rate in Actionlog 2025-03-12 15:55:13 -07:00
Marcus Moore a28ff22d03 Fix eol rate in ReportsController 2025-03-12 15:44:06 -07:00
Marcus Moore 63a1ee0047 Fix eol rate in AssetMaintenancesController 2025-03-12 15:38:54 -07:00
Marcus Moore ab8f4454d1 Add failing test for updating asset maintenance 2025-03-12 15:38:46 -07:00
Marcus Moore e439f1f42b Fix eol rate in AssetMaintenancesController 2025-03-12 15:35:46 -07:00
Marcus Moore 9be27bdf07 Add failing test for creating asset maintenance 2025-03-12 15:35:14 -07:00
snipe bfc122469c Merge pull request #16495 from snipe/add_highlight_for_low_qty
Added highlight to items when the remaining is less than the min_amt
2025-03-12 21:23:15 +00:00
snipe 036c225dcf Fixed dashboard string
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 21:22:24 +00:00
snipe 083cf78305 Added footer style for nicer number cell padding
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 21:17:41 +00:00
Marcus Moore 824271078e Fix eol rate in AssetsController 2025-03-12 13:55:12 -07:00
Marcus Moore 1122cd8567 Fix eol rate in asset transformer 2025-03-12 13:33:15 -07:00
Marcus Moore 6cf7df22cd Fix eol rate on asset show page 2025-03-12 13:28:34 -07:00
snipe 7421d089ff Added formatter and centering in presenters
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 20:25:00 +00:00
snipe bdb4bd73d2 Return an indicator for no value set
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 20:18:51 +00:00
snipe bf88597132 Added qtySumFormatter and made seats formatter more generic
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 20:14:23 +00:00
snipe dfacd876d5 Added remaining/min_amt to transformers
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 20:13:15 +00:00
snipe f1e70eb7a0 Add min_qty to asset model bulk edit
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 18:27:27 +00:00
snipe dbe78c30d5 Merge pull request #16491 from snipe/bug/sc-28671
Fixed new user modal pre-populating with first name and last name of acting user
2025-03-12 18:05:57 +00:00
snipe c1601b9a8c Updated tests
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 17:28:50 +00:00
snipe aa8e9f61d2 Check for value override
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 17:28:44 +00:00
snipe 163ddc8026 Check for value override
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 17:28:29 +00:00
snipe c73dba4a43 Better visual spacing
Signed-off-by: snipe <snipe@snipe.net>
2025-03-12 17:28:05 +00:00
snipe 5e61a814a7 Merge pull request #16489 from marcusmoore/bug/sc-28644
Fixed timestamp in action log for bulk accessory check in
2025-03-12 14:56:52 +00:00
Marcus Moore 935d3eea9f Attempt to match and populate created_by 2025-03-11 17:17:39 -07:00
Marcus Moore fffc606d9a Improve output 2025-03-11 17:06:02 -07:00
Marcus Moore c3a48182fd Display the created_by 2025-03-11 17:03:26 -07:00
Marcus Moore 147e610062 Add todos 2025-03-11 17:00:36 -07:00
Marcus Moore dd14eac1eb Prompt for confirmation 2025-03-11 16:56:07 -07:00
Marcus Moore 4954d972bb Write table of ids 2025-03-11 16:52:20 -07:00
Marcus Moore c8177eb51e Update timestamps 2025-03-11 16:50:16 -07:00
Marcus Moore a8cccffa1e Update output 2025-03-11 16:41:22 -07:00
Marcus Moore c774e969d7 Scaffold command 2025-03-11 16:34:07 -07:00
Marcus Moore b5fa538a54 Set created_by correctly in he action log 2025-03-11 16:05:02 -07:00
snipe fcdc1494c2 Merge pull request #16443 from azmcnutt/feature/labels_Avery_5520_1DBarcode
Nice work! Thank you!
2025-03-11 22:12:09 +00:00
snipe badb367e74 Merge pull request #16445 from ubc-cpsc/bugfix/CVE-2025-27515
Fixes CVE-2025-27515: Laravel has a File Validation Bypass
2025-03-11 21:55:58 +00:00
snipe 2960a13772 Upgrade webpack from 5.97.1 to 5.98.0 #16461
Signed-off-by: snipe <snipe@snipe.net>
2025-03-11 21:24:44 +00:00
snipe 5e10c213f6 Merge pull request #16488 from marcusmoore/bug/sc-28631
Nice catch!
2025-03-11 21:05:46 +00:00
snipe b61eacbdab Merge pull request #16473 from marcusmoore/chore/replace-form-close
Replaced call to Form::close()
2025-03-11 20:47:08 +00:00
Marcus Moore e2f643e7ed Backfill tests 2025-03-11 13:46:21 -07:00
Marcus Moore 9cbcfba4e9 Add test 2025-03-11 13:35:50 -07:00
Marcus Moore fd854072b0 Properly handle route model bound LicenseSeat not being found 2025-03-11 13:03:00 -07:00
Marcus Moore 18b208bba2 Account for missing location 2025-03-11 11:46:38 -07:00
Marcus Moore 71d93ca3c3 Use dedicated location select component
Copy/paste/modify from partials.forms.edit.location-select
2025-03-11 11:41:26 -07:00
Marcus Moore bac2760c6d Replace Form::close 2025-03-10 12:55:35 -07:00
snipe 33c9ea4bb1 Merge pull request #16065 from marcusmoore/chore/migrate-select-helper
Convert  Form::select to blade component
2025-03-07 01:05:58 +00:00
Marcus Moore d88fe1f48a Merge branch 'develop' into chore/migrate-select-helper 2025-03-06 13:39:05 -08:00
Marcus Moore f202817852 Use translation strings 2025-03-06 12:43:59 -08:00
Joël Pittet 618d81777a Fixes CVE-2025-27515 2025-03-06 12:33:29 -08:00
James M 0b6d810ca6 FEAT: Add Label 5520 with 1D barcode - remove 2D barcode
FEAT: Add Label 5520 with 1D barcode - remove 2D barcode
2025-03-06 11:12:16 -07:00
snipe 8c164d1b09 Merge pull request #16441 from snipe/added_modal_tests
Added modal tests
2025-03-06 15:06:08 +00:00
snipe 6d74053ca3 Added modal tests
Signed-off-by: snipe <snipe@snipe.net>
2025-03-06 15:02:55 +00:00
snipe f42fcd25b1 Make the assets tab active by default on locations page
Signed-off-by: snipe <snipe@snipe.net>
2025-03-06 12:05:36 +00:00
Marcus Moore 7df636515f Move to data providers 2025-03-05 18:20:40 -08:00
Marcus Moore 3db124e709 First pass at updating wording for asset checkout mail 2025-03-05 18:12:23 -08:00
snipe c5dd942f3d Merge pull request #16436 from marcusmoore/fixes/report-template-link
Fixed linking in saved report template dropdown
2025-03-06 01:06:18 +00:00
Marcus Moore 70de08a211 Replace hard-coded link to report template 2025-03-05 16:26:26 -08:00
snipe 64f49afce1 Merge pull request #16432 from marcusmoore/bug/sc-24475
Added validation around user store endpoint
2025-03-05 20:16:30 +00:00
Marcus Moore 25395e9af1 Add test for storing user 2025-03-05 11:37:03 -08:00
Marcus Moore 69009e027f Add authorization test 2025-03-05 11:34:45 -08:00
Marcus Moore 695c9d070f Require int for department and company ids when creating user via api 2025-03-05 11:32:04 -08:00
snipe c9f55bfd94 Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2025-03-05 17:04:57 +00:00
snipe 27d98fbb93 Merge pull request #16429 from snipe/bug/sc-28609
Updated HTML label colors in blue skin
2025-03-05 13:43:53 +00:00
snipe 0e1f40626f Updated label colors in blue skin
Signed-off-by: snipe <snipe@snipe.net>
2025-03-05 13:40:36 +00:00
snipe 7a312d075c Check for null on webhook url
Signed-off-by: snipe <snipe@snipe.net>
2025-03-05 11:59:35 +00:00
snipe e1156be919 Merge pull request #16427 from snipe/fixed_user_modal_email
Fixes user create modal - check if $item is set
2025-03-05 10:52:00 +00:00
snipe fcf7a543fd Check if $item is set
Signed-off-by: snipe <snipe@snipe.net>
2025-03-05 10:47:33 +00:00
Spencer Long 6201e475cb Merge branch 'develop' into feature/sc-24347 2025-03-04 21:20:46 -06:00
snipe bef54983fa Merge pull request #16421 from marcusmoore/chore/migrate-label-helpers-pt8
Replace calls to Form::label pt8
2025-03-05 01:01:21 +00:00
Marcus Moore f2b44f7002 Replace Form::label in company select partial 2025-03-04 16:46:43 -08:00
Marcus Moore de7d32f632 Replace Form::label on ldap settings page 2025-03-04 16:44:26 -08:00
Marcus Moore c798df2920 Replace Form::label on label settings page 2025-03-04 16:34:22 -08:00
snipe 779330af14 Merge pull request #16420 from marcusmoore/fixes/report-url
Fixed custom report template route
2025-03-05 00:11:30 +00:00
Marcus Moore f01ff1f1d7 Add dedicated named route 2025-03-04 16:07:35 -08:00
Marcus Moore 1e4daf0348 Fix route 2025-03-04 16:04:28 -08:00
snipe fae79a91f6 Merge pull request #16418 from marcusmoore/chore/custom-report-template-activity-log-removal
Stop reporting report template events to activity log
2025-03-04 23:28:43 +00:00
Marcus Moore e8ee218f39 Purge activity log of report template data 2025-03-04 15:20:23 -08:00
Marcus Moore cb5b0bd89c Stop reporting report template creates, updates, and deletes to action log 2025-03-04 15:11:37 -08:00
snipe 0ed49fa7a0 Dev assets
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 22:55:58 +00:00
snipe f25b8379e6 Merge pull request #16413 from Godmartinz/visited_link-color-change
Changed `visited-link` and `link` colors in default theme to be more accessible
2025-03-04 22:55:17 +00:00
snipe bc618fcef4 Merge pull request #16414 from Godmartinz/Audit_error_fix
Adds audit notification for MS Teams
2025-03-04 22:54:32 +00:00
Godfrey M 7c194422f3 Merge branch 'develop' into Audit_error_fix 2025-03-04 14:51:21 -08:00
Godfrey M 49ff47fbcf Merge branch 'develop' into visited_link-color-change 2025-03-04 14:47:48 -08:00
snipe 77c978d29a Merge pull request #16416 from Godmartinz/expiring-asset-email-test
fix Expiring alert tests
2025-03-04 21:11:52 +00:00
Godfrey M 437b3cd865 fix conflicts 2025-03-04 13:08:53 -08:00
Godfrey M 3641016271 remove commented code 2025-03-04 13:08:03 -08:00
Godfrey Martinez cc127c8e1e Merge branch 'develop' into expiring-asset-email-test 2025-03-04 13:06:36 -08:00
Godfrey M 4bac509341 fix Expiring alert tests 2025-03-04 12:58:04 -08:00
snipe f4f6dcb885 Updated test
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 20:48:59 +00:00
snipe d89f38bbfb Set defaults on save
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 20:38:33 +00:00
snipe 9351cc2252 Set default to 0
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 20:38:22 +00:00
snipe a31a732d38 Nicer UI for invert flag
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 20:38:15 +00:00
snipe 1be420b0e9 Fixed test
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 20:37:45 +00:00
snipe 2b0127ab0c Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 19:56:58 +00:00
snipe 8791640908 Add @Fiala06 as a contributor 2025-03-04 19:53:25 +00:00
snipe 1dacd25e6d Check for purchase_cost before adding the “each”
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 19:50:11 +00:00
snipe 45317c0959 Merge pull request #16415 from snipe/nicer_error_message_on_model_not_found
Nicer model name formatting on RMB model not found
2025-03-04 19:42:45 +00:00
snipe 63e4c42445 Added strtolower
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 19:33:17 +00:00
snipe d4e227f003 Whitespace
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 19:29:09 +00:00
snipe bbabbe1b87 Nicer model name formatting on RMB model not found
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 19:21:46 +00:00
Marcus Moore 2a156776a4 Merge branch 'develop' into chore/migrate-select-helper
# Conflicts:
#	resources/views/settings/branding.blade.php
#	resources/views/settings/general.blade.php
2025-03-04 11:19:38 -08:00
Godfrey M 15a09e5187 adds audit notification for MS Teams 2025-03-04 11:15:03 -08:00
Godfrey M 5eebdcddb2 changes visited and link colors 2025-03-04 10:12:45 -08:00
snipe 16b97c18e0 Shim route for asset maintenances redirect on RMB not found
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 17:16:16 +00:00
snipe fa72696a35 Removed redundent text
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 17:06:56 +00:00
snipe 9467ec2571 Nicer env text
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 17:05:45 +00:00
snipe ac3f61c96a Fixed extra escape character
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 17:00:44 +00:00
snipe d2e0a23994 Merge pull request #16412 from snipe/#16150_fixed_regression_in_branding_page
Fixed regression from #16150
2025-03-04 15:55:24 +00:00
snipe ccea55308b Fixed regression from #16150
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 15:53:30 +00:00
snipe acaa3a28e7 Merge pull request #16410 from snipe/#16407_component_checkout_and_notes_fix
Fixed #16407 - weird layout on components for non super user
2025-03-04 15:38:20 +00:00
snipe c4722898b7 Fixed #16407 - weird layout on components for non super user
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 15:37:25 +00:00
snipe e19003aea5 Merge pull request #16409 from snipe/#16386_user_dropdown_fields_not_populating_on_edit
Fixed #16386 - some fields not populating with user data on edit
2025-03-04 15:28:11 +00:00
snipe a09a9d3cd6 Added item to payload
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 15:26:25 +00:00
snipe 759e3794df Small upgrader UI fixes
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 14:57:28 +00:00
snipe c50b14763f Merge pull request #16403 from snipe/#16402_each_localization
Fixed #16402 - localize "each" string in components tab on asset view
2025-03-04 13:35:08 +00:00
snipe 369a68fe57 Fixed #16402 - Localize “each” in string
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 13:33:04 +00:00
snipe 83855d44d0 Merge pull request #16251 from Godmartinz/Audit_Checkin_warning_fix
refactors audit notification to mail, adds test, adds  alerts check to scheduler
2025-03-04 12:51:42 +00:00
snipe 6f847294ed Merge pull request #15911 from Fiala06/patch-1
Fixed duplicate entries preventing LDAP sync from continuing
2025-03-04 12:49:54 +00:00
snipe d556d1c6e7 Merge pull request #16150 from Godmartinz/add-translations-to-settings
Adds Translation strings to General and Branding Settings
2025-03-04 12:48:21 +00:00
snipe 3bb94e98f0 Merge pull request #16398 from marcusmoore/bug/sc-28535
Avoid using authenticated user's email address in email partial
2025-03-04 12:47:10 +00:00
Marcus Moore 8f5f6f3502 Avoid using authenticated user's email address in email partial 2025-03-03 16:28:08 -08:00
snipe b3792bfa00 Merge pull request #16396 from marcusmoore/chore/migrate-checkbox-helpers-pt10
Replace calls to Form::checkbox pt10
2025-03-03 20:52:54 +00:00
Marcus Moore 40f7257723 Replace call to Form::checkbox 2025-03-03 12:44:50 -08:00
snipe 8486256142 Merge pull request #16381 from marcusmoore/chore/migrate-checkbox-helpers-pt9
Replace calls to Form::checkbox pt9
2025-03-03 20:28:21 +00:00
snipe 7f36750e33 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2025-03-03 19:54:14 +00:00
snipe de046db106 Add @azmcnutt as a contributor 2025-03-03 19:53:30 +00:00
snipe eb1d27a5bc Merge pull request #16379 from azmcnutt/feature/settings_ldap_invert_active_flag
Feature/settings ldap invert active flag
2025-03-03 19:52:57 +00:00
Godfrey M cc0b9f404a merged develop, fix conflicts 2025-02-27 15:38:31 -08:00
Marcus Moore 70332696c6 Fix test by passing in required properties 2025-02-27 15:23:17 -08:00
Marcus Moore 7a9b5d61b0 Replace another Form::checkbox 2025-02-27 13:25:32 -08:00
Marcus Moore 5876259893 Replace another Form::checkbox 2025-02-27 13:19:26 -08:00
Marcus Moore 8755c54edc Replace Form::checkbox 2025-02-27 13:13:39 -08:00
Marcus Moore 014f3b7652 Cast to boolean 2025-02-27 13:12:01 -08:00
Marcus Moore 3a2579b205 WIP: replace Form::checkbox 2025-02-27 13:08:02 -08:00
James M 149474bfe3 Update general.php
FIX: Spelling error
2025-02-27 12:42:47 -07:00
James M b2b768dede Merge branch 'snipe:develop' into develop 2025-02-27 12:25:07 -07:00
snipe a9ed9e2a7f Merge pull request #16378 from snipe/wrap_pdf_table_results
Wrap long text in PDF export in tables
2025-02-27 19:06:34 +00:00
snipe ce8523b00a Fixed wrapping
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 18:58:18 +00:00
snipe 7076a68d35 Wrap table results in PDF
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 18:38:10 +00:00
James M 112112d258 Feat: #14926 LDAP Active Flag - Add config option to make False = Enable 2025-02-27 10:52:12 -07:00
snipe 3928c8afe9 Merge pull request #16376 from uberbrady/improve_safety_csv_charset_detection
Add some safeties around the charset-detection and transliteration
2025-02-27 16:26:44 +00:00
snipe 23ce54e80c Make sure we’re saving the last_login in 2FA auths
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 16:17:59 +00:00
Brady Wetherington 646e3e8df5 Complete failed-transliteration test, clean up error, new translation string 2025-02-27 16:10:56 +00:00
snipe 30c4e9dbf7 Use formatter for created_at on unaccepted assets
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 15:48:24 +00:00
snipe 27fc30a881 Nicer button layout on unaccepted assets
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 15:43:53 +00:00
snipe 8ac7cda4ee Merge pull request #16366 from marcusmoore/chore/migrate-checkbox-helpers-pt7
Replace calls to Form::checkbox pt7
2025-02-27 15:01:31 +00:00
snipe 6f04d314a8 Merge pull request #16367 from marcusmoore/chore/migrate-checkbox-helpers-pt8
Replace calls to Form::checkbox pt8
2025-02-27 15:01:19 +00:00
snipe 1051b1d16d Merge pull request #16375 from snipe/fixes_16371_name_not_included_in_reminder_emails
Fixed #16371 - incorrect count and missing name in acceptance reminder email
2025-02-27 15:00:04 +00:00
snipe 115bb94704 Merge pull request #16156 from marcusmoore/acceptance-reminder-subject
Added "Reminder" to subject line of follow up asset checkout emails
2025-02-27 14:55:59 +00:00
snipe 25807cc62f Fixed constructor
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 14:22:48 +00:00
snipe cd1d1b2d3e Fixed count
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 14:22:40 +00:00
Brady Wetherington 6dcd3bfd30 Add some safeties around the charset-detection and transliteration 2025-02-27 13:44:31 +00:00
snipe b8799f8038 Bumped bash
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 12:21:28 +00:00
Marcus Moore a0dc056da8 Replace Form::checkbox on label settings page 2025-02-26 16:33:07 -08:00
Marcus Moore 27aeb518ff Replace Form::checkbox on general settings page 2025-02-26 16:18:00 -08:00
snipe dc619bb0dc Merge pull request #16365 from marcusmoore/chore/migrate-checkbox-helpers-pt6
Replace calls to Form::checkbox pt6
2025-02-26 23:54:50 +00:00
Marcus Moore 245a16c377 Replace Form::checkbox on branding settings page 2025-02-26 15:37:07 -08:00
Marcus Moore de3c1d159f Replace Form::checkbox on branding settings page 2025-02-26 15:35:17 -08:00
Marcus Moore af6d9e4a00 Replace Form::checkbox on custom report pages 2025-02-26 15:15:48 -08:00
snipe 8c8af3062e Merge pull request #16354 from snipe/dont_checkin_for_pending_on_asset_update
Allow pending as an asset status type that does not automatically check the asset in
2025-02-26 21:05:11 +00:00
snipe 2ff47edb94 Merge pull request #16361 from Godmartinz/acceptancer_reminder_unlisted_email_info
Fixed acceptance reminder command lag on users with no associated email
2025-02-26 20:33:02 +00:00
snipe d923d29bad Merge pull request #16360 from spencerrlongg/bug/sc-28537
Add Safety Around Bulk Status Update
2025-02-26 20:32:19 +00:00
Godfrey M 899119ae2d changes output to a table 2025-02-26 12:30:19 -08:00
snipe e031de8e49 Merge pull request #16363 from snipe/added_a_few_more_no_interaction_calls
Added `--no-interaction` to additional passport commands
2025-02-26 20:19:37 +00:00
snipe a81c520d93 Follow up for #16341
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 20:15:09 +00:00
Godfrey M 8352e81228 fix unaccepted reminder test 2025-02-26 11:50:08 -08:00
Godfrey M fd0174ff32 remove unwanted changes to livewire 2025-02-26 11:31:41 -08:00
Godfrey M cc26aa02b2 fix acceptance reminder command no email list 2025-02-26 11:25:21 -08:00
snipe 616f3558dd Update example env
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 19:14:25 +00:00
spencerrlongg d6e266cec1 make super safe 2025-02-26 13:11:49 -06:00
snipe 31516d7f24 Merge pull request #16356 from snipe/added_fields_for_model_search
Added name, model_number and notes for strict search
2025-02-26 12:46:13 +00:00
snipe e79af255aa Added name, model_number and notes for strict search
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 12:43:54 +00:00
snipe 5b0d7f4064 One more chonk
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 12:01:50 +00:00
snipe 2f6af10c5d Bumped chunk for custom report
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 11:59:34 +00:00
snipe afabda9235 Remove greater than 0 for alert threshold - fixes FD-47040
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 11:38:33 +00:00
snipe 1618c9ae8e Changed confirmation message
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 10:53:41 +00:00
snipe 5344ef4a1a Allow pending as an asset status that does not automatically check the asset in
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 10:46:38 +00:00
snipe 16420b1e00 Audit Log Number under Days to next Audit [sc-28530]
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 10:25:07 +00:00
snipe 62f66e724e Drop errors to warnings to stop pooping on rollbar
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 09:54:13 +00:00
snipe 9b0ea51d35 Moved composer clear commands to after composer install per #16334
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 09:37:31 +00:00
snipe 992214fc66 Check for adminuser withTrashed
Signed-off-by: snipe <snipe@snipe.net>
2025-02-26 08:25:22 +00:00
snipe 93dab12461 Merge pull request #16342 from snipe/nicer_upgrade_script
Nicer upgrade.php UI
2025-02-26 07:16:28 +00:00
snipe ea0f105180 Merge pull request #16347 from marcusmoore/chore/migrate-checkbox-helpers-pt4
Replace calls to Form::checkbox pt4
2025-02-26 07:05:38 +00:00
snipe 5b6da0c1e8 Merge pull request #16348 from marcusmoore/chore/migrate-checkbox-helpers-pt5
Replace calls to Form::checkbox pt5
2025-02-26 07:05:22 +00:00
snipe 4d7655bbe1 Merge pull request #16346 from marcusmoore/chore/migrate-checkbox-helpers-pt3
Replace calls to Form::checkbox pt3
2025-02-26 07:04:27 +00:00
snipe 5e3855ee5b Merge pull request #16345 from marcusmoore/fixes/update-custom-fields
Fixed renaming custom fields
2025-02-26 07:03:40 +00:00
Marcus Moore e01226a174 Replace Form::checkbox on saml settings page 2025-02-25 17:31:05 -08:00
Marcus Moore f9ccf32af4 Replace Form::checkbox on ldap settings page 2025-02-25 17:22:44 -08:00
Marcus Moore 2c5170a218 Replace Form::checkbox on security settings page 2025-02-25 17:09:25 -08:00
Marcus Moore 15f842e2dc Replace Form::checkbox on bulk user edit page 2025-02-25 16:00:46 -08:00
Marcus Moore 46b31dfe14 Replace Form::checkbox on user create and edit page 2025-02-25 14:32:39 -08:00
Marcus Moore d88c79366c Replace Form::checkbox on google settings page 2025-02-25 14:27:07 -08:00
Marcus Moore de330a47cd Replace Form::checkbox on asset tag settings page 2025-02-25 14:25:02 -08:00
Marcus Moore ecd7dc2094 Replace Form::checkbox on alert settings page 2025-02-25 14:23:26 -08:00
Marcus Moore 510946e0eb Replace Form::checkbox in logo upload partial 2025-02-25 14:20:43 -08:00
Marcus Moore 37e4a13979 Replace Form::checkbox in image upload partial 2025-02-25 14:15:12 -08:00
Marcus Moore c73d64cdbc Remove manual doctrine mapping for enums 2025-02-25 13:37:23 -08:00
snipe 037d2d9e84 Merge pull request #16341 from uberbrady/fix_passport_install_no_interaction
Fixes #16331 - Don't make passport:install command require user input
2025-02-25 16:43:49 +00:00
Brady Wetherington bce2007b97 Fixes #16331 - Don't make passport:install command require user input 2025-02-25 16:38:40 +00:00
Marcus Moore e88bba51bb Merge branch 'develop' into acceptance-reminder-subject 2025-02-24 11:55:50 -08:00
Marcus Moore f97211f6cd Remove unused language string 2025-02-24 11:45:58 -08:00
Marcus Moore 027c2b3627 Change subject to "You have Unaccepted Assets." 2025-02-24 11:45:23 -08:00
Godfrey M 4c43a06eee add overdue asset to test 2025-02-13 11:19:49 -08:00
Godfrey M 25c8449e86 remove unused 2025-02-13 11:14:18 -08:00
Godfrey M 13e1f4a127 adds mailable 2025-02-13 11:09:39 -08:00
Godfrey M ed96fd766c refactors audit notification to mail, adds test, ads check to scheduler 2025-02-13 11:08:53 -08:00
Marcus Moore 00e7795414 Merge branch 'develop' into chore/migrate-select-helper
# Conflicts:
#	resources/views/partials/forms/edit/company.blade.php
2025-02-10 15:44:02 -08:00
Marcus Moore 7cbb3f7e07 Add assertion 2025-01-30 11:48:55 -08:00
Marcus Moore 7e9c564d0b Simplify test 2025-01-30 11:47:43 -08:00
Marcus Moore fc88b2487f Extract method 2025-01-30 11:44:37 -08:00
Marcus Moore e94ee48f74 Extract helper 2025-01-30 10:37:11 -08:00
Marcus Moore 6a4a5d1380 Add translation 2025-01-30 10:35:31 -08:00
Marcus Moore ab9e9b66d2 Reduce complexity 2025-01-29 16:27:18 -08:00
Marcus Moore c15c338ffd Merge if/else 2025-01-29 16:25:37 -08:00
Marcus Moore d1197d015c Add another case scenario 2025-01-29 16:24:43 -08:00
Marcus Moore ce31ce477e Inline additional variables 2025-01-29 16:16:47 -08:00
Marcus Moore 78f9292555 Inline variable 2025-01-29 16:15:27 -08:00
Marcus Moore 4e7c6bd2cf Fix relationship 2025-01-29 16:14:09 -08:00
Marcus Moore 70aed45bfe Improve naming 2025-01-29 15:56:20 -08:00
Marcus Moore e2805f4033 Add "Reminder" to subject line 2025-01-29 15:36:45 -08:00
Marcus Moore d254a40e0a Scaffold tests 2025-01-29 15:21:10 -08:00
Marcus Moore fdcb891cbb Improve test case 2025-01-29 15:20:56 -08:00
Godfrey M 16d322d70e fix translation 2025-01-29 10:38:30 -08:00
Godfrey M 2163312997 adds translations for branding and general settings 2025-01-29 10:35:26 -08:00
Godfrey M 0dfb71cfe5 added some translations to branding and general setrting 2025-01-28 11:56:10 -08:00
Tobias Regnery 4e0bcac1a1 Furhter validation for scoped locations
There is a new validator introduced that checks on object update (assets, users, etc.) if the company matches the locations company.
In case of the creation of a new location it must be checked that the parent matches the own company.
On updating a location a check for every related object must be made to see if the company matches the location.

Signed-off-by: Tobias Regnery <tobias.regnery@gmail.com>
2025-01-24 11:12:11 +01:00
Tobias Regnery 6921df9334 Check for inconsistencies before activating scoped locations
Before activating scoped location all locations and their related objects will be checked.
If there are locations with different companies than the related objects error out.

Because this operation is quite slow, bail out on the first inconsistent entry.
There is a new artisan command introduced that checks every location.

Depending on the size of the database, this will take very long.

Signed-off-by: Tobias Regnery <tobias.regnery@gmail.com>
2025-01-23 15:26:04 +01:00
snipe b6f05bff1f Merge branch 'develop' into feature/locations_with_companies 2025-01-17 17:18:17 +00:00
Marcus Moore abb50fcd09 Delete unused partials 2025-01-13 16:21:19 -08:00
Marcus Moore 3fadeebd82 Convert additional Form::select to blade component 2025-01-13 16:20:02 -08:00
Marcus Moore 9a043da005 Convert additional Form::select to blade component 2025-01-13 16:16:49 -08:00
Marcus Moore 3eb307a019 Convert additional Form::select to blade component 2025-01-13 15:58:15 -08:00
Marcus Moore 888c96718c Convert additional Form::select to blade component 2025-01-13 15:38:51 -08:00
Marcus Moore bc3f236b86 Weaken comparsion so string and int keys match 2025-01-13 15:38:45 -08:00
Marcus Moore 1f4cc0a4b4 Convert additional Form::select to blade component 2025-01-13 15:23:43 -08:00
Marcus Moore 589ec3a564 Convert additional Form::select to blade component 2025-01-13 13:08:59 -08:00
Marcus Moore 2206b0a699 Convert additional Form::select to blade component 2025-01-13 12:50:18 -08:00
Marcus Moore d74454620a Convert Form::select to blade component 2025-01-13 12:45:49 -08:00
Marcus Moore ed0cec5ba6 Introduce select component 2025-01-13 12:17:01 -08:00
spencerrlongg 513c78a09f Refactor CheckoutRequest actions for consistency
Renamed CheckoutRequest action classes to include "Action" in their names for consistency and clarity. Enhanced error handling in controllers to standardize error responses with translations. Updated usage of the renamed action classes throughout the code to ensure proper integration.
2024-12-03 17:51:12 -06:00
spencerrlongg 0103f20193 Merge branch 'develop' into feature/sc-24347 2024-12-03 16:05:39 -06:00
Fiala06 bdb0e6c2a3 Update LdapSync.php
Fix for duplicate entries preventing the sync from continuing. 

  SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '13178-6' for key 'PRIMARY' (Connection: mysql, SQL: insert into users_groups (group_id, user_id) values (6, 13178))
2024-12-02 11:07:37 -08:00
Tobias Regnery 651d1c735b Another slightly less ugly way for backward compatibility
Instead of using a constructor, add a special check in the boot-method for locations.
This seems to fit better in the system and does hopefully not break the existing tests.

Signed-off-by: Tobias Regnery <tobias.regnery@gmail.com>
2024-11-04 10:03:38 +01:00
Tobias Regnery 1318dc6111 Add a backward compatibility setting for locations with companies
Now that locations have a company_id they get restricted to the users company with FullMultipleCompanySupport.
This breaks backward compatibility, because before everyone can handle locations without restrictions.
Add a setting right below FullMultipleCompanySupport so that everyone can switch to the desired behaviour.
The default is off and the existing behaviour is preserved.
2024-11-04 10:03:38 +01:00
Tobias Regnery 1ccbf8942c Add ability to tie locations to companies
Locations are the last big part of the application that can't be tied to companies.
This can be a problem with FullMultipleCompanySupport, because you can't restrict the visibility of locations to the company of the users.

In order to change this, add a company_id to the locations table and wire everything up in the views and controllers.
Aditionally add a new formatter to filter the locations to a specific company, like it is done for assets.

Locations are properly scoped to the users company if FullMultipleCompanySupport is enabled.
If a parent location of a location has a different company than the user, the location does not show up.
2024-11-04 10:03:37 +01:00
spencerrlongg c76cccbb68 rm extraneous methods in exception 2024-10-23 12:56:36 -05:00
spencerrlongg fdb6970f36 this is pretty much done 2024-10-23 01:41:27 -05:00
spencerrlongg b0d7eb2168 cancel action finished 2024-10-22 22:44:28 -05:00
spencerrlongg 79e6eafafa cancelled scaffolded out 2024-10-22 22:33:29 -05:00
spencerrlongg 3ee008e871 revert 2024-10-22 20:54:20 -05:00
spencerrlongg 48c812d345 bunch of cleanup, split cancel out 2024-10-22 20:48:27 -05:00
spencerrlongg 8a99cc1391 test working 2024-10-22 17:25:58 -05:00
spencerrlongg b59bf495e1 stuff 2024-10-22 15:09:35 -05:00
spencerrlongg e40849c910 this stuff works 2024-10-21 17:31:26 -05:00
spencerrlongg 95a32864cf hm, lots to think about 🤔 2024-10-17 18:11:18 -05:00
spencerrlongg 1fd945c2d8 this works 2024-10-17 12:45:49 -05:00
spencerrlongg b2ff34260a hm, lots of thinking to do 2024-10-17 00:02:15 -05:00
spencerrlongg a524c0b418 more work 2024-10-16 18:48:15 -05:00
spencerrlongg b1d62cc478 initial untested 2024-10-16 17:23:22 -05:00
1216 changed files with 17627 additions and 7195 deletions
+63
View File
@@ -3289,6 +3289,69 @@
"contributions": [
"code"
]
},
{
"login": "azmcnutt",
"name": "James M",
"avatar_url": "https://avatars.githubusercontent.com/u/31522486?v=4",
"profile": "https://github.com/azmcnutt",
"contributions": [
"code"
]
},
{
"login": "Fiala06",
"name": "Fiala06",
"avatar_url": "https://avatars.githubusercontent.com/u/5183146?v=4",
"profile": "https://github.com/Fiala06",
"contributions": [
"code"
]
},
{
"login": "ntaylor-86",
"name": "Nathan Taylor",
"avatar_url": "https://avatars.githubusercontent.com/u/28693782?v=4",
"profile": "https://github.com/ntaylor-86",
"contributions": [
"code"
]
},
{
"login": "fvollmer",
"name": "fvollmer",
"avatar_url": "https://avatars.githubusercontent.com/u/16699443?v=4",
"profile": "https://github.com/fvollmer",
"contributions": [
"code"
]
},
{
"login": "36864",
"name": 36864,
"avatar_url": "https://avatars.githubusercontent.com/u/109086466?v=4",
"profile": "https://github.com/36864",
"contributions": [
"code"
]
},
{
"login": "CloCkWeRX",
"name": "Daniel O'Connor",
"avatar_url": "https://avatars.githubusercontent.com/u/365751?v=4",
"profile": "http://clockwerx.blogspot.com/",
"contributions": [
"code"
]
},
{
"login": "BeatSpark",
"name": "BeatSpark",
"avatar_url": "https://avatars.githubusercontent.com/u/102852568?v=4",
"profile": "https://github.com/BeatSpark",
"contributions": [
"code"
]
}
]
}
+8
View File
@@ -35,6 +35,7 @@ DB_USERNAME=snipeit
DB_PASSWORD=changeme1234
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=true
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
@@ -78,6 +79,13 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
BACKUP_ENV=true
# --------------------------------------------
# OPTIONAL: CHANGE PHP UPLOAD LIMITS (UNCOMMENT WHEN NEEDING TO BE CHANGED)
# --------------------------------------------
#PHP_UPLOAD_LIMIT=10
#PHP_POST_MAX_SIZE=10
#PHP_UPLOAD_MAX_FILESIZE=10
#PHP_MEMORY_LIMIT=10
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS
+10
View File
@@ -35,6 +35,7 @@ DB_PASSWORD=changeme1234
MYSQL_ROOT_PASSWORD=changeme1234
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=true
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
@@ -83,6 +84,15 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null
MAIL_BACKUP_NOTIFICATION_ADDRESS=null
BACKUP_ENV=true
# --------------------------------------------
# OPTIONAL: CHANGE PHP UPLOAD LIMITS (UNCOMMENT WHEN NEEDING TO BE CHANGED)
# --------------------------------------------
#PHP_UPLOAD_LIMIT=10
#PHP_POST_MAX_SIZE=10
#PHP_UPLOAD_MAX_FILESIZE=10
#PHP_MEMORY_LIMIT=10
# --------------------------------------------
# OPTIONAL: SESSION SETTINGS
# --------------------------------------------
+3 -1
View File
@@ -30,6 +30,7 @@ DB_USERNAME=null
DB_PASSWORD=null
DB_PREFIX=null
DB_DUMP_PATH='/usr/bin'
DB_DUMP_SKIP_SSL=false
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_SANITIZE_BY_DEFAULT=false
@@ -99,7 +100,7 @@ PASSPORT_COOKIE_NAME='snipeit_passport_token'
COOKIE_DOMAIN=null
SECURE_COOKIES=false
API_TOKEN_EXPIRATION_YEARS=15
BS_TABLE_STORAGE=cookieStorage
BS_TABLE_STORAGE=localStorage
BS_TABLE_DEEPLINK=true
# --------------------------------------------
@@ -178,6 +179,7 @@ PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50
# OPTIONAL: MISC
# --------------------------------------------
LOG_CHANNEL=single
LOG_DEPRECATIONS=false
LOG_MAX_DAYS=10
APP_LOCKED=false
APP_CIPHER=AES-256-CBC
+2 -2
View File
@@ -22,11 +22,11 @@ permissions:
jobs:
codacy-security-scan:
# Ensure schedule job never runs on forked repos. It's only executed for 'snipe/snipe-it'
# Ensure schedule job never runs on forked repos. It's only executed for 'grokability/snipe-it'
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
if: (github.repository == 'snipe/snipe-it') || ((github.repository != 'snipe/snipe-it') && (github.event_name != 'schedule'))
if: (github.repository == 'grokability/snipe-it') || ((github.repository != 'grokability/snipe-it') && (github.event_name != 'schedule'))
name: Codacy Security Scan
runs-on: ubuntu-latest
steps:
-86
View File
@@ -1,86 +0,0 @@
# Snipe-IT (Alpine) Docker image build for hub.docker.com
name: Docker images (Alpine)
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
on:
push:
branches:
- master
- develop
tags:
- 'v**'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
docker:
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
if: github.repository == 'snipe/snipe-it'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=tag,suffix=-alpine
type=semver,pattern=v{{major}}-latest-alpine
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'snipe/snipe-it' image
###############################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.alpine
platforms: linux/amd64,linux/arm64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
+151
View File
@@ -0,0 +1,151 @@
# Snipe-IT Docker image build for hub.docker.com
name: Docker ARM64 images (Ubuntu)
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
on:
push:
branches:
- master
- develop
tags:
- 'v**'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
docker-ubuntu-arm:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
runs-on: ubuntu-24.04-arm
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=tag
type=semver,pattern=v{{major}}-latest
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'grokability/snipe-it' image
###############################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v5
with:
images: grokability/snipe-it
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/arm64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
docker-alpine-arm:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
runs-on: ubuntu-24.04-arm
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=tag,suffix=-alpine
type=semver,pattern=v{{major}}-latest-alpine
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'grokability/snipe-it' image
###############################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v5
with:
images: grokability/snipe-it
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.alpine
platforms: linux/arm64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
+151
View File
@@ -0,0 +1,151 @@
# Snipe-IT Docker image build for hub.docker.com
name: Docker Intel/amd64 images (Ubuntu)
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
on:
push:
branches:
- master
- develop
tags:
- 'v**'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
docker-ubuntu-intel:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=tag
type=semver,pattern=v{{major}}-latest
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'grokability/snipe-it' image
###############################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v5
with:
images: grokability/snipe-it
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
docker-alpine-intel:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=tag,suffix=-alpine
type=semver,pattern=v{{major}}-latest-alpine
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'grokability/snipe-it' image
###############################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v5
with:
images: grokability/snipe-it
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.alpine
platforms: linux/amd64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
-86
View File
@@ -1,86 +0,0 @@
# Snipe-IT Docker image build for hub.docker.com
name: Docker images
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
on:
push:
branches:
- master
- develop
tags:
- 'v**'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
pull_request:
permissions:
contents: read
jobs:
docker:
# Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it'
if: github.repository == 'snipe/snipe-it'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=tag
type=semver,pattern=v{{major}}-latest
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'snipe/snipe-it' image
###############################################
# https://github.com/docker/metadata-action
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
+1 -1
View File
@@ -67,7 +67,7 @@ jobs:
run: |
php artisan key:generate
php artisan migrate --force
php artisan passport:install
php artisan passport:install --no-interaction
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
run: |
php artisan key:generate
php artisan migrate --force
php artisan passport:install
php artisan passport:install --no-interaction
chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
+54 -53
View File
@@ -1,59 +1,60 @@
Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far:
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
| [<img src="https://avatars3.githubusercontent.com/u/197404?v=3" width="110px;"/><br /><sub>snipe</sub>](http://www.snipe.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/36335?v=3" width="110px;"/><br /><sub>Brady Wetherington</sub>](http://www.uberbrady.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/3803132?v=3" width="110px;"/><br /><sub>Daniel Meltzer</sub>](https://github.com/dmeltzer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1609106?v=3" width="110px;"/><br /><sub>Michael T</sub>](http://www.tuckertechonline.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [<img src="https://avatars2.githubusercontent.com/u/3274937?v=3" width="110px;"/><br /><sub>madd15</sub>](https://github.com/madd15)<br />[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [<img src="https://avatars2.githubusercontent.com/u/894126?v=3" width="110px;"/><br /><sub>Vincent Sposato</sub>](https://github.com/vsposato)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [<img src="https://avatars0.githubusercontent.com/u/1639757?v=3" width="110px;"/><br /><sub>Andrea Bergamasco</sub>](https://github.com/vjandrea)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars0.githubusercontent.com/u/10640152?v=3" width="110px;"/><br /><sub>Karol</sub>](https://github.com/kpawelski)<br />[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [<img src="https://avatars3.githubusercontent.com/u/600106?v=3" width="110px;"/><br /><sub>morph027</sub>](http://blog.morph027.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [<img src="https://avatars3.githubusercontent.com/u/22935755?v=3" width="110px;"/><br /><sub>fvleminckx</sub>](https://github.com/fvleminckx)<br />[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars2.githubusercontent.com/u/15633547?v=3" width="110px;"/><br /><sub>itsupportcmsukorg</sub>](https://github.com/itsupportcmsukorg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/12373799?v=3" width="110px;"/><br /><sub>Frank</sub>](https://override.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [<img src="https://avatars0.githubusercontent.com/u/10137?v=3" width="110px;"/><br /><sub>Deleted user</sub>](https://github.com/ghost)<br />[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [<img src="https://avatars1.githubusercontent.com/u/10802313?v=3" width="110px;"/><br /><sub>tiagom62</sub>](https://github.com/tiagom62)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
| [<img src="https://avatars3.githubusercontent.com/u/2389047?v=3" width="110px;"/><br /><sub>Ryan Stafford</sub>](https://github.com/rystaf)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [<img src="https://avatars2.githubusercontent.com/u/10345935?v=3" width="110px;"/><br /><sub>Eammon Hanlon</sub>](https://github.com/ehanlon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [<img src="https://avatars0.githubusercontent.com/u/441924?v=3" width="110px;"/><br /><sub>zjean</sub>](https://github.com/zjean)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [<img src="https://avatars0.githubusercontent.com/u/12660103?v=3" width="110px;"/><br /><sub>Matthias Frei</sub>](http://www.frei.media)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [<img src="https://avatars0.githubusercontent.com/u/3767518?v=3" width="110px;"/><br /><sub>opsydev</sub>](https://github.com/opsydev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [<img src="https://avatars1.githubusercontent.com/u/82290?v=3" width="110px;"/><br /><sub>Daniel Dreier</sub>](http://www.ddreier.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [<img src="https://avatars0.githubusercontent.com/u/23448?v=3" width="110px;"/><br /><sub>Nikolai Prokoschenko</sub>](http://rassie.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/13452757?v=3" width="110px;"/><br /><sub>Drew</sub>](https://github.com/YetAnotherCodeMonkey)<br />[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [<img src="https://avatars0.githubusercontent.com/u/1342320?v=3" width="110px;"/><br /><sub>Walter</sub>](https://github.com/merid14)<br />[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [<img src="https://avatars3.githubusercontent.com/u/11254614?v=3" width="110px;"/><br /><sub>Petr Baloun</sub>](https://github.com/balous)<br />[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [<img src="https://avatars0.githubusercontent.com/u/6117660?v=3" width="110px;"/><br /><sub>reidblomquist</sub>](https://github.com/reidblomquist)<br />[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/539914?v=3" width="110px;"/><br /><sub>Mathieu Kooiman</sub>](https://github.com/mathieuk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [<img src="https://avatars3.githubusercontent.com/u/6606421?v=3" width="110px;"/><br /><sub>csayre</sub>](https://github.com/csayre)<br />[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/768488?v=3" width="110px;"/><br /><sub>Adam Dunson</sub>](https://github.com/adamdunson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/5547470?v=3" width="110px;"/><br /><sub>Hereward</sub>](https://github.com/thehereward)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [<img src="https://avatars0.githubusercontent.com/u/5802977?v=3" width="110px;"/><br /><sub>swoopdk</sub>](https://github.com/swoopdk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [<img src="https://avatars1.githubusercontent.com/u/3470403?v=3" width="110px;"/><br /><sub>Abdullah Alansari</sub>](https://linkedin.com/in/ahimta)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [<img src="https://avatars0.githubusercontent.com/u/796443?v=3" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [<img src="https://avatars0.githubusercontent.com/u/614564?v=3" width="110px;"/><br /><sub>Patrick Gallagher</sub>](http://macadmincorner.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/7165922?v=3" width="110px;"/><br /><sub>Miliamber</sub>](https://github.com/Miliamber)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [<img src="https://avatars3.githubusercontent.com/u/861766?v=3" width="110px;"/><br /><sub>hawk554</sub>](https://github.com/hawk554)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/1695622?v=3" width="110px;"/><br /><sub>Justin Kerr</sub>](http://jbirdkerr.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [<img src="https://avatars3.githubusercontent.com/u/11426176?v=3" width="110px;"/><br /><sub>Ira W. Snyder</sub>](http://www.irasnyder.com/devel/)<br />[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/2475759?v=3" width="110px;"/><br /><sub>Aladin Alaily</sub>](https://github.com/aalaily)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [<img src="https://avatars0.githubusercontent.com/u/10247644?v=3" width="110px;"/><br /><sub>Chase Hansen</sub>](https://github.com/kobie-chasehansen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [<img src="https://avatars2.githubusercontent.com/u/13545400?v=3" width="110px;"/><br /><sub>IDM Helpdesk</sub>](https://github.com/IDM-Helpdesk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [<img src="https://avatars2.githubusercontent.com/u/614439?v=3" width="110px;"/><br /><sub>Kai</sub>](http://balticer.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [<img src="https://avatars1.githubusercontent.com/u/8762511?v=3" width="110px;"/><br /><sub>Michael Daniels</sub>](http://www.michaeldaniels.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/1532660?v=3" width="110px;"/><br /><sub>Tom Castleman</sub>](http://tomcastleman.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [<img src="https://avatars3.githubusercontent.com/u/10723243?v=3" width="110px;"/><br /><sub>Daniel Nemanic</sub>](https://github.com/DanielNemanic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [<img src="https://avatars0.githubusercontent.com/u/150648?v=3" width="110px;"/><br /><sub>SouthWolf</sub>](https://github.com/southwolf)<br />[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [<img src="https://avatars2.githubusercontent.com/u/131616?v=3" width="110px;"/><br /><sub>Ivar Nesje</sub>](https://github.com/ivarne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [<img src="https://avatars1.githubusercontent.com/u/62333?v=3" width="110px;"/><br /><sub>Jérémy Benoist</sub>](http://www.j0k3r.net)<br />[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/724344?v=3" width="110px;"/><br /><sub>Chris Leathley</sub>](https://github.com/cleathley)<br />[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars0.githubusercontent.com/u/972498?v=3" width="110px;"/><br /><sub>splaer</sub>](https://github.com/splaer)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/967362?v=3" width="110px;"/><br /><sub>Joe Ferguson</sub>](http://www.joeferguson.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [<img src="https://avatars3.githubusercontent.com/u/6108682?v=3" width="110px;"/><br /><sub>diwanicki</sub>](https://github.com/diwanicki)<br />[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/2527115?v=3" width="110px;"/><br /><sub>Lee Thoong Ching</sub>](https://github.com/pakkua80)<br />[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [<img src="https://avatars1.githubusercontent.com/u/461491?v=3" width="110px;"/><br /><sub>Marek Šuppa</sub>](http://shu.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [<img src="https://avatars1.githubusercontent.com/u/8693762?v=3" width="110px;"/><br /><sub>Juan J. Martinez</sub>](https://github.com/mizar1616)<br />[🌍](#translation-mizar1616 "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1458388?v=3" width="110px;"/><br /><sub>R Ryan Dial</sub>](https://github.com/rrdial)<br />[🌍](#translation-rrdial "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2871745?v=3" width="110px;"/><br /><sub>Andrej Manduch</sub>](https://github.com/burlito)<br />[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") |
| [<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://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") | [<img src="https://avatars1.githubusercontent.com/u/6551003?v=4" width="110px;"/><br /><sub>Richard Hofman</sub>](https://github.com/richardhofman6)<br />[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [<img src="https://avatars0.githubusercontent.com/u/3697569?v=4" width="110px;"/><br /><sub>gizzmojr</sub>](https://github.com/gizzmojr)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [<img src="https://avatars3.githubusercontent.com/u/404729?v=4" width="110px;"/><br /><sub>Jenny Li</sub>](https://github.com/imjennyli)<br />[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/869227?v=4" width="110px;"/><br /><sub>Geoff Young</sub>](https://github.com/GeoffYoung)<br />[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [<img src="https://avatars3.githubusercontent.com/u/1068477?v=4" width="110px;"/><br /><sub>Elliot Blackburn</sub>](http://www.elliotblackburn.com)<br />[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") |
| [<img src="https://avatars1.githubusercontent.com/u/6357451?v=4" width="110px;"/><br /><sub>Tõnis Ormisson</sub>](http://andmemasin.eu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [<img src="https://avatars0.githubusercontent.com/u/449411?v=4" width="110px;"/><br /><sub>Nicolai Essig</sub>](http://www.nicolai-essig.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [<img src="https://avatars1.githubusercontent.com/u/14809698?v=4" width="110px;"/><br /><sub>Danielle</sub>](https://github.com/techincolor)<br />[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/18545156?v=4" width="110px;"/><br /><sub>Lawrence</sub>](https://github.com/TheVakman)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [<img src="https://avatars1.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>uknzaeinozpas</sub>](https://github.com/uknzaeinozpas)<br />[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [<img src="https://avatars3.githubusercontent.com/u/422752?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/Gelob)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/10672546?v=4" width="110px;"/><br /><sub>vcordes79</sub>](https://github.com/vcordes79)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/27958330?v=4" width="110px;"/><br /><sub>fordster78</sub>](https://github.com/fordster78)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [<img src="https://avatars0.githubusercontent.com/u/34064225?v=4" width="110px;"/><br /><sub>CronKz</sub>](https://github.com/CronKz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [<img src="https://avatars1.githubusercontent.com/u/585486?v=4" width="110px;"/><br /><sub>Tim Bishop</sub>](https://github.com/tdb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [<img src="https://avatars2.githubusercontent.com/u/5384694?v=4" width="110px;"/><br /><sub>Sean McIlvenna</sub>](https://www.seanmcilvenna.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [<img src="https://avatars3.githubusercontent.com/u/36515590?v=4" width="110px;"/><br /><sub>cepacs</sub>](https://github.com/cepacs)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/37537300?v=4" width="110px;"/><br /><sub>lea-mink</sub>](https://github.com/lea-mink)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [<img src="https://avatars0.githubusercontent.com/u/7140719?v=4" width="110px;"/><br /><sub>Hannah Tinkler</sub>](https://github.com/hannahtinkler)<br />[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/1086388?v=4" width="110px;"/><br /><sub>Doeke Zanstra</sub>](https://github.com/doekman)<br />[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [<img src="https://avatars1.githubusercontent.com/u/4325936?v=4" width="110px;"/><br /><sub>Djamon Staal</sub>](https://www.sdhd.nl/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [<img src="https://avatars3.githubusercontent.com/u/12306859?v=4" width="110px;"/><br /><sub>Earl Ramirez</sub>](https://github.com/EarlRamirez)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [<img src="https://avatars2.githubusercontent.com/u/8671456?v=4" width="110px;"/><br /><sub>Richard Ray Thomas</sub>](https://github.com/RichardRay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [<img src="https://avatars3.githubusercontent.com/u/1852688?v=4" width="110px;"/><br /><sub>Ryan Kuba</sub>](https://www.taisun.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [<img src="https://avatars1.githubusercontent.com/u/6751928?v=4" width="110px;"/><br /><sub>Brian Monroe</sub>](https://github.com/ParadoxGuitarist)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [<img src="https://avatars1.githubusercontent.com/u/605167?v=4" width="110px;"/><br /><sub>plexorama</sub>](https://github.com/plexorama)<br />[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/1795149?v=4" width="110px;"/><br /><sub>Till Deeke</sub>](https://tilldeeke.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [<img src="https://avatars0.githubusercontent.com/u/12634129?v=4" width="110px;"/><br /><sub>5quirrel</sub>](https://github.com/5quirrel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [<img src="https://avatars1.githubusercontent.com/u/13071957?v=4" width="110px;"/><br /><sub>Jason</sub>](https://github.com/jasonlshelton)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [<img src="https://avatars3.githubusercontent.com/u/7128321?v=4" width="110px;"/><br /><sub>Antti</sub>](https://github.com/chemfy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [<img src="https://avatars3.githubusercontent.com/u/10080364?v=4" width="110px;"/><br /><sub>DeusMaximus</sub>](https://github.com/DeusMaximus)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [<img src="https://avatars2.githubusercontent.com/u/16384611?v=4" width="110px;"/><br /><sub>a-royal</sub>](https://github.com/A-ROYAL)<br />[🌍](#translation-A-ROYAL "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5358208?v=4" width="110px;"/><br /><sub>Alberto Aldrigo</sub>](https://github.com/albertoaldrigo)<br />[🌍](#translation-albertoaldrigo "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1412342?v=4" width="110px;"/><br /><sub>Alex Stanev</sub>](http://alex.stanev.org/blog)<br />[🌍](#translation-RealEnder "Translation") | [<img src="https://avatars0.githubusercontent.com/u/177295?v=4" width="110px;"/><br /><sub>Andreas Rehm</sub>](http://devel.itsolution2.de)<br />[🌍](#translation-sirrus "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5080535?v=4" width="110px;"/><br /><sub>Andreas Erhard</sub>](https://github.com/xelan)<br />[🌍](#translation-xelan "Translation") | [<img src="https://avatars2.githubusercontent.com/u/142350?v=4" width="110px;"/><br /><sub>Andrés Vanegas Jiménez</sub>](https://github.com/angeldeejay)<br />[🌍](#translation-angeldeejay "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3910403?v=4" width="110px;"/><br /><sub>Antonio Schiavon</sub>](https://github.com/aschiavon91)<br />[🌍](#translation-aschiavon91 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10464547?v=4" width="110px;"/><br /><sub>benunter</sub>](https://github.com/benunter)<br />[🌍](#translation-benunter "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5038647?v=4" width="110px;"/><br /><sub>Borys Żmuda</sub>](http://catweb24.pl)<br />[🌍](#translation-rudashi "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/5539359?v=4" width="110px;"/><br /><sub>chibacityblues</sub>](https://github.com/chibacityblues)<br />[🌍](#translation-chibacityblues "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1954830?v=4" width="110px;"/><br /><sub>Chien Wei Lin</sub>](https://github.com/cwlin0416)<br />[🌍](#translation-cwlin0416 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/11700533?v=4" width="110px;"/><br /><sub>Christian Schuster</sub>](https://github.com/Againstreality)<br />[🌍](#translation-Againstreality "Translation") | [<img src="https://avatars1.githubusercontent.com/u/4308704?v=4" width="110px;"/><br /><sub>Christian Stefanus</sub>](http://chriss.webhostid.com)<br />[🌍](#translation-kopi-item "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3009327?v=4" width="110px;"/><br /><sub>wxcafé</sub>](http://wxcafe.net)<br />[🌍](#translation-wxcafe "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35761525?v=4" width="110px;"/><br /><sub>dpyroc</sub>](https://github.com/dpyroc)<br />[🌍](#translation-dpyroc "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2153639?v=4" width="110px;"/><br /><sub>Daniel Friedlmaier</sub>](http://www.friedlmaier.net)<br />[🌍](#translation-da-friedl "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/2947640?v=4" width="110px;"/><br /><sub>Daniel Heene</sub>](https://github.com/danielheene)<br />[🌍](#translation-danielheene "Translation") | [<img src="https://avatars3.githubusercontent.com/u/319022?v=4" width="110px;"/><br /><sub>danielcb</sub>](https://github.com/danielcb)<br />[🌍](#translation-danielcb "Translation") | [<img src="https://avatars3.githubusercontent.com/u/15846537?v=4" width="110px;"/><br /><sub>Dominik Senti</sub>](https://github.com/dominiksenti)<br />[🌍](#translation-dominiksenti "Translation") | [<img src="https://avatars0.githubusercontent.com/u/25570954?v=4" width="110px;"/><br /><sub>Eric Gautheron</sub>](http://www.konectik.com)<br />[🌍](#translation-EpixFr "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5732623?v=4" width="110px;"/><br /><sub>Erlend Pilø</sub>](https://erlpil.com)<br />[🌍](#translation-Erlpil "Translation") | [<img src="https://avatars0.githubusercontent.com/u/541832?v=4" width="110px;"/><br /><sub>Fabio Rapposelli</sub>](http://fabio.technology)<br />[🌍](#translation-frapposelli "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3605240?v=4" width="110px;"/><br /><sub>Felipe Barros</sub>](https://github.com/fgbs)<br />[🌍](#translation-fgbs "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/257745?v=4" width="110px;"/><br /><sub>Fernando Possebon</sub>](https://github.com/possebon)<br />[🌍](#translation-possebon "Translation") | [<img src="https://avatars3.githubusercontent.com/u/2540832?v=4" width="110px;"/><br /><sub>gdraque</sub>](https://github.com/gdraque)<br />[🌍](#translation-gdraque "Translation") | [<img src="https://avatars0.githubusercontent.com/u/23440381?v=4" width="110px;"/><br /><sub>Georg Wallisch</sub>](https://github.com/georgwallisch)<br />[🌍](#translation-georgwallisch "Translation") | [<img src="https://avatars1.githubusercontent.com/u/9852832?v=4" width="110px;"/><br /><sub>Gerardo Robles</sub>](https://github.com/jgroblesr85)<br />[🌍](#translation-jgroblesr85 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/11082640?v=4" width="110px;"/><br /><sub>Gluek</sub>](https://t.me/Gluek)<br />[🌍](#translation-mrgluek "Translation") | [<img src="https://avatars0.githubusercontent.com/u/6847946?v=4" width="110px;"/><br /><sub>AdnanAbuShahad</sub>](https://github.com/AdnanAbuShahad)<br />[🌍](#translation-AdnanAbuShahad "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3580608?v=4" width="110px;"/><br /><sub>Hafidzi My</sub>](https://hafidzi.my)<br />[🌍](#translation-hafidzi "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/205521?v=4" width="110px;"/><br /><sub>Harim Park</sub>](https://github.com/fofwisdom)<br />[🌍](#translation-fofwisdom "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3333841?v=4" width="110px;"/><br /><sub>Henrik Kentsson</sub>](http://www.kentsson.se)<br />[🌍](#translation-Kentsson "Translation") | [<img src="https://avatars0.githubusercontent.com/u/36551034?v=4" width="110px;"/><br /><sub>Husnul Yaqien</sub>](https://github.com/husnulyaqien)<br />[🌍](#translation-husnulyaqien "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2372747?v=4" width="110px;"/><br /><sub>Ibrahim</sub>](http://abaalkhail.org)<br />[🌍](#translation-abaalkh "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1389334?v=4" width="110px;"/><br /><sub>igolman</sub>](https://github.com/igolman)<br />[🌍](#translation-igolman "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3257070?v=4" width="110px;"/><br /><sub>itangiang</sub>](https://github.com/itangiang)<br />[🌍](#translation-itangiang "Translation") | [<img src="https://avatars2.githubusercontent.com/u/14814254?v=4" width="110px;"/><br /><sub>jarby1211</sub>](https://github.com/jarby1211)<br />[🌍](#translation-jarby1211 "Translation") |
| [<img src="https://avatars3.githubusercontent.com/u/6719357?v=4" width="110px;"/><br /><sub>Jhonn Willker</sub>](http://jwillker.com)<br />[🌍](#translation-JohnWillker "Translation") | [<img src="https://avatars2.githubusercontent.com/u/10983635?v=4" width="110px;"/><br /><sub>Jose</sub>](https://github.com/joxelito94)<br />[🌍](#translation-joxelito94 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5206122?v=4" width="110px;"/><br /><sub>laopangzi</sub>](https://github.com/laopangzi)<br />[🌍](#translation-laopangzi "Translation") | [<img src="https://avatars2.githubusercontent.com/u/79707?v=4" width="110px;"/><br /><sub>Lars Strojny</sub>](http://usrportage.de)<br />[🌍](#translation-lstrojny "Translation") | [<img src="https://avatars0.githubusercontent.com/u/389801?v=4" width="110px;"/><br /><sub>MarcosBL</sub>](http://twitter.com/marcosbl)<br />[🌍](#translation-MarcosBL "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35664606?v=4" width="110px;"/><br /><sub>marie joy cajes</sub>](https://github.com/mariejoyacajes)<br />[🌍](#translation-mariejoyacajes "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3052816?v=4" width="110px;"/><br /><sub>Mark S. Johansen</sub>](http://www.markjohansen.dk)<br />[🌍](#translation-msjohansen "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/4930051?v=4" width="110px;"/><br /><sub>Wes Hulette</sub>](http://macfoo.wordpress.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [<img src="https://avatars0.githubusercontent.com/u/8134591?v=4" width="110px;"/><br /><sub>patrict</sub>](https://github.com/patrict)<br />[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/2611616?v=4" width="110px;"/><br /><sub>Dmitriy Minaev</sub>](https://github.com/VELIKII-DIVAN)<br />[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [<img src="https://avatars0.githubusercontent.com/u/5132245?v=4" width="110px;"/><br /><sub>liquidhorse</sub>](https://github.com/liquidhorse)<br />[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [<img src="https://avatars1.githubusercontent.com/u/183678?v=4" width="110px;"/><br /><sub>Jordi Boggiano</sub>](https://seld.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [<img src="https://avatars0.githubusercontent.com/u/653557?v=4" width="110px;"/><br /><sub>Ivan Nieto</sub>](https://github.com/inietov)<br />[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [<img src="https://avatars2.githubusercontent.com/u/6764151?v=4" width="110px;"/><br /><sub>Ben RUBSON</sub>](https://github.com/benrubson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [<img src="https://avatars2.githubusercontent.com/u/8554558?v=4" width="110px;"/><br /><sub>NMathar</sub>](https://github.com/NMathar)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [<img src="https://avatars1.githubusercontent.com/u/139566?v=4" width="110px;"/><br /><sub>Steffen</sub>](https://github.com/smb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/6609453?v=4" width="110px;"/><br /><sub>Sxderp</sub>](https://github.com/Sxderp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [<img src="https://avatars1.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>fanta8897</sub>](https://github.com/fanta8897)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [<img src="https://avatars2.githubusercontent.com/u/2576509?v=4" width="110px;"/><br /><sub>Andrey Bolonin</sub>](https://andreybolonin.com/phpconsulting/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [<img src="https://avatars3.githubusercontent.com/u/2173307?v=4" width="110px;"/><br /><sub>shinayoshi</sub>](http://www.shinayoshi.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [<img src="https://avatars3.githubusercontent.com/u/2130159?v=4" width="110px;"/><br /><sub>Hubert</sub>](https://github.com/reuser)<br />[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [<img src="https://avatars0.githubusercontent.com/u/6865789?v=4" width="110px;"/><br /><sub>KeenRivals</sub>](https://brashear.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [<img src="https://avatars3.githubusercontent.com/u/2902513?v=4" width="110px;"/><br /><sub>omyno</sub>](https://github.com/omyno)<br />[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/6271335?v=4" width="110px;"/><br /><sub>Evgeny</sub>](https://github.com/jackka)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [<img src="https://avatars2.githubusercontent.com/u/1169963?v=4" width="110px;"/><br /><sub>Colin Campbell</sub>](https://digitalist.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [<img src="https://avatars3.githubusercontent.com/u/2872098?v=4" width="110px;"/><br /><sub>Ľubomír Kučera</sub>](https://github.com/lubo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [<img src="https://avatars3.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://www.sourceguru.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars1.githubusercontent.com/u/63399474?v=4" width="110px;"/><br /><sub>johnson-yi</sub>](https://github.com/johnson-yi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [<img src="https://avatars1.githubusercontent.com/u/1862720?v=4" width="110px;"/><br /><sub>Sanjay Govind</sub>](https://tangentmc.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/1255375?v=4" width="110px;"/><br /><sub>Peter Upfold</sub>](https://peter.upfold.org.uk/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [<img src="https://avatars2.githubusercontent.com/u/961717?v=4" width="110px;"/><br /><sub>Jared Biel</sub>](https://github.com/jbiel)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [<img src="https://avatars1.githubusercontent.com/u/1733625?v=4" width="110px;"/><br /><sub>Dampfklon</sub>](https://github.com/dampfklon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [<img src="https://avatars2.githubusercontent.com/u/52973156?v=4" width="110px;"/><br /><sub>Charles Hamilton</sub>](https://communityclosing.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [<img src="https://avatars.githubusercontent.com/u/551789?v=4" width="110px;"/><br /><sub>Giuseppe Iannello</sub>](https://github.com/giannello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [<img src="https://avatars.githubusercontent.com/u/3691490?v=4" width="110px;"/><br /><sub>Peter Dave Hello</sub>](https://www.peterdavehello.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [<img src="https://avatars.githubusercontent.com/u/6106332?v=4" width="110px;"/><br /><sub>sigmoidal</sub>](https://github.com/sigmoidal)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") |
| [<img src="https://avatars.githubusercontent.com/u/2082554?v=4" width="110px;"/><br /><sub>Vincent Lainé</sub>](https://github.com/phenixdotnet)<br />[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [<img src="https://avatars.githubusercontent.com/u/1943040?v=4" width="110px;"/><br /><sub>Lucas Pleß</sub>](http://www.lucas-pless.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [<img src="https://avatars.githubusercontent.com/u/472804?v=4" width="110px;"/><br /><sub>Ian Littman</sub>](http://twitter.com/iansltx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [<img src="https://avatars.githubusercontent.com/u/3519029?v=4" width="110px;"/><br /><sub>João Paulo</sub>](https://github.com/PauloLuna)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [<img src="https://avatars.githubusercontent.com/u/70443365?v=4" width="110px;"/><br /><sub>ThoBur</sub>](https://github.com/ThoBur)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [<img src="https://avatars.githubusercontent.com/u/1972329?v=4" width="110px;"/><br /><sub>Alexander Chibrikin</sub>](http://phpprofi.ru/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [<img src="https://avatars.githubusercontent.com/u/438332?v=4" width="110px;"/><br /><sub>Anthony Winstanley</sub>](https://github.com/winstan)<br />[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") |
| [<img src="https://avatars.githubusercontent.com/u/3075214?v=4" width="110px;"/><br /><sub>Folke</sub>](https://github.com/fashberg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [<img src="https://avatars.githubusercontent.com/u/1351571?v=4" width="110px;"/><br /><sub>Bennett Blodinger</sub>](https://github.com/benwa)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [<img src="https://avatars.githubusercontent.com/u/2974631?v=4" width="110px;"/><br /><sub>NMC</sub>](https://nmc.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [<img src="https://avatars.githubusercontent.com/u/52182449?v=4" width="110px;"/><br /><sub>andres-baller</sub>](https://github.com/andres-baller)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [<img src="https://avatars.githubusercontent.com/u/67109348?v=4" width="110px;"/><br /><sub>sean-borg</sub>](https://github.com/sean-borg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [<img src="https://avatars.githubusercontent.com/u/32170051?v=4" width="110px;"/><br /><sub>EDVLeer</sub>](https://github.com/EDVLeer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [<img src="https://avatars.githubusercontent.com/u/23075196?v=4" width="110px;"/><br /><sub>Kurokat</sub>](https://github.com/Kurokat)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") |
| [<img src="https://avatars.githubusercontent.com/u/915514?v=4" width="110px;"/><br /><sub>Kevin Köllmann</sub>](https://www.kevinkoellmann.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [<img src="https://avatars.githubusercontent.com/u/49025941?v=4" width="110px;"/><br /><sub>sw-mreyes</sub>](https://github.com/sw-mreyes)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [<img src="https://avatars.githubusercontent.com/u/70129?v=4" width="110px;"/><br /><sub>Joel Pittet</sub>](https://pittet.ca)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [<img src="https://avatars.githubusercontent.com/u/792695?v=4" width="110px;"/><br /><sub>Eli Young</sub>](https://elyscape.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [<img src="https://avatars.githubusercontent.com/u/317015?v=4" width="110px;"/><br /><sub>Raell Dottin</sub>](https://github.com/raelldottin)<br />[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [<img src="https://avatars.githubusercontent.com/u/1446856?v=4" width="110px;"/><br /><sub>Tom Misilo</sub>](https://github.com/misilot)<br />[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [<img src="https://avatars.githubusercontent.com/u/4496300?v=4" width="110px;"/><br /><sub>David Davenne</sub>](http://david.davenne.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") |
| [<img src="https://avatars.githubusercontent.com/u/9255772?v=4" width="110px;"/><br /><sub>Mark Stenglein</sub>](https://markstenglein.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [<img src="https://avatars.githubusercontent.com/u/35658596?v=4" width="110px;"/><br /><sub>ajsy</sub>](https://github.com/ajsy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [<img src="https://avatars.githubusercontent.com/u/3628035?v=4" width="110px;"/><br /><sub>Jan Kiesewetter</sub>](https://github.com/t3easy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [<img src="https://avatars.githubusercontent.com/u/79449630?v=4" width="110px;"/><br /><sub>Tetrachloromethane250</sub>](https://github.com/Tetrachloromethane250)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [<img src="https://avatars.githubusercontent.com/u/22004482?v=4" width="110px;"/><br /><sub>Lars Kajes</sub>](https://www.kajes.se/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [<img src="https://avatars.githubusercontent.com/u/13993216?v=4" width="110px;"/><br /><sub>Joly0</sub>](https://github.com/Joly0)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>theburger</sub>](https://github.com/limeless)<br />[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") |
| [<img src="https://avatars.githubusercontent.com/u/36065681?v=4" width="110px;"/><br /><sub>David Valin Alonso</sub>](https://github.com/deivishome)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [<img src="https://avatars.githubusercontent.com/u/8290389?v=4" width="110px;"/><br /><sub>andreaci</sub>](https://github.com/andreaci)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [<img src="https://avatars.githubusercontent.com/u/1828542?v=4" width="110px;"/><br /><sub>Jelle Sebreghts</sub>](http://www.jellesebreghts.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [<img src="https://avatars.githubusercontent.com/u/11180862?v=4" width="110px;"/><br /><sub>Michael Pietsch</sub>](https://github.com/Skywalker-11)<br /> | [<img src="https://avatars.githubusercontent.com/u/22068886?v=4" width="110px;"/><br /><sub>Masudul Haque Shihab</sub>](https://github.com/sh1hab)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [<img src="https://avatars.githubusercontent.com/u/16099942?v=4" width="110px;"/><br /><sub>Supapong Areeprasertkul</sub>](http://www.freedomdive.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [<img src="https://avatars.githubusercontent.com/u/207358?v=4" width="110px;"/><br /><sub>Peter Sarossy</sub>](https://github.com/psarossy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") |
| [<img src="https://avatars.githubusercontent.com/u/11823649?v=4" width="110px;"/><br /><sub>Renee Margaret McConahy</sub>](https://github.com/nepella)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [<img src="https://avatars.githubusercontent.com/u/5553884?v=4" width="110px;"/><br /><sub>JohnnyPicnic</sub>](https://github.com/JohnnyPicnic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [<img src="https://avatars.githubusercontent.com/u/8799594?v=4" width="110px;"/><br /><sub>markbrule</sub>](https://github.com/markbrule)<br />[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [<img src="https://avatars.githubusercontent.com/u/1962801?v=4" width="110px;"/><br /><sub>Mike Campbell</sub>](https://github.com/mikecmpbll)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [<img src="https://avatars.githubusercontent.com/u/11973217?v=4" width="110px;"/><br /><sub>tbrconnect</sub>](https://github.com/tbrconnect)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [<img src="https://avatars.githubusercontent.com/u/12447225?v=4" width="110px;"/><br /><sub>kcoyo</sub>](https://github.com/kcoyo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [<img src="https://avatars.githubusercontent.com/u/494017?v=4" width="110px;"/><br /><sub>Travis Miller</sub>](https://travismiller.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1975640?v=4" width="110px;"/><br /><sub>Evan Taylor</sub>](https://github.com/Delta5)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") |
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") |
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") |
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") |
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") |
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/197404?v=3" width="110px;"/><br /><sub>snipe</sub>](http://www.snipe.net)<br />[💻](https://github.com/grokability/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/grokability/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/grokability/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/grokability/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/36335?v=3" width="110px;"/><br /><sub>Brady Wetherington</sub>](http://www.uberbrady.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/grokability/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/3803132?v=3" width="110px;"/><br /><sub>Daniel Meltzer</sub>](https://github.com/dmeltzer)<br />[💻](https://github.com/grokability/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/grokability/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/grokability/snipe-it/commits?author=dmeltzer "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/1609106?v=3" width="110px;"/><br /><sub>Michael T</sub>](http://www.tuckertechonline.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mtucker6784 "Code") | [<img src="https://avatars2.githubusercontent.com/u/3274937?v=3" width="110px;"/><br /><sub>madd15</sub>](https://github.com/madd15)<br />[📖](https://github.com/grokability/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [<img src="https://avatars2.githubusercontent.com/u/894126?v=3" width="110px;"/><br /><sub>Vincent Sposato</sub>](https://github.com/vsposato)<br />[💻](https://github.com/grokability/snipe-it/commits?author=vsposato "Code") | [<img src="https://avatars0.githubusercontent.com/u/1639757?v=3" width="110px;"/><br /><sub>Andrea Bergamasco</sub>](https://github.com/vjandrea)<br />[💻](https://github.com/grokability/snipe-it/commits?author=vjandrea "Code") |
| :---: | :---: | :---: |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| :---: | :---: |
| [<img src="https://avatars0.githubusercontent.com/u/10640152?v=3" width="110px;"/><br /><sub>Karol</sub>](https://github.com/kpawelski)<br />[🌍](#translation-kpawelski "Translation") [💻](https://github.com/grokability/snipe-it/commits?author=kpawelski "Code") | [<img src="https://avatars3.githubusercontent.com/u/600106?v=3" width="110px;"/><br /><sub>morph027</sub>](http://blog.morph027.de/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=morph027 "Code") | [<img src="https://avatars3.githubusercontent.com/u/22935755?v=3" width="110px;"/><br /><sub>fvleminckx</sub>](https://github.com/fvleminckx)<br />[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars2.githubusercontent.com/u/15633547?v=3" width="110px;"/><br /><sub>itsupportcmsukorg</sub>](https://github.com/itsupportcmsukorg)<br />[💻](https://github.com/grokability/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/grokability/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [<img src="https://avatars3.githubusercontent.com/u/12373799?v=3" width="110px;"/><br /><sub>Frank</sub>](https://override.io)<br />[💻](https://github.com/grokability/snipe-it/commits?author=base-zero "Code") | [<img src="https://avatars0.githubusercontent.com/u/10137?v=3" width="110px;"/><br /><sub>Deleted user</sub>](https://github.com/ghost)<br />[🌍](#translation-ghost "Translation") [💻](https://github.com/grokability/snipe-it/commits?author=ghost "Code") | [<img src="https://avatars1.githubusercontent.com/u/10802313?v=3" width="110px;"/><br /><sub>tiagom62</sub>](https://github.com/tiagom62)<br />[💻](https://github.com/grokability/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") |
| [<img src="https://avatars3.githubusercontent.com/u/2389047?v=3" width="110px;"/><br /><sub>Ryan Stafford</sub>](https://github.com/rystaf)<br />[💻](https://github.com/grokability/snipe-it/commits?author=rystaf "Code") | [<img src="https://avatars2.githubusercontent.com/u/10345935?v=3" width="110px;"/><br /><sub>Eammon Hanlon</sub>](https://github.com/ehanlon)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ehanlon "Code") | [<img src="https://avatars0.githubusercontent.com/u/441924?v=3" width="110px;"/><br /><sub>zjean</sub>](https://github.com/zjean)<br />[💻](https://github.com/grokability/snipe-it/commits?author=zjean "Code") | [<img src="https://avatars0.githubusercontent.com/u/12660103?v=3" width="110px;"/><br /><sub>Matthias Frei</sub>](http://www.frei.media)<br />[💻](https://github.com/grokability/snipe-it/commits?author=FREImedia "Code") | [<img src="https://avatars0.githubusercontent.com/u/3767518?v=3" width="110px;"/><br /><sub>opsydev</sub>](https://github.com/opsydev)<br />[💻](https://github.com/grokability/snipe-it/commits?author=opsydev "Code") | [<img src="https://avatars1.githubusercontent.com/u/82290?v=3" width="110px;"/><br /><sub>Daniel Dreier</sub>](http://www.ddreier.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ddreier "Code") | [<img src="https://avatars0.githubusercontent.com/u/23448?v=3" width="110px;"/><br /><sub>Nikolai Prokoschenko</sub>](http://rassie.org)<br />[💻](https://github.com/grokability/snipe-it/commits?author=rassie "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/13452757?v=3" width="110px;"/><br /><sub>Drew</sub>](https://github.com/YetAnotherCodeMonkey)<br />[💻](https://github.com/grokability/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [<img src="https://avatars0.githubusercontent.com/u/1342320?v=3" width="110px;"/><br /><sub>Walter</sub>](https://github.com/merid14)<br />[💻](https://github.com/grokability/snipe-it/commits?author=merid14 "Code") | [<img src="https://avatars3.githubusercontent.com/u/11254614?v=3" width="110px;"/><br /><sub>Petr Baloun</sub>](https://github.com/balous)<br />[💻](https://github.com/grokability/snipe-it/commits?author=balous "Code") | [<img src="https://avatars0.githubusercontent.com/u/6117660?v=3" width="110px;"/><br /><sub>reidblomquist</sub>](https://github.com/reidblomquist)<br />[📖](https://github.com/grokability/snipe-it/commits?author=reidblomquist "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/539914?v=3" width="110px;"/><br /><sub>Mathieu Kooiman</sub>](https://github.com/mathieuk)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mathieuk "Code") | [<img src="https://avatars3.githubusercontent.com/u/6606421?v=3" width="110px;"/><br /><sub>csayre</sub>](https://github.com/csayre)<br />[📖](https://github.com/grokability/snipe-it/commits?author=csayre "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/768488?v=3" width="110px;"/><br /><sub>Adam Dunson</sub>](https://github.com/adamdunson)<br />[💻](https://github.com/grokability/snipe-it/commits?author=adamdunson "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/5547470?v=3" width="110px;"/><br /><sub>Hereward</sub>](https://github.com/thehereward)<br />[💻](https://github.com/grokability/snipe-it/commits?author=thehereward "Code") | [<img src="https://avatars0.githubusercontent.com/u/5802977?v=3" width="110px;"/><br /><sub>swoopdk</sub>](https://github.com/swoopdk)<br />[💻](https://github.com/grokability/snipe-it/commits?author=swoopdk "Code") | [<img src="https://avatars1.githubusercontent.com/u/3470403?v=3" width="110px;"/><br /><sub>Abdullah Alansari</sub>](https://linkedin.com/in/ahimta)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Ahimta "Code") | [<img src="https://avatars0.githubusercontent.com/u/796443?v=3" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[💻](https://github.com/grokability/snipe-it/commits?author=MicaelRodrigues "Code") | [<img src="https://avatars0.githubusercontent.com/u/614564?v=3" width="110px;"/><br /><sub>Patrick Gallagher</sub>](http://macadmincorner.com)<br />[📖](https://github.com/grokability/snipe-it/commits?author=patgmac "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/7165922?v=3" width="110px;"/><br /><sub>Miliamber</sub>](https://github.com/Miliamber)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Miliamber "Code") | [<img src="https://avatars3.githubusercontent.com/u/861766?v=3" width="110px;"/><br /><sub>hawk554</sub>](https://github.com/hawk554)<br />[💻](https://github.com/grokability/snipe-it/commits?author=hawk554 "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/1695622?v=3" width="110px;"/><br /><sub>Justin Kerr</sub>](http://jbirdkerr.net)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jbirdkerr "Code") | [<img src="https://avatars3.githubusercontent.com/u/11426176?v=3" width="110px;"/><br /><sub>Ira W. Snyder</sub>](http://www.irasnyder.com/devel/)<br />[📖](https://github.com/grokability/snipe-it/commits?author=irasnyd "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/2475759?v=3" width="110px;"/><br /><sub>Aladin Alaily</sub>](https://github.com/aalaily)<br />[💻](https://github.com/grokability/snipe-it/commits?author=aalaily "Code") | [<img src="https://avatars0.githubusercontent.com/u/10247644?v=3" width="110px;"/><br /><sub>Chase Hansen</sub>](https://github.com/kobie-chasehansen)<br />[💻](https://github.com/grokability/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/grokability/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [<img src="https://avatars2.githubusercontent.com/u/13545400?v=3" width="110px;"/><br /><sub>IDM Helpdesk</sub>](https://github.com/IDM-Helpdesk)<br />[💻](https://github.com/grokability/snipe-it/commits?author=IDM-Helpdesk "Code") | [<img src="https://avatars2.githubusercontent.com/u/614439?v=3" width="110px;"/><br /><sub>Kai</sub>](http://balticer.de)<br />[💻](https://github.com/grokability/snipe-it/commits?author=balticer "Code") | [<img src="https://avatars1.githubusercontent.com/u/8762511?v=3" width="110px;"/><br /><sub>Michael Daniels</sub>](http://www.michaeldaniels.me)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mdaniels5757 "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/1532660?v=3" width="110px;"/><br /><sub>Tom Castleman</sub>](http://tomcastleman.me)<br />[💻](https://github.com/grokability/snipe-it/commits?author=tomcastleman "Code") | [<img src="https://avatars3.githubusercontent.com/u/10723243?v=3" width="110px;"/><br /><sub>Daniel Nemanic</sub>](https://github.com/DanielNemanic)<br />[💻](https://github.com/grokability/snipe-it/commits?author=DanielNemanic "Code") | [<img src="https://avatars0.githubusercontent.com/u/150648?v=3" width="110px;"/><br /><sub>SouthWolf</sub>](https://github.com/southwolf)<br />[💻](https://github.com/grokability/snipe-it/commits?author=southwolf "Code") | [<img src="https://avatars2.githubusercontent.com/u/131616?v=3" width="110px;"/><br /><sub>Ivar Nesje</sub>](https://github.com/ivarne)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ivarne "Code") | [<img src="https://avatars1.githubusercontent.com/u/62333?v=3" width="110px;"/><br /><sub>Jérémy Benoist</sub>](http://www.j0k3r.net)<br />[📖](https://github.com/grokability/snipe-it/commits?author=j0k3r "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/724344?v=3" width="110px;"/><br /><sub>Chris Leathley</sub>](https://github.com/cleathley)<br />[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars0.githubusercontent.com/u/972498?v=3" width="110px;"/><br /><sub>splaer</sub>](https://github.com/splaer)<br />[🐛](https://github.com/grokability/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/grokability/snipe-it/commits?author=splaer "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/967362?v=3" width="110px;"/><br /><sub>Joe Ferguson</sub>](http://www.joeferguson.me)<br />[💻](https://github.com/grokability/snipe-it/commits?author=svpernova09 "Code") | [<img src="https://avatars3.githubusercontent.com/u/6108682?v=3" width="110px;"/><br /><sub>diwanicki</sub>](https://github.com/diwanicki)<br />[💻](https://github.com/grokability/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/grokability/snipe-it/commits?author=diwanicki "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/2527115?v=3" width="110px;"/><br /><sub>Lee Thoong Ching</sub>](https://github.com/pakkua80)<br />[📖](https://github.com/grokability/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/grokability/snipe-it/commits?author=pakkua80 "Code") | [<img src="https://avatars1.githubusercontent.com/u/461491?v=3" width="110px;"/><br /><sub>Marek Šuppa</sub>](http://shu.io)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mrshu "Code") | [<img src="https://avatars1.githubusercontent.com/u/8693762?v=3" width="110px;"/><br /><sub>Juan J. Martinez</sub>](https://github.com/mizar1616)<br />[🌍](#translation-mizar1616 "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1458388?v=3" width="110px;"/><br /><sub>R Ryan Dial</sub>](https://github.com/rrdial)<br />[🌍](#translation-rrdial "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2871745?v=3" width="110px;"/><br /><sub>Andrej Manduch</sub>](https://github.com/burlito)<br />[📖](https://github.com/grokability/snipe-it/commits?author=burlito "Documentation") |
| [<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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/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/grokability/snipe-it/commits?author=imanghafoori1 "Code") | [<img src="https://avatars1.githubusercontent.com/u/6551003?v=4" width="110px;"/><br /><sub>Richard Hofman</sub>](https://github.com/richardhofman6)<br />[💻](https://github.com/grokability/snipe-it/commits?author=richardhofman6 "Code") | [<img src="https://avatars0.githubusercontent.com/u/3697569?v=4" width="110px;"/><br /><sub>gizzmojr</sub>](https://github.com/gizzmojr)<br />[💻](https://github.com/grokability/snipe-it/commits?author=gizzmojr "Code") | [<img src="https://avatars3.githubusercontent.com/u/404729?v=4" width="110px;"/><br /><sub>Jenny Li</sub>](https://github.com/imjennyli)<br />[📖](https://github.com/grokability/snipe-it/commits?author=imjennyli "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/869227?v=4" width="110px;"/><br /><sub>Geoff Young</sub>](https://github.com/GeoffYoung)<br />[💻](https://github.com/grokability/snipe-it/commits?author=GeoffYoung "Code") | [<img src="https://avatars3.githubusercontent.com/u/1068477?v=4" width="110px;"/><br /><sub>Elliot Blackburn</sub>](http://www.elliotblackburn.com)<br />[📖](https://github.com/grokability/snipe-it/commits?author=BlueHatbRit "Documentation") |
| [<img src="https://avatars1.githubusercontent.com/u/6357451?v=4" width="110px;"/><br /><sub>Tõnis Ormisson</sub>](http://andmemasin.eu)<br />[💻](https://github.com/grokability/snipe-it/commits?author=TonisOrmisson "Code") | [<img src="https://avatars0.githubusercontent.com/u/449411?v=4" width="110px;"/><br /><sub>Nicolai Essig</sub>](http://www.nicolai-essig.de)<br />[💻](https://github.com/grokability/snipe-it/commits?author=thakilla "Code") | [<img src="https://avatars1.githubusercontent.com/u/14809698?v=4" width="110px;"/><br /><sub>Danielle</sub>](https://github.com/techincolor)<br />[📖](https://github.com/grokability/snipe-it/commits?author=techincolor "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/18545156?v=4" width="110px;"/><br /><sub>Lawrence</sub>](https://github.com/TheVakman)<br />[⚠️](https://github.com/grokability/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/grokability/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [<img src="https://avatars1.githubusercontent.com/u/22473767?v=4" width="110px;"/><br /><sub>uknzaeinozpas</sub>](https://github.com/uknzaeinozpas)<br />[⚠️](https://github.com/grokability/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/grokability/snipe-it/commits?author=uknzaeinozpas "Code") | [<img src="https://avatars3.githubusercontent.com/u/422752?v=4" width="110px;"/><br /><sub>Ryan</sub>](https://github.com/Gelob)<br />[📖](https://github.com/grokability/snipe-it/commits?author=Gelob "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/10672546?v=4" width="110px;"/><br /><sub>vcordes79</sub>](https://github.com/vcordes79)<br />[💻](https://github.com/grokability/snipe-it/commits?author=vcordes79 "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/27958330?v=4" width="110px;"/><br /><sub>fordster78</sub>](https://github.com/fordster78)<br />[💻](https://github.com/grokability/snipe-it/commits?author=fordster78 "Code") | [<img src="https://avatars0.githubusercontent.com/u/34064225?v=4" width="110px;"/><br /><sub>CronKz</sub>](https://github.com/CronKz)<br />[💻](https://github.com/grokability/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [<img src="https://avatars1.githubusercontent.com/u/585486?v=4" width="110px;"/><br /><sub>Tim Bishop</sub>](https://github.com/tdb)<br />[💻](https://github.com/grokability/snipe-it/commits?author=tdb "Code") | [<img src="https://avatars2.githubusercontent.com/u/5384694?v=4" width="110px;"/><br /><sub>Sean McIlvenna</sub>](https://www.seanmcilvenna.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=seanmcilvenna "Code") | [<img src="https://avatars3.githubusercontent.com/u/36515590?v=4" width="110px;"/><br /><sub>cepacs</sub>](https://github.com/cepacs)<br />[🐛](https://github.com/grokability/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/grokability/snipe-it/commits?author=cepacs "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/37537300?v=4" width="110px;"/><br /><sub>lea-mink</sub>](https://github.com/lea-mink)<br />[💻](https://github.com/grokability/snipe-it/commits?author=lea-mink "Code") | [<img src="https://avatars0.githubusercontent.com/u/7140719?v=4" width="110px;"/><br /><sub>Hannah Tinkler</sub>](https://github.com/hannahtinkler)<br />[💻](https://github.com/grokability/snipe-it/commits?author=hannahtinkler "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/1086388?v=4" width="110px;"/><br /><sub>Doeke Zanstra</sub>](https://github.com/doekman)<br />[💻](https://github.com/grokability/snipe-it/commits?author=doekman "Code") | [<img src="https://avatars1.githubusercontent.com/u/4325936?v=4" width="110px;"/><br /><sub>Djamon Staal</sub>](https://www.sdhd.nl/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=SjamonDaal "Code") | [<img src="https://avatars3.githubusercontent.com/u/12306859?v=4" width="110px;"/><br /><sub>Earl Ramirez</sub>](https://github.com/EarlRamirez)<br />[💻](https://github.com/grokability/snipe-it/commits?author=EarlRamirez "Code") | [<img src="https://avatars2.githubusercontent.com/u/8671456?v=4" width="110px;"/><br /><sub>Richard Ray Thomas</sub>](https://github.com/RichardRay)<br />[💻](https://github.com/grokability/snipe-it/commits?author=RichardRay "Code") | [<img src="https://avatars3.githubusercontent.com/u/1852688?v=4" width="110px;"/><br /><sub>Ryan Kuba</sub>](https://www.taisun.io/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=thelamer "Code") | [<img src="https://avatars1.githubusercontent.com/u/6751928?v=4" width="110px;"/><br /><sub>Brian Monroe</sub>](https://github.com/ParadoxGuitarist)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ParadoxGuitarist "Code") | [<img src="https://avatars1.githubusercontent.com/u/605167?v=4" width="110px;"/><br /><sub>plexorama</sub>](https://github.com/plexorama)<br />[💻](https://github.com/grokability/snipe-it/commits?author=plexorama "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/1795149?v=4" width="110px;"/><br /><sub>Till Deeke</sub>](https://tilldeeke.de)<br />[💻](https://github.com/grokability/snipe-it/commits?author=tilldeeke "Code") | [<img src="https://avatars0.githubusercontent.com/u/12634129?v=4" width="110px;"/><br /><sub>5quirrel</sub>](https://github.com/5quirrel)<br />[💻](https://github.com/grokability/snipe-it/commits?author=5quirrel "Code") | [<img src="https://avatars1.githubusercontent.com/u/13071957?v=4" width="110px;"/><br /><sub>Jason</sub>](https://github.com/jasonlshelton)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jasonlshelton "Code") | [<img src="https://avatars3.githubusercontent.com/u/7128321?v=4" width="110px;"/><br /><sub>Antti</sub>](https://github.com/chemfy)<br />[💻](https://github.com/grokability/snipe-it/commits?author=chemfy "Code") | [<img src="https://avatars3.githubusercontent.com/u/10080364?v=4" width="110px;"/><br /><sub>DeusMaximus</sub>](https://github.com/DeusMaximus)<br />[💻](https://github.com/grokability/snipe-it/commits?author=DeusMaximus "Code") | [<img src="https://avatars2.githubusercontent.com/u/16384611?v=4" width="110px;"/><br /><sub>a-royal</sub>](https://github.com/A-ROYAL)<br />[🌍](#translation-A-ROYAL "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5358208?v=4" width="110px;"/><br /><sub>Alberto Aldrigo</sub>](https://github.com/albertoaldrigo)<br />[🌍](#translation-albertoaldrigo "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1412342?v=4" width="110px;"/><br /><sub>Alex Stanev</sub>](http://alex.stanev.org/blog)<br />[🌍](#translation-RealEnder "Translation") | [<img src="https://avatars0.githubusercontent.com/u/177295?v=4" width="110px;"/><br /><sub>Andreas Rehm</sub>](http://devel.itsolution2.de)<br />[🌍](#translation-sirrus "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5080535?v=4" width="110px;"/><br /><sub>Andreas Erhard</sub>](https://github.com/xelan)<br />[🌍](#translation-xelan "Translation") | [<img src="https://avatars2.githubusercontent.com/u/142350?v=4" width="110px;"/><br /><sub>Andrés Vanegas Jiménez</sub>](https://github.com/angeldeejay)<br />[🌍](#translation-angeldeejay "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3910403?v=4" width="110px;"/><br /><sub>Antonio Schiavon</sub>](https://github.com/aschiavon91)<br />[🌍](#translation-aschiavon91 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10464547?v=4" width="110px;"/><br /><sub>benunter</sub>](https://github.com/benunter)<br />[🌍](#translation-benunter "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5038647?v=4" width="110px;"/><br /><sub>Borys Żmuda</sub>](http://catweb24.pl)<br />[🌍](#translation-rudashi "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/5539359?v=4" width="110px;"/><br /><sub>chibacityblues</sub>](https://github.com/chibacityblues)<br />[🌍](#translation-chibacityblues "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1954830?v=4" width="110px;"/><br /><sub>Chien Wei Lin</sub>](https://github.com/cwlin0416)<br />[🌍](#translation-cwlin0416 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/11700533?v=4" width="110px;"/><br /><sub>Christian Schuster</sub>](https://github.com/Againstreality)<br />[🌍](#translation-Againstreality "Translation") | [<img src="https://avatars1.githubusercontent.com/u/4308704?v=4" width="110px;"/><br /><sub>Christian Stefanus</sub>](http://chriss.webhostid.com)<br />[🌍](#translation-kopi-item "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3009327?v=4" width="110px;"/><br /><sub>wxcafé</sub>](http://wxcafe.net)<br />[🌍](#translation-wxcafe "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35761525?v=4" width="110px;"/><br /><sub>dpyroc</sub>](https://github.com/dpyroc)<br />[🌍](#translation-dpyroc "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2153639?v=4" width="110px;"/><br /><sub>Daniel Friedlmaier</sub>](http://www.friedlmaier.net)<br />[🌍](#translation-da-friedl "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/2947640?v=4" width="110px;"/><br /><sub>Daniel Heene</sub>](https://github.com/danielheene)<br />[🌍](#translation-danielheene "Translation") | [<img src="https://avatars3.githubusercontent.com/u/319022?v=4" width="110px;"/><br /><sub>danielcb</sub>](https://github.com/danielcb)<br />[🌍](#translation-danielcb "Translation") | [<img src="https://avatars3.githubusercontent.com/u/15846537?v=4" width="110px;"/><br /><sub>Dominik Senti</sub>](https://github.com/dominiksenti)<br />[🌍](#translation-dominiksenti "Translation") | [<img src="https://avatars0.githubusercontent.com/u/25570954?v=4" width="110px;"/><br /><sub>Eric Gautheron</sub>](http://www.konectik.com)<br />[🌍](#translation-EpixFr "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5732623?v=4" width="110px;"/><br /><sub>Erlend Pilø</sub>](https://erlpil.com)<br />[🌍](#translation-Erlpil "Translation") | [<img src="https://avatars0.githubusercontent.com/u/541832?v=4" width="110px;"/><br /><sub>Fabio Rapposelli</sub>](http://fabio.technology)<br />[🌍](#translation-frapposelli "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3605240?v=4" width="110px;"/><br /><sub>Felipe Barros</sub>](https://github.com/fgbs)<br />[🌍](#translation-fgbs "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/257745?v=4" width="110px;"/><br /><sub>Fernando Possebon</sub>](https://github.com/possebon)<br />[🌍](#translation-possebon "Translation") | [<img src="https://avatars3.githubusercontent.com/u/2540832?v=4" width="110px;"/><br /><sub>gdraque</sub>](https://github.com/gdraque)<br />[🌍](#translation-gdraque "Translation") | [<img src="https://avatars0.githubusercontent.com/u/23440381?v=4" width="110px;"/><br /><sub>Georg Wallisch</sub>](https://github.com/georgwallisch)<br />[🌍](#translation-georgwallisch "Translation") | [<img src="https://avatars1.githubusercontent.com/u/9852832?v=4" width="110px;"/><br /><sub>Gerardo Robles</sub>](https://github.com/jgroblesr85)<br />[🌍](#translation-jgroblesr85 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/11082640?v=4" width="110px;"/><br /><sub>Gluek</sub>](https://t.me/Gluek)<br />[🌍](#translation-mrgluek "Translation") | [<img src="https://avatars0.githubusercontent.com/u/6847946?v=4" width="110px;"/><br /><sub>AdnanAbuShahad</sub>](https://github.com/AdnanAbuShahad)<br />[🌍](#translation-AdnanAbuShahad "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3580608?v=4" width="110px;"/><br /><sub>Hafidzi My</sub>](https://hafidzi.my)<br />[🌍](#translation-hafidzi "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/205521?v=4" width="110px;"/><br /><sub>Harim Park</sub>](https://github.com/fofwisdom)<br />[🌍](#translation-fofwisdom "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3333841?v=4" width="110px;"/><br /><sub>Henrik Kentsson</sub>](http://www.kentsson.se)<br />[🌍](#translation-Kentsson "Translation") | [<img src="https://avatars0.githubusercontent.com/u/36551034?v=4" width="110px;"/><br /><sub>Husnul Yaqien</sub>](https://github.com/husnulyaqien)<br />[🌍](#translation-husnulyaqien "Translation") | [<img src="https://avatars1.githubusercontent.com/u/2372747?v=4" width="110px;"/><br /><sub>Ibrahim</sub>](http://abaalkhail.org)<br />[🌍](#translation-abaalkh "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1389334?v=4" width="110px;"/><br /><sub>igolman</sub>](https://github.com/igolman)<br />[🌍](#translation-igolman "Translation") | [<img src="https://avatars1.githubusercontent.com/u/3257070?v=4" width="110px;"/><br /><sub>itangiang</sub>](https://github.com/itangiang)<br />[🌍](#translation-itangiang "Translation") | [<img src="https://avatars2.githubusercontent.com/u/14814254?v=4" width="110px;"/><br /><sub>jarby1211</sub>](https://github.com/jarby1211)<br />[🌍](#translation-jarby1211 "Translation") |
| [<img src="https://avatars3.githubusercontent.com/u/6719357?v=4" width="110px;"/><br /><sub>Jhonn Willker</sub>](http://jwillker.com)<br />[🌍](#translation-JohnWillker "Translation") | [<img src="https://avatars2.githubusercontent.com/u/10983635?v=4" width="110px;"/><br /><sub>Jose</sub>](https://github.com/joxelito94)<br />[🌍](#translation-joxelito94 "Translation") | [<img src="https://avatars0.githubusercontent.com/u/5206122?v=4" width="110px;"/><br /><sub>laopangzi</sub>](https://github.com/laopangzi)<br />[🌍](#translation-laopangzi "Translation") | [<img src="https://avatars2.githubusercontent.com/u/79707?v=4" width="110px;"/><br /><sub>Lars Strojny</sub>](http://usrportage.de)<br />[🌍](#translation-lstrojny "Translation") | [<img src="https://avatars0.githubusercontent.com/u/389801?v=4" width="110px;"/><br /><sub>MarcosBL</sub>](http://twitter.com/marcosbl)<br />[🌍](#translation-MarcosBL "Translation") | [<img src="https://avatars3.githubusercontent.com/u/35664606?v=4" width="110px;"/><br /><sub>marie joy cajes</sub>](https://github.com/mariejoyacajes)<br />[🌍](#translation-mariejoyacajes "Translation") | [<img src="https://avatars2.githubusercontent.com/u/3052816?v=4" width="110px;"/><br /><sub>Mark S. Johansen</sub>](http://www.markjohansen.dk)<br />[🌍](#translation-msjohansen "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/grokability/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/4930051?v=4" width="110px;"/><br /><sub>Wes Hulette</sub>](http://macfoo.wordpress.com/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jwhulette "Code") | [<img src="https://avatars0.githubusercontent.com/u/8134591?v=4" width="110px;"/><br /><sub>patrict</sub>](https://github.com/patrict)<br />[💻](https://github.com/grokability/snipe-it/commits?author=patrict "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/2611616?v=4" width="110px;"/><br /><sub>Dmitriy Minaev</sub>](https://github.com/VELIKII-DIVAN)<br />[💻](https://github.com/grokability/snipe-it/commits?author=VELIKII-DIVAN "Code") | [<img src="https://avatars0.githubusercontent.com/u/5132245?v=4" width="110px;"/><br /><sub>liquidhorse</sub>](https://github.com/liquidhorse)<br />[💻](https://github.com/grokability/snipe-it/commits?author=liquidhorse "Code") | [<img src="https://avatars1.githubusercontent.com/u/183678?v=4" width="110px;"/><br /><sub>Jordi Boggiano</sub>](https://seld.be/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Seldaek "Code") | [<img src="https://avatars0.githubusercontent.com/u/653557?v=4" width="110px;"/><br /><sub>Ivan Nieto</sub>](https://github.com/inietov)<br />[💻](https://github.com/grokability/snipe-it/commits?author=inietov "Code") | [<img src="https://avatars2.githubusercontent.com/u/6764151?v=4" width="110px;"/><br /><sub>Ben RUBSON</sub>](https://github.com/benrubson)<br />[💻](https://github.com/grokability/snipe-it/commits?author=benrubson "Code") | [<img src="https://avatars2.githubusercontent.com/u/8554558?v=4" width="110px;"/><br /><sub>NMathar</sub>](https://github.com/NMathar)<br />[💻](https://github.com/grokability/snipe-it/commits?author=NMathar "Code") | [<img src="https://avatars1.githubusercontent.com/u/139566?v=4" width="110px;"/><br /><sub>Steffen</sub>](https://github.com/smb)<br />[💻](https://github.com/grokability/snipe-it/commits?author=smb "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/6609453?v=4" width="110px;"/><br /><sub>Sxderp</sub>](https://github.com/Sxderp)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Sxderp "Code") | [<img src="https://avatars1.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>fanta8897</sub>](https://github.com/fanta8897)<br />[💻](https://github.com/grokability/snipe-it/commits?author=fanta8897 "Code") | [<img src="https://avatars2.githubusercontent.com/u/2576509?v=4" width="110px;"/><br /><sub>Andrey Bolonin</sub>](https://andreybolonin.com/phpconsulting/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=andreybolonin "Code") | [<img src="https://avatars3.githubusercontent.com/u/2173307?v=4" width="110px;"/><br /><sub>shinayoshi</sub>](http://www.shinayoshi.net/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=shinayoshi "Code") | [<img src="https://avatars3.githubusercontent.com/u/2130159?v=4" width="110px;"/><br /><sub>Hubert</sub>](https://github.com/reuser)<br />[💻](https://github.com/grokability/snipe-it/commits?author=reuser "Code") | [<img src="https://avatars0.githubusercontent.com/u/6865789?v=4" width="110px;"/><br /><sub>KeenRivals</sub>](https://brashear.me)<br />[💻](https://github.com/grokability/snipe-it/commits?author=KeenRivals "Code") | [<img src="https://avatars3.githubusercontent.com/u/2902513?v=4" width="110px;"/><br /><sub>omyno</sub>](https://github.com/omyno)<br />[💻](https://github.com/grokability/snipe-it/commits?author=omyno "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/6271335?v=4" width="110px;"/><br /><sub>Evgeny</sub>](https://github.com/jackka)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jackka "Code") | [<img src="https://avatars2.githubusercontent.com/u/1169963?v=4" width="110px;"/><br /><sub>Colin Campbell</sub>](https://digitalist.se)<br />[💻](https://github.com/grokability/snipe-it/commits?author=colin-campbell "Code") | [<img src="https://avatars3.githubusercontent.com/u/2872098?v=4" width="110px;"/><br /><sub>Ľubomír Kučera</sub>](https://github.com/lubo)<br />[💻](https://github.com/grokability/snipe-it/commits?author=lubo "Code") | [<img src="https://avatars3.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://www.sourceguru.net)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Mezzle "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/grokability/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mskrip "Code") | [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Godmartinz "Code") |
| [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/grokability/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/grokability/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/grokability/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/grokability/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars1.githubusercontent.com/u/63399474?v=4" width="110px;"/><br /><sub>johnson-yi</sub>](https://github.com/johnson-yi)<br />[💻](https://github.com/grokability/snipe-it/commits?author=johnson-yi "Code") | [<img src="https://avatars1.githubusercontent.com/u/1862720?v=4" width="110px;"/><br /><sub>Sanjay Govind</sub>](https://tangentmc.net)<br />[💻](https://github.com/grokability/snipe-it/commits?author=sanjay900 "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/1255375?v=4" width="110px;"/><br /><sub>Peter Upfold</sub>](https://peter.upfold.org.uk/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=PeterUpfold "Code") | [<img src="https://avatars2.githubusercontent.com/u/961717?v=4" width="110px;"/><br /><sub>Jared Biel</sub>](https://github.com/jbiel)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jbiel "Code") | [<img src="https://avatars1.githubusercontent.com/u/1733625?v=4" width="110px;"/><br /><sub>Dampfklon</sub>](https://github.com/dampfklon)<br />[💻](https://github.com/grokability/snipe-it/commits?author=dampfklon "Code") | [<img src="https://avatars2.githubusercontent.com/u/52973156?v=4" width="110px;"/><br /><sub>Charles Hamilton</sub>](https://communityclosing.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=chamilton-ccn "Code") | [<img src="https://avatars.githubusercontent.com/u/551789?v=4" width="110px;"/><br /><sub>Giuseppe Iannello</sub>](https://github.com/giannello)<br />[💻](https://github.com/grokability/snipe-it/commits?author=giannello "Code") | [<img src="https://avatars.githubusercontent.com/u/3691490?v=4" width="110px;"/><br /><sub>Peter Dave Hello</sub>](https://www.peterdavehello.org/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=PeterDaveHello "Code") | [<img src="https://avatars.githubusercontent.com/u/6106332?v=4" width="110px;"/><br /><sub>sigmoidal</sub>](https://github.com/sigmoidal)<br />[💻](https://github.com/grokability/snipe-it/commits?author=sigmoidal "Code") |
| [<img src="https://avatars.githubusercontent.com/u/2082554?v=4" width="110px;"/><br /><sub>Vincent Lainé</sub>](https://github.com/phenixdotnet)<br />[💻](https://github.com/grokability/snipe-it/commits?author=phenixdotnet "Code") | [<img src="https://avatars.githubusercontent.com/u/1943040?v=4" width="110px;"/><br /><sub>Lucas Pleß</sub>](http://www.lucas-pless.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=derlucas "Code") | [<img src="https://avatars.githubusercontent.com/u/472804?v=4" width="110px;"/><br /><sub>Ian Littman</sub>](http://twitter.com/iansltx)<br />[💻](https://github.com/grokability/snipe-it/commits?author=iansltx "Code") | [<img src="https://avatars.githubusercontent.com/u/3519029?v=4" width="110px;"/><br /><sub>João Paulo</sub>](https://github.com/PauloLuna)<br />[💻](https://github.com/grokability/snipe-it/commits?author=PauloLuna "Code") | [<img src="https://avatars.githubusercontent.com/u/70443365?v=4" width="110px;"/><br /><sub>ThoBur</sub>](https://github.com/ThoBur)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ThoBur "Code") | [<img src="https://avatars.githubusercontent.com/u/1972329?v=4" width="110px;"/><br /><sub>Alexander Chibrikin</sub>](http://phpprofi.ru/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=alek13 "Code") | [<img src="https://avatars.githubusercontent.com/u/438332?v=4" width="110px;"/><br /><sub>Anthony Winstanley</sub>](https://github.com/winstan)<br />[💻](https://github.com/grokability/snipe-it/commits?author=winstan "Code") |
| [<img src="https://avatars.githubusercontent.com/u/3075214?v=4" width="110px;"/><br /><sub>Folke</sub>](https://github.com/fashberg)<br />[💻](https://github.com/grokability/snipe-it/commits?author=fashberg "Code") | [<img src="https://avatars.githubusercontent.com/u/1351571?v=4" width="110px;"/><br /><sub>Bennett Blodinger</sub>](https://github.com/benwa)<br />[💻](https://github.com/grokability/snipe-it/commits?author=benwa "Code") | [<img src="https://avatars.githubusercontent.com/u/2974631?v=4" width="110px;"/><br /><sub>NMC</sub>](https://nmc.dev)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ncareau "Code") | [<img src="https://avatars.githubusercontent.com/u/52182449?v=4" width="110px;"/><br /><sub>andres-baller</sub>](https://github.com/andres-baller)<br />[💻](https://github.com/grokability/snipe-it/commits?author=andres-baller "Code") | [<img src="https://avatars.githubusercontent.com/u/67109348?v=4" width="110px;"/><br /><sub>sean-borg</sub>](https://github.com/sean-borg)<br />[💻](https://github.com/grokability/snipe-it/commits?author=sean-borg "Code") | [<img src="https://avatars.githubusercontent.com/u/32170051?v=4" width="110px;"/><br /><sub>EDVLeer</sub>](https://github.com/EDVLeer)<br />[💻](https://github.com/grokability/snipe-it/commits?author=EDVLeer "Code") | [<img src="https://avatars.githubusercontent.com/u/23075196?v=4" width="110px;"/><br /><sub>Kurokat</sub>](https://github.com/Kurokat)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Kurokat "Code") |
| [<img src="https://avatars.githubusercontent.com/u/915514?v=4" width="110px;"/><br /><sub>Kevin Köllmann</sub>](https://www.kevinkoellmann.de)<br />[💻](https://github.com/grokability/snipe-it/commits?author=koelle25 "Code") | [<img src="https://avatars.githubusercontent.com/u/49025941?v=4" width="110px;"/><br /><sub>sw-mreyes</sub>](https://github.com/sw-mreyes)<br />[💻](https://github.com/grokability/snipe-it/commits?author=sw-mreyes "Code") | [<img src="https://avatars.githubusercontent.com/u/70129?v=4" width="110px;"/><br /><sub>Joel Pittet</sub>](https://pittet.ca)<br />[💻](https://github.com/grokability/snipe-it/commits?author=joelpittet "Code") | [<img src="https://avatars.githubusercontent.com/u/792695?v=4" width="110px;"/><br /><sub>Eli Young</sub>](https://elyscape.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=elyscape "Code") | [<img src="https://avatars.githubusercontent.com/u/317015?v=4" width="110px;"/><br /><sub>Raell Dottin</sub>](https://github.com/raelldottin)<br />[💻](https://github.com/grokability/snipe-it/commits?author=raelldottin "Code") | [<img src="https://avatars.githubusercontent.com/u/1446856?v=4" width="110px;"/><br /><sub>Tom Misilo</sub>](https://github.com/misilot)<br />[💻](https://github.com/grokability/snipe-it/commits?author=misilot "Code") | [<img src="https://avatars.githubusercontent.com/u/4496300?v=4" width="110px;"/><br /><sub>David Davenne</sub>](http://david.davenne.be)<br />[💻](https://github.com/grokability/snipe-it/commits?author=JuustoMestari "Code") |
| [<img src="https://avatars.githubusercontent.com/u/9255772?v=4" width="110px;"/><br /><sub>Mark Stenglein</sub>](https://markstenglein.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ocelotsloth "Code") | [<img src="https://avatars.githubusercontent.com/u/35658596?v=4" width="110px;"/><br /><sub>ajsy</sub>](https://github.com/ajsy)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ajsy "Code") | [<img src="https://avatars.githubusercontent.com/u/3628035?v=4" width="110px;"/><br /><sub>Jan Kiesewetter</sub>](https://github.com/t3easy)<br />[💻](https://github.com/grokability/snipe-it/commits?author=t3easy "Code") | [<img src="https://avatars.githubusercontent.com/u/79449630?v=4" width="110px;"/><br /><sub>Tetrachloromethane250</sub>](https://github.com/Tetrachloromethane250)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Tetrachloromethane250 "Code") | [<img src="https://avatars.githubusercontent.com/u/22004482?v=4" width="110px;"/><br /><sub>Lars Kajes</sub>](https://www.kajes.se/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=kajes "Code") | [<img src="https://avatars.githubusercontent.com/u/13993216?v=4" width="110px;"/><br /><sub>Joly0</sub>](https://github.com/Joly0)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Joly0 "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>theburger</sub>](https://github.com/limeless)<br />[💻](https://github.com/grokability/snipe-it/commits?author=limeless "Code") |
| [<img src="https://avatars.githubusercontent.com/u/36065681?v=4" width="110px;"/><br /><sub>David Valin Alonso</sub>](https://github.com/deivishome)<br />[💻](https://github.com/grokability/snipe-it/commits?author=deivishome "Code") | [<img src="https://avatars.githubusercontent.com/u/8290389?v=4" width="110px;"/><br /><sub>andreaci</sub>](https://github.com/andreaci)<br />[💻](https://github.com/grokability/snipe-it/commits?author=andreaci "Code") | [<img src="https://avatars.githubusercontent.com/u/1828542?v=4" width="110px;"/><br /><sub>Jelle Sebreghts</sub>](http://www.jellesebreghts.be)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Jelle-S "Code") | [<img src="https://avatars.githubusercontent.com/u/11180862?v=4" width="110px;"/><br /><sub>Michael Pietsch</sub>](https://github.com/Skywalker-11)<br /> | [<img src="https://avatars.githubusercontent.com/u/22068886?v=4" width="110px;"/><br /><sub>Masudul Haque Shihab</sub>](https://github.com/sh1hab)<br />[💻](https://github.com/grokability/snipe-it/commits?author=sh1hab "Code") | [<img src="https://avatars.githubusercontent.com/u/16099942?v=4" width="110px;"/><br /><sub>Supapong Areeprasertkul</sub>](http://www.freedomdive.com/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=zybersup "Code") | [<img src="https://avatars.githubusercontent.com/u/207358?v=4" width="110px;"/><br /><sub>Peter Sarossy</sub>](https://github.com/psarossy)<br />[💻](https://github.com/grokability/snipe-it/commits?author=psarossy "Code") |
| [<img src="https://avatars.githubusercontent.com/u/11823649?v=4" width="110px;"/><br /><sub>Renee Margaret McConahy</sub>](https://github.com/nepella)<br />[💻](https://github.com/grokability/snipe-it/commits?author=nepella "Code") | [<img src="https://avatars.githubusercontent.com/u/5553884?v=4" width="110px;"/><br /><sub>JohnnyPicnic</sub>](https://github.com/JohnnyPicnic)<br />[💻](https://github.com/grokability/snipe-it/commits?author=JohnnyPicnic "Code") | [<img src="https://avatars.githubusercontent.com/u/8799594?v=4" width="110px;"/><br /><sub>markbrule</sub>](https://github.com/markbrule)<br />[💻](https://github.com/grokability/snipe-it/commits?author=markbrule "Code") | [<img src="https://avatars.githubusercontent.com/u/1962801?v=4" width="110px;"/><br /><sub>Mike Campbell</sub>](https://github.com/mikecmpbll)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mikecmpbll "Code") | [<img src="https://avatars.githubusercontent.com/u/11973217?v=4" width="110px;"/><br /><sub>tbrconnect</sub>](https://github.com/tbrconnect)<br />[💻](https://github.com/grokability/snipe-it/commits?author=tbrconnect "Code") | [<img src="https://avatars.githubusercontent.com/u/12447225?v=4" width="110px;"/><br /><sub>kcoyo</sub>](https://github.com/kcoyo)<br />[💻](https://github.com/grokability/snipe-it/commits?author=kcoyo "Code") | [<img src="https://avatars.githubusercontent.com/u/494017?v=4" width="110px;"/><br /><sub>Travis Miller</sub>](https://travismiller.com/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=travismiller "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1975640?v=4" width="110px;"/><br /><sub>Evan Taylor</sub>](https://github.com/Delta5)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Delta5 "Code") | [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/grokability/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/grokability/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/grokability/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/grokability/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jerm "Code") |
| [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Toreg87 "Code") | [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/grokability/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/grokability/snipe-it/commits?author=alwism "Code") | [<img src="https://avatars.githubusercontent.com/u/24418301?v=4" width="110px;"/><br /><sub>LEITWERK AG</sub>](https://www.leitwerk.de/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=leitwerk-ag "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1911435?v=4" width="110px;"/><br /><sub>Adam</sub>](http://www.aboutcher.co.uk)<br />[💻](https://github.com/grokability/snipe-it/commits?author=adamboutcher "Code") | [<img src="https://avatars.githubusercontent.com/u/16104273?v=4" width="110px;"/><br /><sub>Ian</sub>](https://snksrv.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=sneak-it "Code") | [<img src="https://avatars.githubusercontent.com/u/4023909?v=4" width="110px;"/><br /><sub>Shao Yu-Lung (Allen)</sub>](http://blog.bestlong.idv.tw/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=bestlong "Code") | [<img src="https://avatars.githubusercontent.com/u/76475453?v=4" width="110px;"/><br /><sub>Haxatron</sub>](https://github.com/Haxatron)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Haxatron "Code") | [<img src="https://avatars.githubusercontent.com/u/88776392?v=4" width="110px;"/><br /><sub>PlaneNuts</sub>](https://github.com/PlaneNuts)<br />[💻](https://github.com/grokability/snipe-it/commits?author=PlaneNuts "Code") | [<img src="https://avatars.githubusercontent.com/u/3842948?v=4" width="110px;"/><br /><sub>Bradley Coudriet</sub>](http://bjcpgd.cias.rit.edu)<br />[💻](https://github.com/grokability/snipe-it/commits?author=exula "Code") | [<img src="https://avatars.githubusercontent.com/u/21966173?v=4" width="110px;"/><br /><sub>Dalton Durst</sub>](https://daltondur.st)<br />[💻](https://github.com/grokability/snipe-it/commits?author=UniversalSuperBox "Code") |
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/grokability/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/grokability/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/grokability/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/grokability/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/grokability/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/grokability/snipe-it/commits?author=QveenSi "Code") |
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/grokability/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/grokability/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/grokability/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/grokability/snipe-it/commits?author=vickyjaura183 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/grokability/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/grokability/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/grokability/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/grokability/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/grokability/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Vautia "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/grokability/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/grokability/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/grokability/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/grokability/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/grokability/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/grokability/snipe-it/commits?author=fernando-almeida "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/grokability/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/grokability/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/grokability/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/grokability/snipe-it/commits?author=subdriven "Code") | [<img src="https://avatars.githubusercontent.com/u/658865?v=4" width="110px;"/><br /><sub>Andrew Savinykh</sub>](https://github.com/AndrewSav)<br />[💻](https://github.com/grokability/snipe-it/commits?author=AndrewSav "Code") | [<img src="https://avatars.githubusercontent.com/u/1155067?v=4" width="110px;"/><br /><sub>Tadayuki Onishi</sub>](https://kenchan0130.github.io)<br />[💻](https://github.com/grokability/snipe-it/commits?author=kenchan0130 "Code") | [<img src="https://avatars.githubusercontent.com/u/112496896?v=4" width="110px;"/><br /><sub>Florian</sub>](https://github.com/floschoepfer)<br />[💻](https://github.com/grokability/snipe-it/commits?author=floschoepfer "Code") |
| [<img src="https://avatars.githubusercontent.com/u/7305753?v=4" width="110px;"/><br /><sub>Spencer Long</sub>](http://spencerlong.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=spencerrlongg "Code") | [<img src="https://avatars.githubusercontent.com/u/1141514?v=4" width="110px;"/><br /><sub>Marcus Moore</sub>](https://github.com/marcusmoore)<br />[💻](https://github.com/grokability/snipe-it/commits?author=marcusmoore "Code") | [<img src="https://avatars.githubusercontent.com/u/570639?v=4" width="110px;"/><br /><sub>Martin Meredith</sub>](https://github.com/Mezzle)<br /> | [<img src="https://avatars.githubusercontent.com/u/5731963?v=4" width="110px;"/><br /><sub>dboth</sub>](http://dboth.de)<br />[💻](https://github.com/grokability/snipe-it/commits?author=dboth "Code") | [<img src="https://avatars.githubusercontent.com/u/87536651?v=4" width="110px;"/><br /><sub>Zachary Fleck</sub>](https://github.com/zacharyfleck)<br />[💻](https://github.com/grokability/snipe-it/commits?author=zacharyfleck "Code") | [<img src="https://avatars.githubusercontent.com/u/74609912?v=4" width="110px;"/><br /><sub>VIKAAS-A</sub>](https://github.com/vikaas-cyper)<br />[💻](https://github.com/grokability/snipe-it/commits?author=vikaas-cyper "Code") | [<img src="https://avatars.githubusercontent.com/u/88882041?v=4" width="110px;"/><br /><sub>Abdul Kareem</sub>](https://github.com/ak-piracha)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ak-piracha "Code") |
| [<img src="https://avatars.githubusercontent.com/u/111287779?v=4" width="110px;"/><br /><sub>NojoudAlshehri</sub>](https://github.com/NojoudAlshehri)<br />[💻](https://github.com/grokability/snipe-it/commits?author=NojoudAlshehri "Code") | [<img src="https://avatars.githubusercontent.com/u/54367449?v=4" width="110px;"/><br /><sub>Stefan Stidl</sub>](https://github.com/stefanstidlffg)<br />[💻](https://github.com/grokability/snipe-it/commits?author=stefanstidlffg "Code") | [<img src="https://avatars.githubusercontent.com/u/87803479?v=4" width="110px;"/><br /><sub>Quentin Aymard</sub>](https://github.com/qay21)<br />[💻](https://github.com/grokability/snipe-it/commits?author=qay21 "Code") | [<img src="https://avatars.githubusercontent.com/u/5396871?v=4" width="110px;"/><br /><sub>Grant Le Roux</sub>](https://github.com/cram42)<br />[💻](https://github.com/grokability/snipe-it/commits?author=cram42 "Code") | [<img src="https://avatars.githubusercontent.com/u/58479551?v=4" width="110px;"/><br /><sub>Bogdan</sub>](http://@singrity)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Singrity "Code") | [<img src="https://avatars.githubusercontent.com/u/3483684?v=4" width="110px;"/><br /><sub>mmanjos</sub>](https://github.com/mmanjos)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mmanjos "Code") | [<img src="https://avatars.githubusercontent.com/u/7429229?v=4" width="110px;"/><br /><sub>Abdelaziz Faki</sub>](https://azooz2014.github.io/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Azooz2014 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/47315739?v=4" width="110px;"/><br /><sub>bilias</sub>](https://github.com/bilias)<br />[💻](https://github.com/grokability/snipe-it/commits?author=bilias "Code") | [<img src="https://avatars.githubusercontent.com/u/2565989?v=4" width="110px;"/><br /><sub>coach1988</sub>](https://github.com/coach1988)<br />[💻](https://github.com/grokability/snipe-it/commits?author=coach1988 "Code") | [<img src="https://avatars.githubusercontent.com/u/11910225?v=4" width="110px;"/><br /><sub>MrM</sub>](https://github.com/mauro-miatello)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mauro-miatello "Code") | [<img src="https://avatars.githubusercontent.com/u/60405354?v=4" width="110px;"/><br /><sub>koiakoia</sub>](https://github.com/koiakoia)<br />[💻](https://github.com/grokability/snipe-it/commits?author=koiakoia "Code") | [<img src="https://avatars.githubusercontent.com/u/5323832?v=4" width="110px;"/><br /><sub>Mustafa Online</sub>](https://github.com/mustafa-online)<br />[💻](https://github.com/grokability/snipe-it/commits?author=mustafa-online "Code") | [<img src="https://avatars.githubusercontent.com/u/104601439?v=4" width="110px;"/><br /><sub>franceslui</sub>](https://github.com/franceslui)<br />[💻](https://github.com/grokability/snipe-it/commits?author=franceslui "Code") | [<img src="https://avatars.githubusercontent.com/u/125313163?v=4" width="110px;"/><br /><sub>Q4kK</sub>](https://github.com/Q4kK)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Q4kK "Code") |
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/grokability/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/grokability/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/grokability/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/grokability/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/grokability/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/grokability/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/grokability/snipe-it/commits?author=bryanlopezinc "Tests") |
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/grokability/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/grokability/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/grokability/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/grokability/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/grokability/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/grokability/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/grokability/snipe-it/commits?author=arne-kroeger "Code") |
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/grokability/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/grokability/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/grokability/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/grokability/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/grokability/snipe-it/commits?author=sgross-emlix "Code") |
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/grokability/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/grokability/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/grokability/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/grokability/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/grokability/snipe-it/commits?author=azmcnutt "Code") | [<img src="https://avatars.githubusercontent.com/u/5183146?v=4" width="110px;"/><br /><sub>Fiala06</sub>](https://github.com/Fiala06)<br />[💻](https://github.com/grokability/snipe-it/commits?author=Fiala06 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28693782?v=4" width="110px;"/><br /><sub>Nathan Taylor</sub>](https://github.com/ntaylor-86)<br />[💻](https://github.com/grokability/snipe-it/commits?author=ntaylor-86 "Code") | [<img src="https://avatars.githubusercontent.com/u/16699443?v=4" width="110px;"/><br /><sub>fvollmer</sub>](https://github.com/fvollmer)<br />[💻](https://github.com/grokability/snipe-it/commits?author=fvollmer "Code") |[<img src="https://avatars.githubusercontent.com/u/109086466?v=4" width="110px;"/><br /><sub>36864</sub>](https://github.com/36864)<br />[💻](https://github.com/grokability/snipe-it/commits?author=36864 "Code") |[<img src="https://avatars.githubusercontent.com/u/365751?v=4" width="110px;"/><br /><sub>CloCkWeRX</sub>](https://github.com/CloCkWeRX)<br />[💻](https://github.com/grokability/snipe-it/commits?author=CloCkWeRX "Code")|[<img src="https://avatars.githubusercontent.com/u/102852568?v=4" width="110px;"/><br /><sub>BeatSpark</sub>](https://github.com/BeatSpark)<br />[💻](https://github.com/grokability/snipe-it/commits?author=BeatSpark "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
+2 -2
View File
@@ -2,7 +2,7 @@ FROM ubuntu:24.04
LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>"
# No need to add `apt-get clean` here, reference:
# - https://github.com/snipe/snipe-it/pull/9201
# - https://github.com/grokability/snipe-it/pull/9201
# - https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get
RUN export DEBIAN_FRONTEND=noninteractive; \
@@ -110,7 +110,7 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Get dependencies
USER docker
RUN composer install --no-dev --working-dir=/var/www/html
RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html && rm -rf /var/www/html/vendor/*/*/.git
USER root
############### APPLICATION INSTALL/INIT #################
+1 -1
View File
@@ -70,7 +70,7 @@ COPY --from=composer /usr/bin/composer /usr/local/bin
ARG COMPOSER_ALLOW_SUPERUSER=1
RUN set -eux; \
# Download and extract snipeit tarball
curl -o snipeit.tar.gz -fL "https://github.com/snipe/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \
curl -o snipeit.tar.gz -fL "https://github.com/grokability/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \
tar -xzf snipeit.tar.gz --strip-components=1 -C /var/www/html/; \
rm snipeit.tar.gz; \
# Install composer php dependencies
+19 -8
View File
@@ -1,6 +1,6 @@
![snipe-it-by-grok](https://github.com/snipe/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602)
![snipe-it-by-grok](https://github.com/grokability/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests.yml)
[![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk)
## Snipe-IT - Open Source Asset Management System
@@ -9,7 +9,7 @@ This is a FOSS project for asset management in IT Operations. Knowing who has wh
It is built on [Laravel 11](http://laravel.com).
Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
Snipe-IT is actively developed and we [release quite frequently](https://github.com/grokability/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).)
> [!TIP]
> __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into.
@@ -44,7 +44,7 @@ For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.i
-----
### Bug Reports & Feature Requests
Feel free to check out the [GitHub Issues for this project](https://github.com/snipe/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
Feel free to check out the [GitHub Issues for this project](https://github.com/grokability/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue.
> [!IMPORTANT]
> **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.**
@@ -76,26 +76,37 @@ Since the release of the JSON REST API, several third-party developers have been
> [!NOTE]
> As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
#### Libraries & Modules
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
- [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT.
- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT.
- [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API
- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [UniFi to Snipe-IT](https://www.edtechirl.com/p/snipe-it-and-azure-asset-management) originally by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT.
- [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT.
- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT.
- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API.
- [InQRy (archived)](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
- [Marksman (archived)](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
- [Python Module (archived)](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
We also have a handful of [Google Apps scripts](https://github.com/grokability/google-apps-scripts-for-snipe-it) to help with various tasks.
#### Mobile Apps
We're currently working on our own mobile app, but in the meantime, check out these third-party apps that work with Snipe-IT:
- [SnipeMate](https://snipemate.app/) (iOS, Google Play, Huawei AppGallery) by Mars Technology
- [Snipe-Scan](https://apps.apple.com/do/app/snipe-scan/id6744179400?uo=2) (iOS) by Nicolas Maton
- [Snipe-IT Assets Management](https://play.google.com/store/apps/details?id=com.diegogarciadev.assetsmanager.snipeit&hl=en&pli=1) (Google Play) by DiegoGarciaDEV
- [AssetX](https://apps.apple.com/my/app/assetx-for-snipe-it/id6741996196?uo=2) (iOS) for Snipe-IT by Rishi Gupta
-----
### Join the Community!
Vendored
+1 -1
View File
@@ -1,7 +1,7 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh"
SNIPEIT_SH_URL= "https://raw.githubusercontent.com/grokability/snipe-it/master/snipeit.sh"
NETWORK_BRIDGE= "en0: Wi-Fi (AirPort)"
Vagrant.configure("2") do |config|
+1 -1
View File
@@ -6,7 +6,7 @@
"it asset"
],
"website": "https://snipeitapp.com/",
"repository": "https://github.com/snipe/snipe-it",
"repository": "https://github.com/grokability/snipe-it",
"logo": "https://pbs.twimg.com/profile_images/976748875733020672/K-HnZCCK_400x400.jpg",
"success_url": "/setup",
"env": {
@@ -0,0 +1,48 @@
<?php
namespace App\Actions\CheckoutRequests;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\RequestAssetCancelation;
use Illuminate\Auth\Access\AuthorizationException;
class CancelCheckoutRequestAction
{
public static function run(Asset $asset, User $user)
{
if (!Company::isCurrentUserHasAccess($asset)) {
throw new AuthorizationException();
}
$asset->cancelRequest();
$asset->decrement('requests_counter', 1);
$data['item'] = $asset;
$data['target'] = $user;
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
$logaction->target_id = $data['user_id'] = auth()->id();
$logaction->target_type = User::class;
$logaction->location_id = $user->location_id ?? null;
$logaction->logaction('request canceled');
try {
$settings->notify(new RequestAssetCancelation($data));
} catch (\Exception $e) {
\Log::warning($e);
}
return true;
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Actions\CheckoutRequests;
use App\Exceptions\AssetNotRequestable;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\RequestAssetNotification;
use Illuminate\Auth\Access\AuthorizationException;
use Log;
class CreateCheckoutRequestAction
{
/**
* @throws AssetNotRequestable
* @throws AuthorizationException
*/
public static function run(Asset $asset, User $user): string
{
if (is_null(Asset::RequestableAssets()->find($asset->id))) {
throw new AssetNotRequestable($asset);
}
if (!Company::isCurrentUserHasAccess($asset)) {
throw new AuthorizationException();
}
$data['item'] = $asset;
$data['target'] = $user;
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
$logaction->target_id = $data['user_id'] = auth()->id();
$logaction->target_type = User::class;
$logaction->location_id = $user->location_id ?? null;
$logaction->logaction('requested');
$asset->request();
$asset->increment('requests_counter', 1);
try {
$settings->notify(new RequestAssetNotification($data));
} catch (\Exception $e) {
Log::warning($e);
}
return true;
}
}
@@ -0,0 +1,151 @@
<?php
namespace App\Console\Commands;
use App\Models\Accessory;
use App\Models\Actionlog;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
class FixBulkAccessoryCheckinActionLogEntries extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:fix-bulk-accessory-action-log-entries {--dry-run : Run the sync process but don\'t update the database} {--skip-backup : Skip pre-execution backup}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This script attempts to fix timestamps and missing created_by values for bulk checkin entries in the log table';
private bool $dryrun = false;
private bool $skipBackup = false;
/**
* Execute the console command.
*/
public function handle()
{
$this->skipBackup = $this->option('skip-backup');
$this->dryrun = $this->option('dry-run');
if ($this->dryrun) {
$this->info('This is a DRY RUN - no changes will be saved.');
$this->newLine();
}
$logs = Actionlog::query()
// only look for accessory checkin logs
->where('item_type', Accessory::class)
// that were part of a bulk checkin
->where('note', 'Bulk checkin items')
// logs that were improperly timestamped should have created_at in the 1970s
->whereYear('created_at', '1970')
->get();
if ($logs->isEmpty()) {
$this->info('No logs found with incorrect timestamps.');
return 0;
}
$this->info('Found ' . $logs->count() . ' logs with incorrect timestamps:');
$this->table(
['ID', 'Created By', 'Created At', 'Updated At'],
$logs->map(function ($log) {
return [
$log->id,
$log->created_by,
$log->created_at,
$log->updated_at,
];
})
);
if (!$this->dryrun && !$this->confirm('Update these logs?')) {
return 0;
}
if (!$this->dryrun && !$this->skipBackup) {
$this->info('Backing up the database before making changes...');
$this->call('snipeit:backup');
}
if ($this->dryrun) {
$this->newLine();
$this->info('DRY RUN. NOT ACTUALLY UPDATING LOGS.');
}
foreach ($logs as $log) {
$this->newLine();
$this->info('Processing log id:' . $log->id);
// created_by was not being set for accessory bulk checkins
// so let's see if there was another bulk checkin log
// with the same timestamp and a created_by value we can use.
if (is_null($log->created_by)) {
$createdByFromSimilarLog = $this->getCreatedByAttributeFromSimilarLog($log);
if ($createdByFromSimilarLog) {
$this->line(vsprintf('Updating log id:%s created_by to %s', [$log->id, $createdByFromSimilarLog]));
$log->created_by = $createdByFromSimilarLog;
} else {
$this->warn(vsprintf('No created_by found for log id:%s', [$log->id]));
$this->warn('Skipping updating this log since no similar log was found to update created_by from.');
// If we can't find a similar log then let's skip updating it
continue;
}
}
$this->line(vsprintf('Updating log id:%s from %s to %s', [$log->id, $log->created_at, $log->updated_at]));
$log->created_at = $log->updated_at;
if (!$this->dryrun) {
Model::withoutTimestamps(function () use ($log) {
$log->saveQuietly();
});
}
}
$this->newLine();
if ($this->dryrun) {
$this->info('DRY RUN. NO CHANGES WERE ACTUALLY MADE.');
}
return 0;
}
/**
* Hopefully the bulk checkin included other items like assets or licenses
* so we can use one of those logs to get the correct created_by value.
*
* This method attempts to find a bulk check in log that was
* created at the same time as the log passed in.
*/
private function getCreatedByAttributeFromSimilarLog(Actionlog $log): null|int
{
$similarLog = Actionlog::query()
->whereNotNull('created_by')
->where([
'action_type' => 'checkin from',
'note' => 'Bulk checkin items',
'target_id' => $log->target_id,
'target_type' => $log->target_type,
'created_at' => $log->updated_at,
])
->first();
if ($similarLog) {
return $similarLog->created_by;
}
return null;
}
}
@@ -2,11 +2,9 @@
namespace App\Console\Commands;
use App\Helpers\Helper;
use Illuminate\Console\Command;
use App\Models\User;
use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\DB;
class GeneratePersonalAccessToken extends Command
@@ -43,9 +41,8 @@ class GeneratePersonalAccessToken extends Command
*
* @return void
*/
public function __construct(TokenRepository $tokenRepository, ValidationFactory $validation)
public function __construct(TokenRepository $tokenRepository)
{
$this->validation = $validation;
$this->tokenRepository = $tokenRepository;
parent::__construct();
}
@@ -76,7 +73,7 @@ class GeneratePersonalAccessToken extends Command
} else {
$this->warn('Your API Token has been created. Be sure to copy this token now, as it will not be accessible again.');
$this->warn('Your API Token has been created. Be sure to copy this token now, as it WILL NOT be accessible again.');
if ($token = DB::table('oauth_access_tokens')->where('user_id', '=', $user->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first()) {
$this->info('API Token ID: '.$token->id);
+13 -3
View File
@@ -361,9 +361,15 @@ class LdapSync extends Command
// (Specifically, we don't handle a value of '0.0' correctly)
$raw_value = @$results[$i][$ldap_map["active_flag"]][0];
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$boolean_cast = (bool) $raw_value;
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
if (Setting::getSettings()->ldap_invert_active_flag === 1) {
// Because ldap_active_flag is set, if filter_var is true or boolean_cast is true, then user is suspended
$user->activated = !($filter_var ?? $boolean_cast);
}else{
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
}
} elseif (array_key_exists('useraccountcontrol', $results[$i])) {
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
@@ -428,8 +434,12 @@ class LdapSync extends Command
$item['note'] = $item['createorupdate'];
$item['status'] = 'success';
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
$user->groups()->attach($ldap_default_group);
// Check if the relationship already exists
if (!$user->groups()->where('group_id', $ldap_default_group)->exists()) {
$user->groups()->attach($ldap_default_group);
}
}
//updates assets location based on user's location
if ($user->wasChanged('location_id')) {
foreach ($user->assets as $asset) {
+3 -3
View File
@@ -51,8 +51,7 @@ class PaveIt extends Command
}
// List all the tables in the database so we don't have to worry about missing some as the app grows
$tables = DB::connection()->getDoctrineSchemaManager()->listTableNames();
$tables = Schema::getTables();
$except_tables = [
'oauth_access_tokens',
'oauth_clients',
@@ -74,7 +73,8 @@ class PaveIt extends Command
}
}
foreach ($tables as $table) {
foreach ($tables as $table_obj) {
$table = $table_obj['name'];
if (in_array($table, $except_tables)) {
$this->info($table. ' is SKIPPED.');
} else {
+12 -2
View File
@@ -289,6 +289,7 @@ class RestoreFromBackup extends Command
$interesting_files = [];
$boring_files = [];
$unsafe_files = [];
for ($i = 0; $i < $za->numFiles; $i++) {
$stat_results = $za->statIndex($i);
@@ -328,7 +329,8 @@ class RestoreFromBackup extends Command
}
}
$good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt',
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',];
'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico', 'avif'
];
foreach (array_merge($private_files, $public_files) as $file) {
$has_wildcard = (strpos($file, '*') !== false);
if ($has_wildcard) {
@@ -338,7 +340,9 @@ class RestoreFromBackup extends Command
if ($last_pos !== false) {
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
if (!in_array($extension, $good_extensions)) {
$this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped');
// gathering potentially unsafe files here to return at exit
$unsafe_files[] = $raw_path;
Log::debug('Potentially unsafe file '.$raw_path.' is being skipped');
$boring_files[] = $raw_path;
continue 2;
}
@@ -372,6 +376,7 @@ class RestoreFromBackup extends Command
if ($this->option('sanitize-guess-prefix')) {
$prefix = SQLStreamer::guess_prefix($sql_contents);
$this->line($prefix);
return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitize your SQL.");
}
@@ -505,6 +510,11 @@ class RestoreFromBackup extends Command
} else {
$this->info(count($interesting_files).' files were succesfully transferred');
}
if (count($unsafe_files) > 0) {
foreach ($unsafe_files as $unsafe_file) {
$this->warn('Potentially unsafe file '.$unsafe_file.' was skipped');
}
}
foreach ($boring_files as $boring_file) {
$this->warn($boring_file.' was skipped.');
}
@@ -64,28 +64,43 @@ class SendAcceptanceReminder extends Command
->groupBy(function($item) {
return $item['acceptance']->assignedTo ? $item['acceptance']->assignedTo->id : '';
});
$no_email_list= [];
foreach($unacceptedAssetGroups as $unacceptedAssetGroup) {
// The [0] is weird, but it allows for the item_count to work and grabs the appropriate info for each user.
// Collapsing and flattening the collection doesn't work above.
$acceptance = $unacceptedAssetGroup[0]['acceptance'];
$locale = $acceptance->assignedTo?->locale;
$email = $acceptance->assignedTo?->email;
if(!$email){
$this->info($acceptance->assignedTo?->present()->fullName().' has no email address.');
$no_email_list[] = [
'id' => $acceptance->assignedTo?->id,
'name' => $acceptance->assignedTo?->present()->fullName(),
];
} else {
$count++;
}
$item_count = $unacceptedAssetGroup->count();
if ($locale && $email) {
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count))->locale($locale));
} elseif ($email) {
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count)));
}
$count++;
}
$this->info($count.' users notified.');
$headers = ['ID', 'Name'];
$rows = [];
foreach ($no_email_list as $user) {
$rows[] = [$user['id'], $user['name']];
}
$this->info("The following users do not have an email address:");
$this->table($headers, $rows);
return 0;
}
@@ -42,27 +42,28 @@ class SendExpirationAlerts extends Command
public function handle()
{
$settings = Setting::getSettings();
$threshold = $settings->alert_interval;
$alert_interval = $settings->alert_interval;
if (($settings->alert_email != '') && ($settings->alerts_enabled == 1)) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item)) // Trim each email
->filter(fn($item) => !empty($item))
->all();
// Expiring Assets
$assets = Asset::getExpiringWarrantee($threshold);
$assets = Asset::getExpiringWarrantee($alert_interval);
if ($assets->count() > 0) {
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold]));
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $threshold));
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
}
// Expiring licenses
$licenses = License::getExpiringLicenses($threshold);
$licenses = License::getExpiringLicenses($alert_interval);
if ($licenses->count() > 0) {
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold]));
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $threshold));
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval));
}
} else {
if ($settings->alert_email == '') {
@@ -2,13 +2,12 @@
namespace App\Console\Commands;
use App\Mail\SendUpcomingAuditMail;
use App\Models\Asset;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class SendUpcomingAuditReport extends Command
{
@@ -48,19 +47,20 @@ class SendUpcomingAuditReport extends Command
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline');
$assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline');
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item))
->filter(fn($item) => !empty($item))
->all();
$this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
}
}
@@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use App\Helpers\Helper;
use Illuminate\Console\Command;
class TestLocationsFMCS extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:test-locations-fmcs {--location_id=}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Test for company ID inconsistencies if FullMultipleCompanySupport with scoped locations will be used.';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('This script checks for company ID inconsistencies if Full Multiple Company Support with scoped locations will be used.');
$this->info('This could take few moments if have a very large dataset.');
$this->newLine();
// if parameter location_id is set, only test this location
$location_id = null;
if ($this->option('location_id')) {
$location_id = $this->option('location_id');
}
$mismatched = Helper::test_locations_fmcs(true, $location_id);
$this->warn(trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)));
$this->newLine();
$this->info('Edit your locations to associate them with the correct company.');
$header = ['Type', 'ID', 'Name', 'Checkout Type', 'Company ID', 'Item Company', 'Item Location', 'Location Company', 'Location Company ID'];
sort($mismatched);
$this->table($header, $mismatched);
}
}
+7 -4
View File
@@ -5,6 +5,7 @@ namespace App\Console;
use App\Console\Commands\ImportLocations;
use App\Console\Commands\ReEncodeCustomFieldNames;
use App\Console\Commands\RestoreDeletedUsers;
use App\Models\Setting;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -18,12 +19,14 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
if(Setting::getSettings()->alerts_enabled === 1) {
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
}
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
$schedule->command('auth:clear-resets')->everyFifteenMinutes();
$schedule->command('saml:clear_expired_nonces')->weekly();
}
+9
View File
@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use Exception;
class AssetNotRequestable extends Exception
{
}
+8 -2
View File
@@ -125,7 +125,9 @@ class Handler extends ExceptionHandler
// This is traaaaash but it handles models that are not found while using route model binding :(
// The only alternative is to set that at *each* route, which is crazypants
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
$model_name = last(explode('\\', $e->getModel()));
// This gets the MVC model name from the exception and formats in a way that's less fugly
$model_name = strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel())))));
$route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
// Sigh.
@@ -137,6 +139,10 @@ class Handler extends ExceptionHandler
$route = 'models.index';
} elseif ($route == 'predefinedkits.index') {
$route = 'kits.index';
} elseif ($route == 'assetmaintenances.index') {
$route = 'maintenances.index';
} elseif ($route === 'licenseseats.index') {
$route = 'licenses.index';
}
return redirect()
@@ -199,4 +205,4 @@ class Handler extends ExceptionHandler
//
});
}
}
}
@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class UserDoestExistException extends Exception
{
}
+95 -4
View File
@@ -12,6 +12,7 @@ use App\Models\Depreciation;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\License;
use App\Models\Location;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
use Carbon\Carbon;
@@ -868,7 +869,7 @@ class Helper
$filetype = @finfo_file($finfo, $file);
finfo_close($finfo);
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif')) {
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif') || ($filetype == 'image/webp')) {
return $filetype;
}
@@ -1479,6 +1480,7 @@ class Helper
$redirect_option = Session::get('redirect_option');
$checkout_to_type = Session::get('checkout_to_type');
$checkedInFrom = Session::get('checkedInFrom');
// return to index
if ($redirect_option == 'index') {
@@ -1520,13 +1522,102 @@ class Helper
if ($redirect_option == 'target') {
switch ($checkout_to_type) {
case 'user':
return route('users.show', $request->assigned_user);
return route('users.show', $request->assigned_user ?? $checkedInFrom);
case 'location':
return route('locations.show', $request->assigned_location);
return route('locations.show', $request->assigned_location ?? $checkedInFrom);
case 'asset':
return route('hardware.show', $request->assigned_asset);
return route('hardware.show', $request->assigned_asset ?? $checkedInFrom);
}
}
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));
}
/**
* Check for inconsistencies before activating scoped locations with FullMultipleCompanySupport
* If there are locations with different companies than related objects unforseen problems could arise
*
* @author T. Regnery <tobias.regnery@gmail.com>
* @since 7.0
*
* @param $artisan when false, bail out on first inconsistent entry
* @param $location_id when set, only test this specific location
* @param $new_company_id in case of updating a location, this is the newly requested company_id
* @return string []
*/
static public function test_locations_fmcs($artisan, $location_id = null, $new_company_id = null) {
$mismatched = [];
if ($location_id) {
$location = Location::find($location_id);
if ($location) {
$locations = collect([])->push(Location::find($location_id));
}
} else {
$locations = Location::all();
}
foreach($locations as $location) {
// in case of an update of a single location, use the newly requested company_id
if ($new_company_id) {
$location_company = $new_company_id;
} else {
$location_company = $location->company_id;
}
// Depending on the relationship, we must use different operations to retrieve the objects
$keywords_relation = [
'many' => [
'accessories',
'assets',
'assignedAccessories',
'assignedAssets',
'components',
'consumables',
'rtd_assets',
'users',
],
'one' => [
'manager',
'parent',
]];
// In case of a single location, the children must be checked as well, because we don't walk every location
if ($location_id) {
$keywords_relation['many'][] = 'children';
}
foreach ($keywords_relation as $relation => $keywords) {
foreach($keywords as $keyword) {
if ($relation == 'many') {
$items = $location->{$keyword}->all();
} else {
$items = collect([])->push($location->$keyword);
}
foreach ($items as $item) {
if ($item && $item->company_id != $location_company) {
$mismatched[] = [
class_basename(get_class($item)),
$item->id,
$item->name ?? $item->asset_tag ?? $item->serial ?? $item->username,
str_replace('App\\Models\\', '', $item->assigned_type) ?? null,
$item->company_id ?? null,
$item->company->name ?? null,
// $item->defaultLoc->id ?? null,
// $item->defaultLoc->name ?? null,
// $item->defaultLoc->company->id ?? null,
// $item->defaultLoc->company->name ?? null,
$item->location->name ?? null,
$item->location->company->name ?? null,
$location_company ?? null,
];
}
}
}
}
}
return $mismatched;
}
}
@@ -112,13 +112,13 @@ class AccessoriesController extends Controller
{
$this->authorize('create', Accessory::class);
$accessory = clone $accessory;
$accessory->id = null;
$accessory->location_id = null;
$cloned = clone $accessory;
$cloned->id = null;
$cloned->deleted_at = '';
$cloned->location_id = null;
return view('accessories/edit')
->with('item', $accessory);
->with('item', $cloned);
}
@@ -7,7 +7,6 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use \Illuminate\Contracts\View\View;
@@ -30,9 +29,17 @@ class AccessoryCheckinController extends Controller
}
$accessory = Accessory::find($accessory_user->accessory_id);
//based on what the accessory is checked out to the target redirect option will be displayed accordingly.
$target_option = match ($accessory_user->assigned_type) {
'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset')]),
'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]),
default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]),
};
$this->authorize('checkin', $accessory);
return view('accessories/checkin', compact('accessory'))->with('backto', $backto);
return view('accessories/checkin', compact('accessory', 'target_option'))->with('backto', $backto);
}
/**
@@ -51,8 +58,14 @@ class AccessoryCheckinController extends Controller
$accessory = Accessory::find($accessory_checkout->accessory_id);
$this->authorize('checkin', $accessory);
session()->put('checkedInFrom', $accessory_checkout->assigned_to);
session()->put('checkout_to_type', match ($accessory_checkout->assigned_type) {
'App\Models\User' => 'user',
'App\Models\Location' => 'location',
'App\Models\Asset' => 'asset',
});
$this->authorize('checkin', $accessory);
$checkin_hours = date('H:i:s');
$checkin_at = date('Y-m-d H:i:s');
if ($request->filled('checkin_at')) {
@@ -208,9 +208,12 @@ class AcceptanceController extends Controller
*/
$branding_settings = SettingsController::getPDFBranding();
if (is_null($branding_settings->logo)){
$path_logo = "";
} else {
$path_logo = "";
// Check for the PDF logo path and use that, otherwise use the regular logo path
if (!is_null($branding_settings->acceptance_pdf_logo)) {
$path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo;
} elseif (!is_null($branding_settings->logo)) {
$path_logo = public_path() . '/uploads/' . $branding_settings->logo;
}
@@ -66,7 +66,7 @@ class AccessoriesController extends Controller
}
if ($request->filled('company_id')) {
$accessories->where('company_id', '=', $request->input('company_id'));
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use App\Http\Transformers\UploadedFilesTransformer;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
@@ -13,6 +14,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Illuminate\Http\Request;
/**
@@ -72,33 +74,37 @@ class AssetFilesController extends Controller
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function list($assetId = null) : JsonResponse
public function list(Asset $asset, Request $request) : JsonResponse | array
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
// Check that there are some uploads on this asset that can be listed
if ($asset->uploads->count() > 0) {
$files = array();
foreach ($asset->uploads as $upload) {
array_push($files, $upload);
}
// Give the list of files back to the user
return response()->json(Helper::formatStandardApiResponse('success', $files, trans('admin/hardware/message.upload.success')));
}
$this->authorize('view', $asset);
// There are no files.
return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/hardware/message.upload.success')));
$allowed_columns =
[
'id',
'filename',
'eol',
'notes',
'created_at',
'updated_at',
];
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')->where('item_type', '=', Asset::class)->where('item_id', '=', $asset->id);
if ($request->filled('search')) {
$files = $files->TextSearch($request->input('search'));
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error')), 500);
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $files->count()) ? $files->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$files = $files->orderBy($sort, $order);
$files = $files->skip($offset)->take($limit)->get();
return (new UploadedFilesTransformer())->transformFiles($files, $files->count());
}
/**
@@ -111,12 +117,8 @@ class AssetFilesController extends Controller
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function show($assetId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
public function show(Asset $asset, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($asset->id)) {
@@ -164,12 +166,8 @@ class AssetFilesController extends Controller
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function destroy($assetId = null, $fileId = null) : JsonResponse
public function destroy(Asset $asset, $fileId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
$rel_path = 'private_uploads/assets';
@@ -179,12 +177,14 @@ class AssetFilesController extends Controller
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
@@ -77,6 +77,18 @@ class AssetModelsController extends Controller
$assetmodels->onlyTrashed();
}
if ($request->filled('name')) {
$assetmodels = $assetmodels->where('models.name', '=', $request->input('name'));
}
if ($request->filled('model_number')) {
$assetmodels = $assetmodels->where('models.model_number', '=', $request->input('model_number'));
}
if ($request->filled('notes')) {
$assetmodels = $assetmodels->where('models.notes', '=', $request->input('notes'));
}
if ($request->filled('category_id')) {
$assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id'));
}
+99 -52
View File
@@ -34,6 +34,7 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use App\View\Label;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
/**
@@ -435,12 +436,6 @@ class AssetsController extends Controller
}]);
}
/**
* Here we're just determining which Transformer (via $transformer) to use based on the
* variables we set earlier on in this method - we default to AssetsTransformer.
*/
return (new $transformer)->transformAssets($assets, $total, $request);
}
@@ -491,15 +486,32 @@ class AssetsController extends Controller
public function showBySerial(Request $request, $serial): JsonResponse | array
{
$this->authorize('index', Asset::class);
$assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
$assets = Asset::where('serial', $serial)->with([
'assetstatus',
'assignedTo',
'company',
'defaultLoc',
'location',
'model.category',
'model.depreciation',
'model.fieldset',
'model.manufacturer',
'supplier',
]);
// Check if they've passed ?deleted=true
if ($request->input('deleted', 'false') == 'true') {
$assets = $assets->withTrashed();
}
if (($assets = $assets->get()) && ($assets->count()) > 0) {
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
$limit = app('api_limit_value');
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
if (($assets) && ($assets->count()) > 0) {
return (new AssetsTransformer)->transformAssets($assets, $total);
}
// If there are 0 results, return the "no such asset" response
@@ -1047,7 +1059,7 @@ class AssetsController extends Controller
* @param int $id
* @since [v4.0]
*/
public function audit(Request $request): JsonResponse
public function audit(Request $request, Asset $asset): JsonResponse
{
$this->authorize('audit', Asset::class);
@@ -1055,36 +1067,15 @@ class AssetsController extends Controller
$settings = Setting::getSettings();
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
// No tag passed - return an error
if (!$request->filled('asset_tag')) {
return response()->json(Helper::formatStandardApiResponse('error', [
'asset_tag' => '',
'error' => trans('admin/hardware/message.no_tag'),
], trans('admin/hardware/message.no_tag')), 200);
// Allow the asset tag to be passed in the payload (legacy method)
if ($request->filled('asset_tag')) {
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
}
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
if ($asset) {
/**
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
* de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
* the audit log entry we're creating through this controller.
*
* To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
* that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
* will bypass normal model-level validation that's usually handled at the observer )
*
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
* which manually invokes Watson Validating to make sure the asset's model is valid.
*
* @see \App\Observers\AssetObserver::updating()
*/
$asset->unsetEventDispatcher();
$originalValues = $asset->getRawOriginal();
$asset->next_audit_date = $dt;
if ($request->filled('next_audit_date')) {
@@ -1099,33 +1090,89 @@ class AssetsController extends Controller
$asset->last_audit_date = date('Y-m-d H:i:s');
// Set up the payload for re-display in the API response
$payload = [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'note' => $request->input('note'),
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
];
/**
* Update custom fields in the database.
* Validation for these fields is handled through the AssetRequest form request
* $model = AssetModel::find($request->get('model_id'));
*/
if (($asset->model) && ($asset->model->fieldset)) {
$payload['custom_fields'] = [];
foreach ($asset->model->fieldset->fields as $field) {
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
$payload['custom_fields'][$field->db_column] = $request->input($field->db_column);
}
}
}
// Validate custom fields
Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate();
// Validate the rest of the data before we turn off the event dispatcher
if ($asset->isInvalid()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()));
}
/**
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
* de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to
* the audit log entry we're creating through this controller.
*
* To prevent this double-logging (one for update and one for audit), we skip the observer and bypass
* that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher()
* will bypass normal model-level validation that's usually handled at the observer)
*
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
* which manually invokes Watson Validating to make sure the asset's model is valid.
*
* @see \App\Observers\AssetObserver::updating()
* @see \App\Models\Asset::save()
*/
$asset->unsetEventDispatcher();
/**
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
* We have to invoke this manually because of the unsetEventDispatcher() above.)
*/
if ($asset->isValid() && $asset->save()) {
$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($asset->next_audit_date),
], trans('admin/hardware/message.audit.success')));
$asset->logAudit(request('note'), request('location_id'), null, $originalValues);
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/hardware/message.audit.success')));
}
// Asset failed validation or was not able to be saved
return response()->json(Helper::formatStandardApiResponse('error', [
'asset_tag' => e($asset->asset_tag),
'error' => $asset->getErrors()->first(),
], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200);
}
// No matching asset for the asset tag that was passed.
return response()->json(Helper::formatStandardApiResponse('error', [
'asset_tag' => e($request->input('asset_tag')),
'error' => trans('admin/hardware/message.audit.error'),
], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
use App\Exceptions\AssetNotRequestable;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Exception;
class CheckoutRequest extends Controller
{
public function store(Asset $asset): JsonResponse
{
try {
CreateCheckoutRequestAction::run($asset, auth()->user());
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.success')));
} catch (AssetNotRequestable $e) {
return response()->json(Helper::formatStandardApiResponse('error', 'Asset is not requestable'));
} catch (AuthorizationException $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
} catch (Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
}
public function destroy(Asset $asset): JsonResponse
{
try {
CancelCheckoutRequestAction::run($asset, auth()->user());
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.canceled')));
} catch (AuthorizationException $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
} catch (Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
}
}
@@ -60,7 +60,7 @@ class ComponentsController extends Controller
}
if ($request->filled('company_id')) {
$components->where('company_id', '=', $request->input('company_id'));
$components->where('components.company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {
@@ -40,7 +40,7 @@ class ConsumablesController extends Controller
}
if ($request->filled('company_id')) {
$consumables->where('company_id', '=', $request->input('company_id'));
$consumables->where('consumables.company_id', '=', $request->input('company_id'));
}
if ($request->filled('category_id')) {
+29 -13
View File
@@ -66,25 +66,41 @@ class ImportController extends Controller
if (! ini_get('auto_detect_line_endings')) {
ini_set('auto_detect_line_endings', '1');
}
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
$reader = null;
if (strcasecmp($encoding, 'UTF-8') != 0) {
$transliterated = iconv($encoding, 'UTF-8', $file_contents);
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
if ($tmpresults !== false) {
if (function_exists('iconv')) {
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
$reader = null;
if (strcasecmp($encoding, 'UTF-8') != 0) {
$transliterated = false;
try {
$transliterated = iconv(strtoupper($encoding), 'UTF-8', $file_contents);
} catch (\Exception $e) {
$transliterated = false; //blank out the partially-decoded string
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.transliterate_failure', ["encoding" => $encoding])
),
422
);
}
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
$transliterated = null; //save on memory?
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
if ($tmpresults !== false) {
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
}
}
}
}
$file_contents = null; //try to save on memory, I guess?
}
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
$file_contents = null; //try to save on memory, I guess?
try {
$import->header_row = $reader->fetchOne(0);
@@ -136,13 +136,13 @@ class LicenseSeatsController extends Controller
if ($licenseSeat->save()) {
if ($is_checkin) {
$licenseSeat->logCheckin($target, $request->input('note'));
$licenseSeat->logCheckin($target, $request->input('notes'));
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}
// in this case, relevant fields are touched but it's not a checkin operation. so it must be a checkout operation.
$licenseSeat->logCheckout($request->input('note'), $target);
$licenseSeat->logCheckout($request->input('notes'), $target);
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}
@@ -12,7 +12,9 @@ use App\Http\Transformers\SelectlistTransformer;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Location;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
@@ -46,6 +48,7 @@ class LocationsController extends Controller
'id',
'image',
'ldap_ou',
'company_id',
'manager_id',
'name',
'rtd_assets_count',
@@ -74,8 +77,10 @@ class LocationsController extends Controller
'locations.image',
'locations.ldap_ou',
'locations.currency',
'locations.company_id',
'locations.notes',
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('assignedAccessories as assigned_accessories_count')
@@ -84,6 +89,11 @@ class LocationsController extends Controller
->withCount('children as children_count')
->withCount('users as users_count');
// Only scope locations if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$locations = Company::scopeCompanyables($locations);
}
if ($request->filled('search')) {
$locations = $locations->TextSearch($request->input('search'));
}
@@ -116,6 +126,10 @@ class LocationsController extends Controller
$locations->where('locations.manager_id', '=', $request->input('manager_id'));
}
if ($request->filled('company_id')) {
$locations->where('locations.company_id', '=', $request->input('company_id'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -132,6 +146,9 @@ class LocationsController extends Controller
case 'manager':
$locations->OrderManager($order);
break;
case 'company':
$locations->OrderCompany($order);
break;
default:
$locations->orderBy($sort, $order);
break;
@@ -159,6 +176,15 @@ class LocationsController extends Controller
$location->fill($request->all());
$location = $request->handleImages($location);
// Only scope location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
// check if parent is set and has a different company
if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) {
response()->json(Helper::formatStandardApiResponse('error', null, 'different company than parent'));
}
}
if ($location->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new LocationsTransformer)->transformLocation($location), trans('admin/locations/message.create.success')));
}
@@ -176,7 +202,7 @@ class LocationsController extends Controller
public function show($id) : JsonResponse | array
{
$this->authorize('view', Location::class);
$location = Location::with('parent', 'manager', 'children')
$location = Location::with('parent', 'manager', 'children', 'company')
->select([
'locations.id',
'locations.name',
@@ -220,6 +246,19 @@ class LocationsController extends Controller
$location->fill($request->all());
$location = $request->handleImages($location);
if ($request->filled('company_id')) {
// Only scope location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
// check if there are related objects with different company
if (Helper::test_locations_fmcs(false, $id, $location->company_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'error scoped locations'));
}
} else {
$location->company_id = $request->get('company_id');
}
}
if ($location->isValid()) {
$location->save();
@@ -340,6 +379,11 @@ class LocationsController extends Controller
'locations.image',
]);
// Only scope locations if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$locations = Company::scopeCompanyables($locations);
}
$page = 1;
if ($request->filled('page')) {
$page = $request->input('page');
+15 -13
View File
@@ -5,6 +5,8 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ActionlogsTransformer;
use App\Models\Actionlog;
use App\Models\Location;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
@@ -26,22 +28,22 @@ class ReportsController extends Controller
$actionlogs = $actionlogs->TextSearch(e($request->input('search')));
}
if (($request->filled('target_type')) && ($request->filled('target_id'))) {
$actionlogs = $actionlogs->where('target_id', '=', $request->input('target_id'))
->where('target_type', '=', 'App\\Models\\'.ucwords($request->input('target_type')));
// If we want allll the logs
if (($request->filled('id')) && ($request->filled('type'))) {
$actionlogs = $actionlogs->ByTargetOrItem($request->input('id'), $request->input('type'));
}
// We've passed a target
if (($request->filled('target_type')) && ($request->filled('target_id'))) {
$actionlogs = $actionlogs->where('target_id', '=', $request->input('target_id'))
->where('target_type', '=', 'App\\Models\\' . ucwords($request->input('target_type')));
}
// We've passed an item
if (($request->filled('item_type')) && ($request->filled('item_id'))) {
$actionlogs = $actionlogs->where(function($query) use ($request)
{
$query->where('item_id', '=', $request->input('item_id'))
->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')))
->orWhere(function($query) use ($request)
{
$query->where('target_id', '=', $request->input('item_id'))
->where('target_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
});
});
$actionlogs = $actionlogs->where('item_id', '=', $request->input('item_id'))
->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
}
if ($request->filled('action_type')) {
@@ -290,10 +290,12 @@ class StatuslabelsController extends Controller
/**
* Returns a boolean response based on whether the status label
* is one that is deployable.
* is one that is deployable or pending.
*
* This is used by the hardware create/edit view to determine whether
* we should provide a dropdown of users for them to check the asset out to.
* we should provide a dropdown of users for them to check the asset out to,
* and whether we show a warning that the asset will be checked in if it's already
* assigned but the status is changed to one that isn't pending or deployable
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
@@ -301,7 +303,7 @@ class StatuslabelsController extends Controller
public function checkIfDeployable($id) : string
{
$statuslabel = Statuslabel::findOrFail($id);
if ($statuslabel->getStatuslabelType() == 'deployable') {
if (($statuslabel->getStatuslabelType() == 'pending') || ($statuslabel->getStatuslabelType() == 'deployable')) {
return '1';
}
@@ -339,6 +339,7 @@ class UsersController extends Controller
$users = $users->where(function ($query) use ($request) {
$query->SimpleNameSearch($request->get('search'))
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
});
}
@@ -117,7 +117,7 @@ class AssetMaintenancesController extends Controller
) {
$startDate = Carbon::parse($assetMaintenance->start_date);
$completionDate = Carbon::parse($assetMaintenance->completion_date);
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
$assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
// Was the asset maintenance created?
@@ -210,7 +210,7 @@ class AssetMaintenancesController extends Controller
) {
$startDate = Carbon::parse($maintenance->start_date);
$completionDate = Carbon::parse($maintenance->completion_date);
$maintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
$maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true);
}
// Was the asset maintenance created?
@@ -40,8 +40,12 @@ class AssetCheckinController extends Controller
if (!$asset->model) {
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
return view('hardware/checkin', compact('asset'))
$target_option = match ($asset->assigned_type) {
'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset_previous')]),
'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]),
default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]),
};
return view('hardware/checkin', compact('asset', 'target_option'))
->with('item', $asset)
->with('statusLabel_list', Helper::statusLabelList())
->with('backto', $backto)
@@ -75,9 +79,12 @@ class AssetCheckinController extends Controller
$this->authorize('checkin', $asset);
if ($asset->assignedType() == Asset::USER) {
$user = $asset->assignedTo;
}
session()->put('checkedInFrom', $asset->assignedTo->id);
session()->put('checkout_to_type', match ($asset->assigned_type) {
'App\Models\User' => 'user',
'App\Models\Location' => 'location',
'App\Models\Asset' => 'asset',
});
$asset->expected_checkin = null;
$asset->last_checkin = now();
@@ -6,6 +6,7 @@ use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Support\Facades\Log;
@@ -318,7 +319,7 @@ class AssetsController extends Controller
$asset->eol_explicit = false;
} elseif ($request->filled('asset_eol_date')) {
$asset->asset_eol_date = $request->input('asset_eol_date', null);
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
$months = (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true);
if($asset->model->eol) {
if($months != $asset->model->eol > 0) {
$asset->eol_explicit = true;
@@ -340,22 +341,16 @@ class AssetsController extends Controller
$status = Statuslabel::find($request->input('status_id'));
// This is a non-deployable status label - we should check the asset back in.
if (($status && $status->getStatuslabelType() != 'deployable') && ($target = $asset->assignedTo)) {
// This is an archived or undeployable - we should check the asset back in.
// Pending is allowed here
if (($status) && (($status->getStatuslabelType() != 'pending') && ($status->getStatuslabelType() != 'deployable')) && ($target = $asset->assignedTo)) {
$originalValues = $asset->getRawOriginal();
$asset->assigned_to = null;
$asset->assigned_type = null;
$asset->accepted = null;
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update', date('Y-m-d H:i:s'), $originalValues));
event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues));
}
if ($asset->assigned_to == '') {
$asset->location_id = $request->input('rtd_location_id', null);
}
if ($request->filled('image_delete')) {
try {
unlink(public_path().'/uploads/assets/'.$asset->image);
@@ -391,26 +386,26 @@ class AssetsController extends Controller
$asset = $request->handleImages($asset);
// Update custom fields in the database.
// Validation for these fields is handlded through the AssetRequest form request
// FIXME: No idea why this is returning a Builder error on db_column_name.
// Need to investigate and fix. Using static method for now.
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
@@ -866,13 +861,6 @@ class AssetsController extends Controller
return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList());
}
public function audit(Asset $asset)
{
$settings = Setting::getSettings();
$this->authorize('audit', Asset::class);
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
}
public function dueForAudit()
{
@@ -889,19 +877,59 @@ class AssetsController extends Controller
}
public function audit(Asset $asset)
{
$settings = Setting::getSettings();
$this->authorize('audit', Asset::class);
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
return view('hardware/audit')->with('asset', $asset)->with('item', $asset)->with('next_audit_date', $dt)->with('locations_list');
}
public function auditStore(UploadFileRequest $request, Asset $asset)
{
$this->authorize('audit', Asset::class);
$rules = [
'location_id' => 'exists:locations,id|nullable|numeric',
'next_audit_date' => 'date|nullable',
];
$originalValues = $asset->getRawOriginal();
$validator = Validator::make($request->all(), $rules);
$asset->next_audit_date = $request->input('next_audit_date');
$asset->last_audit_date = date('Y-m-d H:i:s');
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
// Check to see if they checked the box to update the physical location,
// not just note it in the audit notes
if ($request->input('update_location') == '1') {
$asset->location_id = $request->input('location_id');
}
// Update custom fields in the database
if (($asset->model) && ($asset->model->fieldset)) {
foreach ($asset->model->fieldset->fields as $field) {
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
}
// Validate custom fields
Validator::make($asset->toArray(), $asset->customFieldValidationRules())->validate();
// Validate the rest of the data before we turn off the event dispatcher
if ($asset->isInvalid()) {
return redirect()->back()->withInput()->withErrors($asset->getErrors());
}
/**
@@ -918,18 +946,11 @@ class AssetsController extends Controller
* which manually invokes Watson Validating to make sure the asset's model is valid.
*
* @see \App\Observers\AssetObserver::updating()
* @see \App\Models\Asset::save()
*/
$asset->unsetEventDispatcher();
$asset->next_audit_date = $request->input('next_audit_date');
$asset->last_audit_date = date('Y-m-d H:i:s');
// Check to see if they checked the box to update the physical location,
// not just note it in the audit notes
if ($request->input('update_location') == '1') {
$asset->location_id = $request->input('location_id');
}
/**
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
@@ -943,7 +964,7 @@ class AssetsController extends Controller
$file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image'));
}
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues);
return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success'));
}
@@ -358,7 +358,11 @@ class BulkAssetsController extends Controller
* to someone/something.
*/
if ($request->filled('status_id')) {
$updated_status = Statuslabel::find($request->input('status_id'));
try {
$updated_status = Statuslabel::findOrFail($request->input('status_id'));
} catch (ModelNotFoundException $e) {
return redirect($bulk_back_url)->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
// We cannot assign a non-deployable status type if the asset is already assigned.
// This could probably be added to a form request.
@@ -366,7 +370,7 @@ class BulkAssetsController extends Controller
// Otherwise we need to make sure the status type is still a deployable one.
if (
($asset->assigned_to == '')
|| ($updated_status->deployable == '1') && ($asset->assetstatus->deployable == '1')
|| ($updated_status->deployable == '1') && ($asset->assetstatus?->deployable == '1')
) {
$this->update_array['status_id'] = $updated_status->id;
}
@@ -566,7 +570,6 @@ class BulkAssetsController extends Controller
*/
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
{
$this->authorize('checkout', Asset::class);
try {
@@ -580,6 +583,8 @@ class BulkAssetsController extends Controller
$asset_ids = array_filter($request->get('selected_assets'));
$assets = Asset::findOrFail($asset_ids);
if (request('checkout_to_type') == 'asset') {
foreach ($asset_ids as $asset_id) {
if ($target->id == $asset_id) {
@@ -599,9 +604,8 @@ class BulkAssetsController extends Controller
}
$errors = [];
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id);
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $assets, $request) { //NOTE: $errors is passsed by reference!
foreach ($assets as $asset) {
$this->authorize('checkout', $asset);
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
@@ -628,7 +632,7 @@ class BulkAssetsController extends Controller
// Redirect to the asset management page with error
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
} catch (ModelNotFoundException $e) {
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $request->input('selected_assets')));
}
}
+9 -43
View File
@@ -206,6 +206,7 @@ class LoginController extends Controller
$user->password = bcrypt($request->input('password'));
}
$user->last_login = \Carbon::now();
$user->email = $ldap_attr['email'];
$user->first_name = $ldap_attr['firstname'];
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
@@ -226,7 +227,7 @@ class LoginController extends Controller
$strip_prefixes = [
// IIS/AD
// https://github.com/snipe/snipe-it/pull/5862
// https://github.com/grokability/snipe-it/pull/5862
'\\',
// Google Cloud IAP
@@ -283,8 +284,11 @@ class LoginController extends Controller
return redirect()->back()->withInput()->withErrors($validator);
}
$this->maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
$this->lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
// Set the custom lockout attempts from the env and sett the custom lockout throttle from the env.
// We divide decayMinutes by 60 here to get minutes, since Laravel changed the default from minutes
// to seconds, and we don't want to break limits on existing systems
$this->maxAttempts = config('auth.passwords.users.throttle.max_attempts');
$this->decayMinutes = (config('auth.passwords.users.throttle.lockout_duration') / 60);
if ($lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
@@ -354,7 +358,7 @@ class LoginController extends Controller
// We wouldn't normally see this page if 2FA isn't enforced via the
// \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled,
// but let's check check anyway in case there's a browser history or back button thing.
// but let's check anyway in case there's a browser history or back button thing.
// While you can access this page directly, enrolling a device when 2FA isn't enforced
// won't cause any harm.
@@ -432,6 +436,7 @@ class LoginController extends Controller
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
$user->two_factor_enrolled = 1;
$user->last_login = \Carbon::now();
$user->saveQuietly();
$request->session()->put('2fa_authed', $user->id);
@@ -519,45 +524,6 @@ class LoginController extends Controller
return 'username';
}
/**
* Redirect the user after determining they are locked out.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendLockoutResponse(Request $request)
{
$seconds = $this->limiter()->availableIn(
$this->throttleKey($request)
);
$minutes = round($seconds / 60);
$message = trans('auth/message.throttle', ['minutes' => $minutes]);
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([$this->username() => $message]);
}
/**
* Override the lockout time and duration
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request)
{
$lockoutTime = config('auth.passwords.users.throttle.lockout_duration');
$maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts');
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request),
$maxLoginAttempts,
$lockoutTime
);
}
public function legacyAuthRedirect()
{
@@ -71,20 +71,28 @@ class BulkAssetModelsController extends Controller
if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id') != 'NC'))) {
$update_array['manufacturer_id'] = $request->input('manufacturer_id');
}
if (($request->filled('category_id') && ($request->input('category_id') != 'NC'))) {
$update_array['category_id'] = $request->input('category_id');
}
if ($request->input('fieldset_id') != 'NC') {
$update_array['fieldset_id'] = $request->input('fieldset_id');
}
if ($request->input('depreciation_id') != 'NC') {
$update_array['depreciation_id'] = $request->input('depreciation_id');
}
if ($request->filled('requestable') != '') {
if ($request->input('requestable') != '') {
$update_array['requestable'] = $request->input('requestable');
}
if ($request->filled('min_amt')) {
$update_array['min_amt'] = $request->input('min_amt');
}
if (count($update_array) > 0) {
AssetModel::whereIn('id', $models_raw_array)->update($update_array);
@@ -106,6 +106,7 @@ class CustomFieldsController extends Controller
"show_in_requestable_list" => $request->get("show_in_requestable_list", 0),
"display_checkin" => $request->get("display_checkin", 0),
"display_checkout" => $request->get("display_checkout", 0),
"display_audit" => $request->get("display_audit", 0),
"created_by" => auth()->id()
]);
@@ -250,6 +251,7 @@ class CustomFieldsController extends Controller
$field->show_in_requestable_list = $request->get("show_in_requestable_list", 0);
$field->display_checkin = $request->get("display_checkin", 0);
$field->display_checkout = $request->get("display_checkout", 0);
$field->display_audit = $request->get("display_audit", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get('custom_format'));
+1 -1
View File
@@ -40,7 +40,7 @@ class DashboardController extends Controller
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
Artisan::call('migrate', ['--force' => true]);
\Artisan::call('passport:install');
Artisan::call('passport:install', ['--no-interaction' => true]);
}
return view('dashboard')->with('asset_stats', $asset_stats)->with('counts', $counts);
@@ -83,6 +83,10 @@ class GroupsController extends Controller
{
$permissions = config('permissions');
$groupPermissions = $group->decodePermissions();
if ((!is_array($groupPermissions)) || (!$groupPermissions)) {
$groupPermissions = [];
}
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'));
}
@@ -38,6 +38,7 @@ class LabelsController extends Controller
$exampleAsset->order_number = '12345';
$exampleAsset->purchase_date = '2023-01-01';
$exampleAsset->status_id = 1;
$exampleAsset->location_id = 1;
$exampleAsset->company = new Company([
'name' => trans('admin/labels/table.example_company'),
@@ -87,6 +87,7 @@ class LicenseCheckinController extends Controller
if($licenseSeat->assigned_to != null){
$return_to = User::find($licenseSeat->assigned_to);
session()->put('checkedInFrom', $return_to->id);
} else {
$return_to = Asset::find($licenseSeat->asset_id);
}
+52 -7
View File
@@ -2,10 +2,13 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
@@ -63,6 +66,7 @@ class LocationsController extends Controller
public function store(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('create', Location::class);
$location = new Location();
$location->name = $request->input('name');
$location->parent_id = $request->input('parent_id', null);
@@ -79,6 +83,18 @@ class LocationsController extends Controller
$location->phone = request('phone');
$location->fax = request('fax');
$location->notes = $request->input('notes');
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
// Only scope the location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
// check if parent is set and has a different company
if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) {
return redirect()->back()->withInput()->withInput()->with('error', 'different company than parent');
}
} else {
$location->company_id = $request->input('company_id');
}
$location = $request->handleImages($location);
@@ -131,6 +147,17 @@ class LocationsController extends Controller
$location->manager_id = $request->input('manager_id');
$location->notes = $request->input('notes');
// Only scope the location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
// check if there are related objects with different company
if (Helper::test_locations_fmcs(false, $location->id, $location->company_id)) {
return redirect()->back()->withInput()->withInput()->with('error', 'error scoped locations');
}
} else {
$location->company_id = $request->input('company_id');
}
$location = $request->handleImages($location);
if ($location->save()) {
@@ -150,6 +177,7 @@ class LocationsController extends Controller
public function destroy($locationId) : RedirectResponse
{
$this->authorize('delete', Location::class);
if (is_null($location = Location::find($locationId))) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -186,6 +214,8 @@ class LocationsController extends Controller
*/
public function show(Location $location) : View | RedirectResponse
{
$this->authorize('view', Location::class);
$location = Location::withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
->withCount('rtd_assets as rtd_assets_count')
@@ -203,20 +233,24 @@ class LocationsController extends Controller
public function print_assigned($id) : View | RedirectResponse
{
$this->authorize('view', Location::class);
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
$manager = User::where('id', $location->manager_id)->first();
$company = Company::where('id', $location->company_id)->first();
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
$assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get();
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
return view('locations/print')
->with('assets', $assets)
->with('users',$users)
->with('location', $location)
->with('parent', $parent)
->with('manager', $manager)
->with('company', $company);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -285,13 +319,20 @@ class LocationsController extends Controller
}
public function print_all_assigned($id) : View | RedirectResponse
{
$this->authorize('view', Location::class);
if ($location = Location::where('id', $id)->first()) {
$parent = Location::where('id', $location->parent_id)->first();
$manager = User::where('id', $location->manager_id)->first();
$company = Company::where('id', $location->company_id)->first();
$users = User::where('location_id', $id)->with('company', 'department', 'location')->get();
$assets = Asset::where('location_id', $id)->with('model', 'model.category')->get();
return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager);
return view('locations/print')
->with('assets', $assets)
->with('users',$users)
->with('location', $location)
->with('parent', $parent)
->with('manager', $manager)
->with('company', $company);
}
return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist'));
}
@@ -305,6 +346,8 @@ class LocationsController extends Controller
*/
public function postBulkDelete(Request $request) : View | RedirectResponse
{
$this->authorize('update', Location::class);
$locations_raw_array = $request->input('ids');
// Make sure some IDs have been selected
@@ -338,6 +381,8 @@ class LocationsController extends Controller
*/
public function postBulkDeleteStore(Request $request) : RedirectResponse
{
$this->authorize('delete', Location::class);
$locations_raw_array = $request->input('ids');
if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) {
+5 -4
View File
@@ -41,10 +41,11 @@ class ModalController extends Controller
$view = view("modals.${type}");
if ($type == "statuslabel") {
$view->with('statuslabel_types', Helper::statusTypeList());
}
if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) {
$view->with('kitId', $itemId);
$view->with('statuslabel_types', Helper::statusTypeList());
}
if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) {
$view->with('kitId', $itemId);
}
return $view;
}
+2 -2
View File
@@ -53,7 +53,7 @@ class ProfileController extends Controller
$user->enable_confetti = $request->input('enable_confetti', false);
if (! config('app.lock_passwords')) {
$user->locale = $request->input('locale', 'en-US');
$user->locale = $request->input('locale');
}
if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled == '1') && (! config('app.lock_passwords')))) {
@@ -136,7 +136,7 @@ class ProfileController extends Controller
}
// This checks to make sure that the user's password isn't the same as their username,
// email address, first name or last name (see https://github.com/snipe/snipe-it/issues/8661)
// email address, first name or last name (see https://github.com/grokability/snipe-it/issues/8661)
// While this is handled via SaveUserRequest form request in other places, we have to do this manually
// here because we don't have the username, etc form fields available in the profile password change
// form.
+33 -38
View File
@@ -243,7 +243,7 @@ class ReportsController extends Controller
$header = [
trans('general.date'),
trans('general.admin'),
trans('general.created_by'),
trans('general.action'),
trans('general.type'),
trans('general.item'),
@@ -266,7 +266,7 @@ class ReportsController extends Controller
$actionlogs = Actionlog::with('item', 'user', 'target', 'location', 'adminuser')
->orderBy('created_at', 'DESC')
->chunk(20, function ($actionlogs) use ($handle) {
->chunk(500, function ($actionlogs) use ($handle) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
Log::debug('Walking results: '.$executionTime);
$count = 0;
@@ -493,6 +493,17 @@ class ReportsController extends Controller
$header[] = trans('admin/hardware/table.eol');
}
if ($request->filled('warranty')) {
$header[] = trans('admin/hardware/form.warranty');
$header[] = trans('admin/hardware/form.warranty_expires');
}
if ($request->filled('depreciation')) {
$header[] = trans('admin/hardware/table.book_value');
$header[] = trans('admin/hardware/table.diff');
$header[] = trans('admin/hardware/form.fully_depreciated');
}
if ($request->filled('order')) {
$header[] = trans('admin/hardware/form.order');
}
@@ -579,17 +590,6 @@ class ReportsController extends Controller
$header[] = trans('general.status');
}
if ($request->filled('warranty')) {
$header[] = trans('admin/hardware/form.warranty');
$header[] = trans('admin/hardware/form.warranty_expires');
}
if ($request->filled('depreciation')) {
$header[] = trans('admin/hardware/table.book_value');
$header[] = trans('admin/hardware/table.diff');
$header[] = trans('admin/hardware/form.fully_depreciated');
}
if ($request->filled('checkout_date')) {
$header[] = trans('admin/hardware/table.checkout_date');
}
@@ -748,7 +748,7 @@ class ReportsController extends Controller
}
Log::debug($assets->toSql());
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
$assets->orderBy('assets.id', 'ASC')->chunk(500, function ($assets) use ($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
Log::debug('Walking results: '.$executionTime);
@@ -805,6 +805,19 @@ class ReportsController extends Controller
$row[] = ($asset->purchase_date != '') ? $asset->asset_eol_date : '';
}
if ($request->filled('warranty')) {
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
$row[] = $asset->present()->warranty_expires();
}
if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
$row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
}
if ($request->filled('order')) {
$row[] = ($asset->order_number) ? $asset->order_number : '';
}
@@ -938,19 +951,6 @@ class ReportsController extends Controller
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}
if ($request->filled('warranty')) {
$row[] = ($asset->warranty_months) ? $asset->warranty_months : '';
$row[] = $asset->present()->warranty_expires();
}
if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
$row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : '';
}
if ($request->filled('checkout_date')) {
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
}
@@ -1081,10 +1081,10 @@ class ReportsController extends Controller
$row[] = e($assetMaintenance->start_date);
$row[] = e($assetMaintenance->completion_date);
if (is_null($assetMaintenance->asset_maintenance_time)) {
$improvementTime = intval(Carbon::now()
->diffInDays(Carbon::parse($assetMaintenance->start_date)));
$improvementTime = (int) Carbon::now()
->diffInDays(Carbon::parse($assetMaintenance->start_date), true);
} else {
$improvementTime = intval($assetMaintenance->asset_maintenance_time);
$improvementTime = (int) $assetMaintenance->asset_maintenance_time;
}
$row[] = $improvementTime;
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
@@ -1175,18 +1175,13 @@ class ReportsController extends Controller
}
$email = $assetItem->assignedTo?->email;
$locale = $assetItem->assignedTo?->locale;
// Only send notification if assigned
if ($locale && $email) {
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note))->locale($locale));
} elseif ($email) {
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)));
}
if ($email == ''){
if (is_null($email) || $email === '') {
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
}
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale));
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
}
+27 -2
View File
@@ -256,7 +256,7 @@ class SettingsController extends Controller
Artisan::call('migrate', ['--force' => true]);
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {
Artisan::call('migrate', ['--path' => 'vendor/laravel/passport/database/migrations', '--force' => true]);
Artisan::call('passport:install');
Artisan::call('passport:install', ['--no-interaction' => true]);
}
return view('setup/migrate')
@@ -314,7 +314,23 @@ class SettingsController extends Controller
$setting->modellist_displays = implode(',', $request->input('show_in_model_list'));
}
$old_locations_fmcs = $setting->scope_locations_fmcs;
$setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0');
$setting->scope_locations_fmcs = $request->input('scope_locations_fmcs', '0');
// Backward compatibility for locations makes no sense without FullMultipleCompanySupport
if (!$setting->full_multiple_companies_support) {
$setting->scope_locations_fmcs = '0';
}
// check for inconsistencies when activating scoped locations
if ($old_locations_fmcs == '0' && $setting->scope_locations_fmcs == '1') {
$mismatched = Helper::test_locations_fmcs(false);
if (count($mismatched) != 0) {
return redirect()->back()->withInput()->with('error', trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)).' '.trans('admin/settings/message.location_scoping.not_saved'));
}
}
$setting->unique_serial = $request->input('unique_serial', '0');
$setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0');
$setting->show_images_in_email = $request->input('show_images_in_email', '0');
@@ -428,6 +444,13 @@ class SettingsController extends Controller
$setting->label_logo = null;
}
// Acceptance PDF upload
$setting = $request->handleImages($setting, 600, 'acceptance_pdf_logo', '', 'acceptance_pdf_logo');
if ('1' == $request->input('clear_acceptance_pdf_logo')) {
$setting = $request->deleteExistingImage($setting, '', 'acceptance_pdf_logo');
$setting->acceptance_pdf_logo = null;
}
// Favicon upload
$setting = $request->handleImages($setting, 100, 'favicon', '', 'favicon');
if ('1' == $request->input('clear_favicon')) {
@@ -435,6 +458,7 @@ class SettingsController extends Controller
$setting->favicon = null;
}
// Default avatar upload
$setting = $request->handleImages($setting, 500, 'default_avatar', 'avatars', 'default_avatar');
if ($request->input('clear_default_avatar') == '1') {
@@ -850,7 +874,8 @@ class SettingsController extends Controller
$setting->ldap_fname_field = $request->input('ldap_fname_field');
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
$setting->ldap_version = $request->input('ldap_version', 3);
$setting->ldap_active_flag = $request->input('ldap_active_flag');
$setting->ldap_active_flag = $request->input('ldap_active_flag', 0);
$setting->ldap_invert_active_flag = $request->input('ldap_invert_active_flag', 0);
$setting->ldap_emp_num = $request->input('ldap_emp_num');
$setting->ldap_email = $request->input('ldap_email');
$setting->ldap_manager = $request->input('ldap_manager');
@@ -360,7 +360,7 @@ class BulkUsersController extends Controller
$logAction->item_type = Accessory::class;
$logAction->target_id = $accessoryUserRow->assigned_to;
$logAction->target_type = User::class;
$logAction->created_at = auth()->id();
$logAction->created_by = auth()->id();
$logAction->note = 'Bulk checkin items';
$logAction->logaction('checkin from');
}
@@ -374,7 +374,7 @@ class BulkUsersController extends Controller
$logAction->item_type = Consumable::class;
$logAction->target_id = $consumableUserRow->assigned_to;
$logAction->target_type = User::class;
$logAction->created_at = auth()->id();
$logAction->created_by = auth()->id();
$logAction->note = 'Bulk checkin items';
$logAction->logaction('checkin from');
}
@@ -26,12 +26,14 @@ class UserFilesController extends Controller
{
$this->authorize('update', $user);
$files = $request->file('file');
$errors = 0;
if (is_null($files)) {
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
foreach ($files as $file) {
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
$file_name = $request->handleFile('private_uploads/users/', 'user-' . $user->id, $file);
//Log the uploaded file to the log
$logAction = new Actionlog();
@@ -39,18 +41,25 @@ class UserFilesController extends Controller
$logAction->item_type = User::class;
$logAction->created_by = auth()->id();
$logAction->note = $request->input('notes');
$logAction->target_id = null;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->target_type = User::class;
$logAction->target_id = $user->id;
$logAction->action_date = date("Y-m-d H:i:s");
$logAction->filename = $file_name;
$logAction->action_source = 'gui';
$logAction->user_agent = request()->header('User-Agent');
$logAction->remote_ip = request()->ip();
$logAction->action_type = 'uploaded';
if (! $logAction->save()) {
return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500);
if (!$logAction->save()) {
$errors++;
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
}
if ($errors > 0) {
return redirect()->back()->withFragment('files')->with('warning', 'Some files could not be uploaded');
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
}
+15 -7
View File
@@ -198,10 +198,9 @@ class UsersController extends Controller
$userPermissions = Helper::selectedPermissionsArray($permissions, $user->permissions);
$permissions = $this->filterDisplayable($permissions);
return view('users/edit', compact('user', 'groups', 'userGroups', 'permissions', 'userPermissions'));
return view('users/edit', compact('user', 'groups', 'userGroups', 'permissions', 'userPermissions'))->with('item', $user);
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
}
/**
@@ -396,13 +395,22 @@ class UsersController extends Controller
// Make sure the user can view users at all
$this->authorize('view', User::class);
$user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id);
$user = User::with([
'consumables',
'accessories',
'licenses',
'userloc',
])
->withTrashed()
->find($user->id);
// Make sure they can view this particular user
$this->authorize('view', $user);
$userlog = $user->userlog->load('item');
return view('users/view', compact('user', 'userlog'))->with('settings', Setting::getSettings());
return view('users/view', [
'user' => $user,
'settings' => Setting::getSettings(),
]);
}
@@ -456,10 +464,10 @@ class UsersController extends Controller
->with('user', $user)
->with('groups', Group::pluck('name', 'id'))
->with('userGroups', $userGroups)
->with('clone_user', $user_to_clone);
->with('clone_user', $user_to_clone)
->with('item', $user);
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
}
+28 -55
View File
@@ -2,18 +2,21 @@
namespace App\Http\Controllers;
use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
use App\Exceptions\AssetNotRequestable;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\RequestAssetCancelation;
use App\Notifications\RequestAssetNotification;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use Log;
use Exception;
/**
* This controller handles all actions related to the ability for users
@@ -81,7 +84,7 @@ class ViewAssetsController extends Controller
return view('account/requestable-assets', compact('assets', 'models'));
}
public function getRequestItem(Request $request, $itemType, $itemId = null, $cancel_by_admin = false, $requestingUser = null) : RedirectResponse
public function getRequestItem(Request $request, $itemType, $itemId = null, $cancel_by_admin = false, $requestingUser = null): RedirectResponse
{
$item = null;
$fullItemType = 'App\\Models\\'.studly_case($itemType);
@@ -144,63 +147,33 @@ class ViewAssetsController extends Controller
* Process a specific requested asset
* @param null $assetId
*/
public function getRequestAsset($assetId = null) : RedirectResponse
public function store(Asset $asset): RedirectResponse
{
$user = auth()->user();
// Check if the asset exists and is requestable
if (is_null($asset = Asset::RequestableAssets()->find($assetId))) {
return redirect()->route('requestable-assets')
->with('error', trans('admin/hardware/message.does_not_exist_or_not_requestable'));
}
if (! Company::isCurrentUserHasAccess($asset)) {
return redirect()->route('requestable-assets')
->with('error', trans('general.insufficient_permissions'));
}
$data['item'] = $asset;
$data['target'] = auth()->user();
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
if ($user->location_id) {
$logaction->location_id = $user->location_id;
}
$logaction->target_id = $data['user_id'] = auth()->id();
$logaction->target_type = User::class;
// If it's already requested, cancel the request.
if ($asset->isRequestedBy(auth()->user())) {
$asset->cancelRequest();
$asset->decrement('requests_counter', 1);
$logaction->logaction('request canceled');
try {
$settings->notify(new RequestAssetCancelation($data));
} catch (\Exception $e) {
Log::warning($e);
}
return redirect()->route('requestable-assets')
->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
}
$logaction->logaction('requested');
$asset->request();
$asset->increment('requests_counter', 1);
try {
$settings->notify(new RequestAssetNotification($data));
} catch (\Exception $e) {
Log::warning($e);
CreateCheckoutRequestAction::run($asset, auth()->user());
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success'));
} catch (AssetNotRequestable $e) {
return redirect()->back()->with('error', 'Asset is not requestable');
} catch (AuthorizationException $e) {
return redirect()->back()->with('error', trans('admin/hardware/message.requests.error'));
} catch (Exception $e) {
report($e);
return redirect()->back()->with('error', trans('general.something_went_wrong'));
}
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success'));
}
public function destroy(Asset $asset): RedirectResponse
{
try {
CancelCheckoutRequestAction::run($asset, auth()->user());
return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
} catch (Exception $e) {
report($e);
return redirect()->back()->with('error', trans('general.something_went_wrong'));
}
}
public function getRequestedAssets() : View
{
return view('account/requested');
+2 -2
View File
@@ -33,9 +33,9 @@ class SaveUserRequest extends FormRequest
public function rules()
{
$rules = [
'department_id' => 'nullable|exists:departments,id',
'department_id' => 'nullable|integer|exists:departments,id',
'manager_id' => 'nullable|exists:users,id',
'company_id' => ['nullable','exists:companies,id']
'company_id' => ['nullable', 'integer', 'exists:companies,id']
];
switch ($this->method()) {
+6
View File
@@ -29,6 +29,12 @@ class StoreLabelSettings extends FormRequest
return $label->getName();
})->values()->toArray();
if (empty($this->input('label2_template'))) {
$this->merge([
'label2_template' => 'DefaultLabel',
]);
}
return [
'labels_per_page' => 'numeric',
'labels_width' => 'numeric',
-2
View File
@@ -27,8 +27,6 @@ class StoreLdapSettings extends FormRequest
'ldap_auth_filter_query' => 'not_in:uid=samaccountname|required_if:ldap_enabled,1',
'ldap_filter' => 'nullable|regex:"^[^(]"|required_if:ldap_enabled,1',
'ldap_server' => 'nullable|required_if:ldap_enabled,1|starts_with:ldap://,ldaps://',
'ldap_uname' => 'nullable|required_if:ldap_enabled,1',
'ldap_pword' => 'nullable|required_if:ldap_enabled,1',
'ldap_basedn' => 'nullable|required_if:ldap_enabled,1',
'ldap_fname_field' => 'nullable|required_if:ldap_enabled,1',
'custom_forgot_pass_url' => 'nullable|url',
@@ -26,7 +26,7 @@ class StoreNotificationSettings extends FormRequest
return [
'alert_email' => 'email_array|nullable',
'admin_cc_email' => 'email_array|nullable',
'alert_threshold' => 'numeric|nullable|gt:0',
'alert_threshold' => 'numeric|nullable',
'alert_interval' => 'numeric|nullable|gt:0',
'audit_warning_days' => 'numeric|nullable',
'due_checkin_days' => 'numeric|nullable|gt:0',
@@ -10,19 +10,36 @@ trait MayContainCustomFields
// this gets called automatically on a form request
public function withValidator($validator)
{
// find the model
if ($this->method() == 'POST') {
$asset_model = AssetModel::find($this->model_id);
}
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
$asset_model = $this->asset->model;
// In case the model is being changed via form
if (request()->has('model_id')!='') {
$asset_model = AssetModel::find(request()->input('model_id'));
// or if we have it available to route-model-binding
} elseif ((request()->route('asset') && (request()->route('asset')->model_id))) {
$asset_model = AssetModel::find(request()->route('asset')->model_id);
} else {
if ($this->method() == 'POST') {
$asset_model = AssetModel::find($this->model_id);
}
if ($this->method() == 'PATCH' || $this->method() == 'PUT') {
$asset_model = $this->asset->model;
}
}
// collect the custom fields in the request
$validator->after(function ($validator) use ($asset_model) {
$request_fields = $this->collect()->keys()->filter(function ($attributes) {
return str_starts_with($attributes, '_snipeit_');
});
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
// if there are custom fields, find the ones that don't exist on the model's fieldset and add an error to the validator's error bag
if (count($request_fields) > 0 && $validator->errors()->isEmpty()) {
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
->each(function ($request_field_name) use ($request_fields, $validator) {
@@ -37,8 +37,10 @@ class AccessoriesTransformer
'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null,
'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost),
'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null,
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
'remaining_qty' => (int) ($accessory->qty - $accessory->checkouts_count),
'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, // Legacy - should phase out - replaced by below, for the bootstrap table formatter
'min_amt' => ($accessory->min_amt) ? (int) $accessory->min_amt : null,
'remaining_qty' => (int) ($accessory->qty - $accessory->checkouts_count), // Legacy - should phase out - replaced by below, for the bootstrap table formatter
'remaining' => (int) ($accessory->qty - $accessory->checkouts_count),
'checkouts_count' => $accessory->checkouts_count,
'created_by' => ($accessory->adminuser) ? [
'id' => (int) $accessory->adminuser->id,
@@ -46,8 +46,9 @@ class AssetModelsTransformer
'name'=> e($assetmodel->manufacturer->name),
] : null,
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
'model_number' => e($assetmodel->model_number),
'model_number' => ($assetmodel->model_number ? e($assetmodel->model_number): null),
'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null,
'remaining' => (int) ($assetmodel->assets_count - $assetmodel->min_amt),
'depreciation' => ($assetmodel->depreciation) ? [
'id' => (int) $assetmodel->depreciation->id,
'name'=> e($assetmodel->depreciation->name),
+25 -23
View File
@@ -42,7 +42,7 @@ class AssetsTransformer
'requestable' => ($asset->requestable ? true : false),
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null,
'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true) . ' months' : null,
'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null,
'status_label' => ($asset->assetstatus) ? [
'id' => (int) $asset->assetstatus->id,
@@ -302,30 +302,32 @@ class AssetsTransformer
public function transformCheckedoutAccessory(AccessoryCheckout $accessory_checkout)
{
if ($accessory_checkout->accessory) {
$array = [
'id' => $accessory_checkout->id,
'accessory' => [
'id' => $accessory_checkout->accessory->id,
'name' => $accessory_checkout->accessory->name,
],
'assigned_to' => $accessory_checkout->assigned_to,
'image' => ($accessory_checkout->accessory->image) ? Storage::disk('public')->url('accessories/' . e($accessory_checkout->accessory->image)) : null,
'note' => $accessory_checkout->note ? e($accessory_checkout->note) : null,
'created_by' => $accessory_checkout->adminuser ? [
'id' => (int)$accessory_checkout->adminuser->id,
'name' => e($accessory_checkout->adminuser->present()->fullName),
] : null,
'created_at' => Helper::getFormattedDateObject($accessory_checkout->created_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($accessory_checkout->deleted_at, 'datetime'),
];
$array = [
'id' => $accessory_checkout->id,
'accessory' => [
'id' => $accessory_checkout->accessory->id,
'name' => $accessory_checkout->accessory->name,
],
'assigned_to' => $accessory_checkout->assigned_to,
'image' => ($accessory_checkout->accessory->image) ? Storage::disk('public')->url('accessories/'.e($accessory_checkout->accessory->image)) : null,
'note' => $accessory_checkout->note ? e($accessory_checkout->note) : null,
'created_by' => $accessory_checkout->adminuser ? [
'id' => (int) $accessory_checkout->adminuser->id,
'name'=> e($accessory_checkout->adminuser->present()->fullName),
]: null,
'created_at' => Helper::getFormattedDateObject($accessory_checkout->created_at, 'datetime'),
];
$permissions_array['available_actions'] = [
'checkout' => false,
'checkin' => Gate::allows('checkin', Accessory::class),
];
$permissions_array['available_actions'] = [
'checkout' => false,
'checkin' => Gate::allows('checkin', Accessory::class),
];
$array += $permissions_array;
return $array;
$array += $permissions_array;
return $array;
}
}
}
@@ -50,6 +50,9 @@ class CustomFieldsTransformer
'display_in_user_view' => ($field->display_in_user_view =='1') ? true : false,
'auto_add_to_fieldsets' => ($field->auto_add_to_fieldsets == '1') ? true : false,
'show_in_listview' => ($field->show_in_listview == '1') ? true : false,
'display_checkin' => ($field->display_checkin == '1') ? true : false,
'display_checkout' => ($field->display_checkout == '1') ? true : false,
'display_audit' => ($field->display_audit == '1') ? true : false,
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
];
@@ -38,6 +38,7 @@ class LicensesTransformer
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
'seats' => (int) $license->seats,
'free_seats_count' => (int) $license->free_seats_count,
'remaining' => (int) $license->free_seats_count,
'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null,
'license_name' => ($license->license_name) ? e($license->license_name) : null,
'license_email' => ($license->license_email) ? e($license->license_email) : null,
+19 -6
View File
@@ -63,6 +63,10 @@ class LocationsTransformer
'name'=> e($location->parent->name),
] : null,
'manager' => ($location->manager) ? (new UsersTransformer)->transformUser($location->manager) : null,
'company' => ($location->company) ? [
'id' => (int) $location->company->id,
'name'=> e($location->company->name)
] : null,
'children' => $children_arr,
];
@@ -101,11 +105,8 @@ class LocationsTransformer
$array = [
'id' => $accessory_checkout->id,
'assigned_to' => $accessory_checkout->assigned_to,
'accessory' => [
'id' => $accessory_checkout->accessory->id,
'name' => $accessory_checkout->accessory->name,
],
'image' => ($accessory_checkout->accessory->image) ? Storage::disk('public')->url('accessories/'.e($accessory_checkout->accessory->image)) : null,
'accessory' => $this->transformAccessory($accessory_checkout->accessory),
'image' => ($accessory_checkout?->accessory?->image) ? Storage::disk('public')->url('accessories/' . e($accessory_checkout->accessory->image)) : null,
'note' => $accessory_checkout->note ? e($accessory_checkout->note) : null,
'created_by' => $accessory_checkout->adminuser ? [
'id' => (int) $accessory_checkout->adminuser->id,
@@ -153,4 +154,16 @@ class LocationsTransformer
return $array;
}
}
}
private function transformAccessory(?Accessory $accessory): ?array
{
if ($accessory) {
return [
'id' => $accessory->id,
'name' => $accessory->name,
];
}
return null;
}
}
@@ -0,0 +1,56 @@
<?php
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage;
class UploadedFilesTransformer
{
public function transformFiles(Collection $files, $total)
{
$array = [];
foreach ($files as $file) {
$array[] = self::transformFile($file);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformFile(Actionlog $file)
{
$snipeModel = $file->item_type;
// This will be used later as we extend out this transformer to handle more types of uploads
if ($file->item_type == Asset::class) {
$file_url = route('show/assetfile', [$file->item_id, $file->id]);
}
$array = [
'id' => (int) $file->id,
'filename' => e($file->filename),
'url' => $file_url,
'created_by' => ($file->adminuser) ? [
'id' => (int) $file->adminuser->id,
'name'=> e($file->adminuser->present()->fullName),
] : null,
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($file->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),
];
$permissions_array['available_actions'] = [
'delete' => (Gate::allows('update', $snipeModel) && ($file->deleted_at == '')),
];
$array += $permissions_array;
return $array;
}
}
+15 -3
View File
@@ -16,10 +16,22 @@ class AssetImporter extends ItemImporter
{
parent::__construct($filename);
$this->defaultStatusLabelId = Statuslabel::first()->id;
$this->defaultStatusLabelId = Statuslabel::first()?->id;
if (!is_null(Statuslabel::deployable()->first())) {
$this->defaultStatusLabelId = Statuslabel::deployable()->first()->id;
$this->defaultStatusLabelId = Statuslabel::deployable()->first()?->id;
}
if (is_null($this->defaultStatusLabelId)) {
$defaultLabel = Statuslabel::create([
'name' => 'Default Status',
'deployable' => 0,
'pending' => 1,
'archived' => 0,
'notes' => 'Default status label created by AssetImporter',
]);
$this->defaultStatusLabelId = $defaultLabel->id;
}
}
+2 -4
View File
@@ -28,9 +28,9 @@ class ConsumableImporter extends ItemImporter
{
$consumable = Consumable::where('name', trim($this->item['name']))->first();
if ($consumable) {
if (! $this->updating) {
$this->log('A matching Consumable '.$this->item['name'].' already exists. ');
return;
}
$this->log('Updating Consumable');
@@ -39,12 +39,10 @@ class ConsumableImporter extends ItemImporter
return;
}
$this->log('No matching consumable, creating one');
$consumable = new Consumable();
$consumable->created_by = auth()->id();
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
$this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
$consumable->fill($this->sanitizeItemForStoring($consumable));
// This sets an attribute on the Loggable trait for the action log
+14 -18
View File
@@ -24,6 +24,12 @@ class ItemImporter extends Importer
protected function handle($row)
{
/**
* This section adds the most common fields into the $item array so we don't have to manually add them to
* things like accessories, consumables, etc.
*/
// Need to reset this between iterations or we'll have stale data.
$this->item = [];
@@ -73,29 +79,20 @@ class ItemImporter extends Importer
$this->item['notes'] = $this->findCsvMatch($row, 'notes');
$this->item['order_number'] = $this->findCsvMatch($row, 'order_number');
$this->item['purchase_cost'] = $this->findCsvMatch($row, 'purchase_cost');
$this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number'));
$this->item['min_amt'] = $this->findCsvMatch($row, 'min_amt');
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
$this->item['created_by'] = auth()->id();
$this->item['serial'] = $this->findCsvMatch($row, 'serial');
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_no'));
$this->item['purchase_date'] = null;
if ($this->findCsvMatch($row, 'purchase_date') != '') {
$this->item['purchase_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'purchase_date')));
}
// $this->item['asset_eol_date'] = null;
// if ($this->findCsvMatch($row, 'asset_eol_date') != '') {
// $csvMatch = $this->findCsvMatch($row, 'asset_eol_date');
// \Log::warning('EOL Date for $csvMatch is '.$csvMatch);
// try {
// $this->item['asset_eol_date'] = CarbonImmutable::parse($csvMatch)->format('Y-m-d');
// } catch (\Exception $e) {
// Log::info($e->getMessage());
// $this->log('Unable to parse date: '.$csvMatch);
// }
// }
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');
$this->item['requestable'] = $this->findCsvMatch($row, 'requestable');
$this->item['created_by'] = auth()->id();
$this->item['serial'] = $this->findCsvMatch($row, 'serial');
// NO need to call this method if we're running the user import.
// TODO: Merge these methods.
$this->item['checkout_class'] = $this->findCsvMatch($row, 'checkout_class');
@@ -512,7 +509,6 @@ class ItemImporter extends Importer
if ($supplier->save()) {
$this->log('Supplier '.$item_supplier.' was created');
return $supplier->id;
}
$this->logError($supplier, 'Supplier');
+1
View File
@@ -68,6 +68,7 @@ class LicenseImporter extends ItemImporter
$this->item['order_number'] = trim($this->findCsvMatch($row, 'order_number'));
$this->item['reassignable'] = trim($this->findCsvMatch($row, 'reassignable'));
$this->item['manufacturer'] = $this->createOrFetchManufacturer(trim($this->findCsvMatch($row, 'manufacturer')));
$this->item['min_amt'] = trim($this->findCsvMatch($row, 'min_amt'));
if($this->item['reassignable'] == "")
{
+73 -37
View File
@@ -63,11 +63,9 @@ class CheckoutableListener
}
$ccEmails = array_filter($adminCcEmailsArray);
$mailable = $this->getCheckoutMailType($event, $acceptance);
$notifiable = $this->getNotifiables($event);
$notifiable = $this->getNotifiableUsers($event);
if ($event->checkedOutTo->locale) {
$mailable->locale($event->checkedOutTo->locale);
}
// Send email notifications
try {
/**
@@ -75,24 +73,33 @@ class CheckoutableListener
* 1. The asset requires acceptance
* 2. The item has a EULA
* 3. The item should send an email at check-in/check-out
* 4. If the admin CC email is set, even if the item being checked out doesn't have an email address (location, etc)
*/
if ($event->checkoutable->requireAcceptance() || $event->checkoutable->getEula() ||
$this->checkoutableShouldSendEmail($event)) {
Log::info('Sending checkout email, Locale: ' . ($event->checkedOutTo->locale ?? 'default'));
if (!empty($notifiable)) {
Mail::to($notifiable)->cc($ccEmails)->send($mailable);
} elseif (!empty($ccEmails)) {
Mail::cc($ccEmails)->send($mailable);
// Send a checkout email to the admin CC addresses, even if the target has no email
if (!empty($ccEmails)) {
Mail::to($ccEmails)->send($mailable);
Log::info('Checkout Mail sent to CC addresses');
}
Log::info('Checkout Mail sent.');
// Send a checkout email to the target if it has an email
if (!empty($notifiable->email)) {
Mail::to($notifiable)->send($mailable);
Log::info('Checkout Mail sent to checkout target');
}
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
} catch (Exception $e) {
Log::debug("Exception caught during checkout email: " . $e->getMessage());
}
// Send Webhook notification
// Send notification
try {
if ($this->shouldSendWebhookNotification()) {
if ($this->newMicrosoftTeamsWebhookEnabled()) {
@@ -115,7 +122,7 @@ class CheckoutableListener
}
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) .trans('admin/settings/message.webhook.webhook_fail') );
} catch (Exception $e) {
Log::error(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
@@ -161,10 +168,8 @@ class CheckoutableListener
}
$ccEmails = array_filter($adminCcEmailsArray);
$mailable = $this->getCheckinMailType($event);
$notifiable = $this->getNotifiables($event);
if ($event->checkedOutTo?->locale) {
$mailable->locale($event->checkedOutTo->locale);
}
$notifiable = $this->getNotifiableUsers($event);
// Send email notifications
try {
/**
@@ -172,17 +177,20 @@ class CheckoutableListener
* 1. The asset requires acceptance
* 2. The item has a EULA
* 3. The item should send an email at check-in/check-out
* 4. If the admin CC email is set, even if the item being checked in doesn't have an email address (location, etc)
*/
if ($event->checkoutable->requireAcceptance() || $event->checkoutable->getEula() ||
$this->checkoutableShouldSendEmail($event)) {
Log::info('Sending checkin email, Locale: ' . ($event->checkedOutTo->locale ?? 'default'));
if (!empty($notifiable)) {
Mail::to($notifiable)->cc($ccEmails)->send($mailable);
} elseif (!empty($ccEmails)){
Mail::cc($ccEmails)->send($mailable);
}
Log::info('Checkin Mail sent.');
}
// Send a checkout email to the admin's CC addresses, even if the target has no email
if (!empty($ccEmails)) {
Mail::to($ccEmails)->send($mailable);
Log::info('Checkin Mail sent to CC addresses');
}
// Send a checkout email to the target if it has an email
if (!empty($notifiable->email)) {
Mail::to($notifiable)->send($mailable);
Log::info('Checkin Mail sent to checkout target');
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkin email: " . $e->getMessage());
} catch (Exception $e) {
@@ -211,7 +219,7 @@ class CheckoutableListener
return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail'));
}
} catch (Exception $e) {
Log::error(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [
'error' => $e->getMessage(),
'webhook_endpoint' => Setting::getSettings()->webhook_endpoint,
'event' => $event,
@@ -324,17 +332,29 @@ class CheckoutableListener
return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
}
private function getNotifiables($event){
if($event->checkedOutTo instanceof Asset){
/**
* This gets the recipient objects based on the type of checkoutable.
* The 'name' property for users is set in the boot method in the User model.
*
* @see \App\Models\User::boot()
* @param $event
* @return mixed
*/
private function getNotifiableUsers($event){
// If it's assigned to an asset, get that asset's assignedTo object
if ($event->checkedOutTo instanceof Asset){
$event->checkedOutTo->load('assignedTo');
return $event->checkedOutTo->assignedto?->email ?? '';
}
else if($event->checkedOutTo instanceof Location) {
return $event->checkedOutTo->manager?->email ?? '';
}
else{
return $event->checkedOutTo?->email ?? '';
return $event->checkedOutTo->assignedto;
// If it's assigned to a location, get that location's manager object
} elseif ($event->checkedOutTo instanceof Location) {
return $event->checkedOutTo->manager;
// Otherwise just return the assigned to object
} else {
return $event->checkedOutTo;
}
}
private function webhookSelected(){
@@ -365,9 +385,25 @@ class CheckoutableListener
private function shouldNotSendAnyNotifications($checkoutable): bool
{
return in_array(get_class($checkoutable), $this->skipNotificationsFor);
if(in_array(get_class($checkoutable), $this->skipNotificationsFor)) {
return true;
}
//runs a check if the category wants to send checkin/checkout emails to users
$category = match (true) {
$checkoutable instanceof Asset => $checkoutable->model->category,
$checkoutable instanceof Accessory,
$checkoutable instanceof Consumable => $checkoutable->category,
$checkoutable instanceof LicenseSeat => $checkoutable->license->category,
default => null,
};
if (!$category->checkin_email) {
return true;
}
return false;
}
private function shouldSendWebhookNotification(): bool
{
return Setting::getSettings() && Setting::getSettings()->webhook_endpoint;
+4
View File
@@ -65,6 +65,8 @@ class LogListener
$logaction->filename = $event->acceptance->stored_eula_file;
$logaction->note = $event->acceptance->note;
$logaction->action_type = 'accepted';
$logaction->action_date = $event->acceptance->accepted_at;
$logaction->created_by = auth()->user()->id;
// TODO: log the actual license seat that was checked out
if ($event->acceptance->checkoutable instanceof LicenseSeat) {
@@ -82,6 +84,8 @@ class LogListener
$logaction->accept_signature = $event->acceptance->signature_filename;
$logaction->note = $event->acceptance->note;
$logaction->action_type = 'declined';
$logaction->action_date = $event->acceptance->declined_at;
$logaction->created_by = auth()->user()->id;
// TODO: log the actual license seat that was checked out
if ($event->acceptance->checkoutable instanceof LicenseSeat) {
+3 -3
View File
@@ -12,11 +12,11 @@ class CategoryEditForm extends Component
public $originalSendCheckInEmailValue;
public $requireAcceptance;
public bool $requireAcceptance;
public $sendCheckInEmail;
public bool $sendCheckInEmail;
public $useDefaultEula;
public bool $useDefaultEula;
public function mount()
{
+84 -82
View File
@@ -177,43 +177,43 @@ class Importer extends Component
* These are the item-type specific columns
*/
$this->accessories_fields = [
'category' => trans('general.category'),
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.accessory')]),
'location' => trans('general.location'),
'manufacturer' => trans('general.manufacturer'),
'min_amt' => trans('mail.min_QTY'),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'min_amt' => trans('mail.min_QTY'),
'order_number' => trans('general.order_number'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'quantity' => trans('general.qty'),
'supplier' => trans('general.supplier'),
];
$this->assets_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.asset')]),
'asset_tag' => trans('general.asset_tag'),
'asset_eol_date' => trans('admin/hardware/form.eol_date'),
'asset_model' => trans('general.model_name'),
'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
'asset_tag' => trans('general.asset_tag'),
'byod' => trans('general.byod'),
'model_number' => trans('general.model_no'),
'status' => trans('general.status'),
'warranty_months' => trans('admin/hardware/form.warranty'),
'category' => trans('general.category'),
'requestable' => trans('admin/hardware/general.requestable'),
'serial' => trans('general.serial_number'),
'supplier' => trans('general.supplier'),
'company' => trans('general.company'),
'image' => trans('general.importer.image_filename'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.asset')]),
'location' => trans('general.location'),
'manufacturer' => trans('general.manufacturer'),
'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
'model_number' => trans('general.model_no'),
'order_number' => trans('general.order_number'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'image' => trans('general.importer.image_filename'),
'asset_eol_date' => trans('admin/hardware/form.eol_date'),
'requestable' => trans('admin/hardware/general.requestable'),
'serial' => trans('general.serial_number'),
'status' => trans('general.status'),
'supplier' => trans('general.supplier'),
'warranty_months' => trans('admin/hardware/form.warranty'),
/**
* Checkout fields:
* Assets can be checked out to other assets, people, or locations, but we currently
@@ -238,122 +238,124 @@ class Importer extends Component
];
$this->consumables_fields = [
'category' => trans('general.category'),
'checkout_class' => trans('general.importer.checkout_type'),
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.consumable')]),
'item_no' => trans('admin/consumables/general.item_no'),
'location' => trans('general.location'),
'manufacturer' => trans('general.manufacturer'),
'min_amt' => trans('general.min_amt'),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'min_amt' => trans('mail.min_QTY'),
'category' => trans('general.category'),
'order_number' => trans('general.order_number'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'checkout_class' => trans('general.importer.checkout_type'),
'quantity' => trans('general.qty'),
'supplier' => trans('general.supplier'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'item_no' => trans('admin/consumables/general.item_no'),
];
$this->components_fields = [
'category' => trans('general.category'),
'company' => trans('general.company'),
'location' => trans('general.location'),
'quantity' => trans('general.qty'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.component')]),
'location' => trans('general.location'),
'manufacturer' => trans('general.manufacturer'),
'min_amt' => trans('mail.min_QTY'),
'model_number' => trans('general.model_no'),
'notes' => trans('general.notes'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'min_amt' => trans('mail.min_QTY'),
'order_number' => trans('general.order_number'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'manufacturer' => trans('general.manufacturer'),
'order_number' => trans('general.order_number'),
'quantity' => trans('general.qty'),
'serial' => trans('general.serial_number'),
'supplier' => trans('general.supplier'),
];
$this->licenses_fields = [
'company' => trans('general.company'),
'location' => trans('general.location'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.license')]),
'asset_tag' => trans('general.importer.checked_out_to_tag'),
'category' => trans('general.category'),
'checkout_class' => trans('general.importer.checkout_type'),
'company' => trans('general.company'),
'email' => trans('general.importer.checked_out_to_email'),
'expiration_date' => trans('admin/licenses/form.expiration'),
'full_name' => trans('general.importer.checked_out_to_fullname'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.license')]),
'license_email' => trans('admin/licenses/form.to_email'),
'license_name' => trans('admin/licenses/form.to_name'),
'purchase_order' => trans('admin/licenses/form.purchase_order'),
'order_number' => trans('general.order_number'),
'reassignable' => trans('admin/licenses/form.reassignable'),
'seats' => trans('admin/licenses/form.seats'),
'location' => trans('general.location'),
'maintained' => trans('admin/licenses/form.maintained'),
'manufacturer' => trans('general.manufacturer'),
'min_amt' => trans('general.min_amt'),
'notes' => trans('general.notes'),
'category' => trans('general.category'),
'supplier' => trans('general.supplier'),
'order_number' => trans('general.order_number'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'maintained' => trans('admin/licenses/form.maintained'),
'checkout_class' => trans('general.importer.checkout_type'),
'purchase_order' => trans('admin/licenses/form.purchase_order'),
'reassignable' => trans('admin/licenses/form.reassignable'),
'seats' => trans('admin/licenses/form.seats'),
'serial' => trans('general.license_serial'),
'email' => trans('general.importer.checked_out_to_email'),
'supplier' => trans('general.supplier'),
'termination_date' => trans('admin/licenses/form.termination_date'),
'username' => trans('general.importer.checked_out_to_username'),
'manufacturer' => trans('general.manufacturer'),
];
$this->users_fields = [
'id' => trans('general.id'),
'company' => trans('general.company'),
'location' => trans('general.location'),
'department' => trans('general.department'),
'first_name' => trans('general.first_name'),
'last_name' => trans('general.last_name'),
'notes' => trans('general.notes'),
'username' => trans('admin/users/table.username'),
'jobtitle' => trans('admin/users/table.title'),
'phone_number' => trans('admin/users/table.phone'),
'manager_first_name' => trans('general.importer.manager_first_name'),
'manager_last_name' => trans('general.importer.manager_last_name'),
'activated' => trans('general.activated'),
'address' => trans('general.address'),
'city' => trans('general.city'),
'state' => trans('general.state'),
'country' => trans('general.country'),
'zip' => trans('general.zip'),
'vip' => trans('general.importer.vip'),
'remote' => trans('admin/users/general.remote'),
'email' => trans('admin/users/table.email'),
'website' => trans('general.website'),
'avatar' => trans('general.image'),
'gravatar' => trans('general.importer.gravatar'),
'start_date' => trans('general.start_date'),
'end_date' => trans('general.end_date'),
'city' => trans('general.city'),
'company' => trans('general.company'),
'country' => trans('general.country'),
'department' => trans('general.department'),
'email' => trans('admin/users/table.email'),
'employee_num' => trans('general.employee_number'),
'end_date' => trans('general.end_date'),
'first_name' => trans('general.first_name'),
'gravatar' => trans('general.importer.gravatar'),
'jobtitle' => trans('admin/users/table.title'),
'last_name' => trans('general.last_name'),
'location' => trans('general.location'),
'manager_first_name' => trans('general.importer.manager_first_name'),
'manager_last_name' => trans('general.importer.manager_last_name'),
'notes' => trans('general.notes'),
'phone_number' => trans('admin/users/table.phone'),
'remote' => trans('admin/users/general.remote'),
'start_date' => trans('general.start_date'),
'state' => trans('general.state'),
'username' => trans('admin/users/table.username'),
'vip' => trans('general.importer.vip'),
'website' => trans('general.website'),
'zip' => trans('general.zip'),
];
$this->locations_fields = [
'id' => trans('general.id'),
'name' => trans('general.item_name_var', ['item' => trans('general.location')]),
'address' => trans('general.address'),
'address2' => trans('general.importer.address2'),
'city' => trans('general.city'),
'state' => trans('general.state'),
'country' => trans('general.country'),
'zip' => trans('general.zip'),
'currency' => trans('general.importer.currency'),
'ldap_ou' => trans('admin/locations/table.ldap_ou'),
'manager_username' => trans('general.importer.manager_username'),
'manager' => trans('general.importer.manager_full_name'),
'parent_location' => trans('admin/locations/table.parent'),
'manager_username' => trans('general.importer.manager_username'),
'name' => trans('general.item_name_var', ['item' => trans('general.location')]),
'notes' => trans('general.notes'),
'parent_location' => trans('admin/locations/table.parent'),
'state' => trans('general.state'),
'zip' => trans('general.zip'),
];
$this->assetmodels_fields = [
'item_name' => trans('general.item_name_var', ['item' => trans('general.asset_model')]),
'category' => trans('general.category'),
'eol' => trans('general.eol'),
'fieldset' => trans('admin/models/general.fieldset'),
'item_name' => trans('general.item_name_var', ['item' => trans('general.asset_model')]),
'manufacturer' => trans('general.manufacturer'),
'min_amt' => trans('mail.min_QTY'),
'model_number' => trans('general.model_no'),
'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
'min_amt' => trans('mail.min_QTY'),
'fieldset' => trans('admin/models/general.fieldset'),
'eol' => trans('general.eol'),
'requestable' => trans('admin/models/general.requestable'),
];
+40 -3
View File
@@ -20,10 +20,12 @@ class CheckoutAssetMail extends Mailable
{
use Queueable, SerializesModels;
private bool $firstTimeSending;
/**
* Create a new message instance.
*/
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note, bool $firstTimeSending = true)
{
$this->item = $asset;
$this->admin = $checkedOutBy;
@@ -36,6 +38,8 @@ class CheckoutAssetMail extends Mailable
$this->last_checkout = '';
$this->expected_checkin = '';
$this->firstTimeSending = $firstTimeSending;
if ($this->item->last_checkout) {
$this->last_checkout = Helper::getFormattedDateObject($this->item->last_checkout, 'date',
false);
@@ -56,7 +60,7 @@ class CheckoutAssetMail extends Mailable
return new Envelope(
from: $from,
subject: trans('mail.Asset_Checkout_Notification'),
subject: $this->getSubject(),
);
}
@@ -70,7 +74,7 @@ class CheckoutAssetMail extends Mailable
{
$this->item->load('assetstatus');
$eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : '';
$req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
$req_accept = $this->requiresAcceptance();
$fields = [];
// Check if the item has custom fields associated with it
@@ -94,6 +98,7 @@ class CheckoutAssetMail extends Mailable
'accept_url' => $accept_url,
'last_checkout' => $this->last_checkout,
'expected_checkin' => $this->expected_checkin,
'introduction_line' => $this->introductionLine(),
],
);
}
@@ -107,4 +112,36 @@ class CheckoutAssetMail extends Mailable
{
return [];
}
private function getSubject(): string
{
if ($this->firstTimeSending) {
return trans('mail.Asset_Checkout_Notification');
}
return trans('mail.unaccepted_asset_reminder');
}
private function introductionLine(): string
{
if ($this->firstTimeSending && $this->requiresAcceptance()) {
return trans('mail.new_item_checked_with_acceptance');
}
if ($this->firstTimeSending && !$this->requiresAcceptance()) {
return trans('mail.new_item_checked');
}
if (!$this->firstTimeSending && $this->requiresAcceptance()) {
return trans('mail.recent_item_checked');
}
// we shouldn't get here but let's send a default message just in case
return trans('new_item_checked');
}
private function requiresAcceptance(): int|bool
{
return method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0;
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class SendUpcomingAuditMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct($params, $threshold)
{
$this->assets = $params;
$this->threshold = $threshold;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$from = new Address(config('mail.from.address'), config('mail.from.name'));
return new Envelope(
from: $from,
subject: trans_choice('mail.upcoming-audits', $this->assets->count(), ['count' => $this->assets->count(), 'threshold' => $this->threshold]),
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'notifications.markdown.upcoming-audits',
with: [
'assets' => $this->assets,
'threshold' => $this->threshold,
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
+3 -2
View File
@@ -19,9 +19,10 @@ class UnacceptedAssetReminderMail extends Mailable
*/
public function __construct($checkout_info, $count)
{
$this->count = $count;
$this->target = $checkout_info['acceptance']?->assignedTo;
$this->acceptance = $checkout_info['acceptance'];
$this->target = $checkout_info?->assignedTo;
$this->acceptance = $checkout_info;
}
/**
+1
View File
@@ -61,6 +61,7 @@ class Accessory extends SnipeModel
'qty' => 'required|integer|min:1',
'category_id' => 'required|integer|exists:categories,id',
'company_id' => 'integer|nullable',
'location_id' => 'exists:locations,id|nullable|fmcs_location',
'min_amt' => 'integer|min:0|nullable',
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
'purchase_date' => 'date_format:Y-m-d|nullable',
+97 -7
View File
@@ -57,7 +57,9 @@ class Actionlog extends SnipeModel
'user_agent',
'item_type',
'target_type',
'action_source'
'action_source',
'created_at',
'action_date',
];
/**
@@ -69,7 +71,25 @@ class Actionlog extends SnipeModel
'company' => ['name'],
'adminuser' => ['first_name','last_name','username', 'email'],
'user' => ['first_name','last_name','username', 'email'],
'assets' => ['asset_tag','name', 'serial'],
'assets' => ['asset_tag','name', 'serial', 'order_number', 'notes', 'purchase_date'],
'assets.model' => ['name', 'model_number', 'eol', 'notes'],
'assets.model.category' => ['name', 'notes'],
'assets.model.manufacturer' => ['name', 'notes'],
'licenses' => ['name', 'serial', 'notes', 'order_number', 'license_email', 'license_name', 'purchase_order', 'purchase_date'],
'licenses.category' => ['name', 'notes'],
'licenses.supplier' => ['name'],
'consumables' => ['name', 'notes', 'order_number', 'model_number', 'item_no', 'purchase_date'],
'consumables.category' => ['name', 'notes'],
'consumables.location' => ['name', 'notes'],
'consumables.supplier' => ['name', 'notes'],
'components' => ['name', 'notes', 'purchase_date'],
'components.category' => ['name', 'notes'],
'components.location' => ['name', 'notes'],
'components.supplier' => ['name', 'notes'],
'accessories' => ['name', 'purchase_date'],
'accessories.category' => ['name'],
'accessories.location' => ['name', 'notes'],
'accessories.supplier' => ['name', 'notes'],
];
/**
@@ -134,6 +154,54 @@ class Actionlog extends SnipeModel
return $this->hasMany(\App\Models\Asset::class, 'id', 'item_id');
}
/**
* Establishes the actionlog -> license relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function licenses()
{
return $this->hasMany(\App\Models\License::class, 'id', 'item_id');
}
/**
* Establishes the actionlog -> consumable relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function consumables()
{
return $this->hasMany(\App\Models\Consumable::class, 'id', 'item_id');
}
/**
* Establishes the actionlog -> consumable relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function accessories()
{
return $this->hasMany(\App\Models\Accessory::class, 'id', 'item_id');
}
/**
* Establishes the actionlog -> components relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function components()
{
return $this->hasMany(\App\Models\Component::class, 'id', 'item_id');
}
/**
* Establishes the actionlog -> item type relationship
*
@@ -293,16 +361,22 @@ 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);
$last_audit_date = $this->created_at; // this is the action log's created at, not the asset itself
$next_audit = $last_audit_date->addMonth($monthInterval); // this actually *modifies* the $last_audit_date
$next_audit_days = (int) round($now->diffInDays($next_audit, true));
$override_default_next = $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);
$override_default_next = Carbon::parse($asset->next_audit_date);
$next_audit_days = (int) round($override_default_next->diffInDays($now, true));
}
// Show as negative number if the next audit date is before the audit date we're looking at
if ($this->created_at > $override_default_next) {
$next_audit_days = '-'.$next_audit_days;
}
return $next_audit_days;
}
@@ -380,4 +454,20 @@ class Actionlog extends SnipeModel
{
return $query->leftJoin('users as admin_sort', 'action_logs.created_by', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);
}
public function scopeByTargetOrItem($query, $id, $type) {
return $query->where(function($query) use ($id, $type)
{
$query->where('item_id', '=', $id)
->where('item_type', '=', 'App\\Models\\'.ucwords($type));
})
->orWhere(function($query) use ($id, $type)
{
$query->where('target_id', '=', $id)
->where('target_type', '=', 'App\\Models\\'.ucwords($type));
});
}
}
+54 -29
View File
@@ -108,8 +108,8 @@ class Asset extends Depreciable
'expected_checkin' => ['nullable', 'date'],
'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'],
'next_audit_date' => ['nullable', 'date'],
'location_id' => ['nullable', 'exists:locations,id'],
'rtd_location_id' => ['nullable', 'exists:locations,id'],
'location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'],
'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'],
'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'],
'serial' => ['nullable', 'unique_undeleted:assets,serial'],
'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:9999999999999'],
@@ -122,7 +122,7 @@ class Asset extends Depreciable
'assigned_to' => ['nullable', 'integer'],
'requestable' => ['nullable', 'boolean'],
'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'],
'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL'],
'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL', 'fmcs_location'],
'assigned_asset' => ['nullable', 'exists:assets,id,deleted_at,NULL']
];
@@ -213,6 +213,31 @@ class Asset extends Depreciable
$this->attributes['expected_checkin'] = $value;
}
public function customFieldValidationRules()
{
$customFieldValidationRules = [];
if (($this->model) && ($this->model->fieldset)) {
foreach ($this->model->fieldset->fields as $field) {
if ($field->format == 'BOOLEAN'){
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
}
}
$customFieldValidationRules += $this->model->fieldset->validation_rules();
}
return $customFieldValidationRules;
}
/**
* This handles the custom field validation for assets
*
@@ -220,29 +245,7 @@ class Asset extends Depreciable
*/
public function save(array $params = [])
{
if ($this->model_id != '') {
$model = AssetModel::find($this->model_id);
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field){
if($field->format == 'BOOLEAN'){
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
}
}
$this->rules += $model->fieldset->validation_rules();
if ($this->model->fieldset){
foreach ($this->model->fieldset->fields as $field){
if($field->format == 'BOOLEAN'){
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
}
}
}
}
}
$this->rules += $this->customFieldValidationRules();
return parent::save($params);
}
@@ -254,7 +257,7 @@ class Asset extends Depreciable
/**
* Returns the warranty expiration date as Carbon object
* @return \Carbon|null
* @return \Carbon\Carbon|null
*/
public function getWarrantyExpiresAttribute()
{
@@ -484,6 +487,13 @@ class Asset extends Depreciable
->orderBy('created_at', 'desc');
}
public function targetLogs()
{
return $this->hasMany('\App\Models\Actionlog', 'target_id')
->where('target_type', '=', Asset::class)
->orderBy('created_at', 'desc');
}
/**
* Determines whether the asset is checked out to a user
*
@@ -687,6 +697,21 @@ class Asset extends Depreciable
->withTrashed();
}
/**
* Get the list of audits for this asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function audits()
{
return $this->assetlog()->where('action_type', '=', 'audit')
->orderBy('created_at', 'desc')
->withTrashed();
}
/**
* Get the list of checkins for this asset
*
@@ -785,7 +810,7 @@ class Asset extends Depreciable
->whereNotNull('warranty_months')
->whereNotNull('purchase_date')
->whereNull('deleted_at')
->whereRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH) <= DATE(NOW() + INTERVAL '
->whereRaw('DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) <= DATE_ADD(NOW(), INTERVAL '
. $days
. ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()')
->orderByRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH)')
@@ -1456,7 +1481,7 @@ class Asset extends Depreciable
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeRequestableAssets($query)
public function scopeRequestableAssets($query): Builder
{
$table = $query->getModel()->getTable();
+1 -1
View File
@@ -234,7 +234,7 @@ class AssetModel extends SnipeModel
*/
public function adminuser()
{
return $this->belongsTo(\App\Models\User::class, 'created_by');
return $this->belongsTo(\App\Models\User::class, 'created_by')->withTrashed();
}
+2 -1
View File
@@ -67,7 +67,8 @@ final class Company extends SnipeModel
'phone',
'fax',
'email',
'created_by'
'created_by',
'notes',
];
private static function isFullMultipleCompanySupportEnabled()
+8 -1
View File
@@ -13,6 +13,13 @@ trait CompanyableTrait
*/
public static function bootCompanyableTrait()
{
static::addGlobalScope(new CompanyableScope);
// In Version 7.0 and before locations weren't scoped by companies, so add a check for the backward compatibility setting
if (__CLASS__ != 'App\Models\Location') {
static::addGlobalScope(new CompanyableScope);
} else {
if (Setting::getSettings()->scope_locations_fmcs == 1) {
static::addGlobalScope(new CompanyableScope);
}
}
}
}
+1
View File
@@ -36,6 +36,7 @@ class Component extends SnipeModel
'category_id' => 'required|integer|exists:categories,id',
'supplier_id' => 'nullable|integer|exists:suppliers,id',
'company_id' => 'integer|nullable|exists:companies,id',
'location_id' => 'exists:locations,id|nullable|fmcs_location',
'min_amt' => 'integer|min:0|nullable',
'purchase_date' => 'date_format:Y-m-d|nullable',
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
+2
View File
@@ -49,6 +49,7 @@ class Consumable extends SnipeModel
'qty' => 'required|integer|min:0|max:99999',
'category_id' => 'required|integer',
'company_id' => 'integer|nullable',
'location_id' => 'exists:locations,id|nullable|fmcs_location',
'min_amt' => 'integer|min:0|max:99999|nullable',
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
'purchase_date' => 'date_format:Y-m-d|nullable',
@@ -75,6 +76,7 @@ class Consumable extends SnipeModel
'item_no',
'location_id',
'manufacturer_id',
'supplier_id',
'name',
'order_number',
'model_number',

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