Compare commits

...

1228 Commits

Author SHA1 Message Date
snipe d990d753ce Switch to 660 2026-03-09 08:23:03 +00:00
snipe 425bab096d Added fix-permissions script to composer to handle #18601 2026-03-08 14:52:23 +00:00
snipe 338fc88095 Merge pull request #18647 from grokability/revert-18601-chore/security-upgrade-passport13-socialite-jwt7
Revert "Upgrade Passport to v13 and move php-jwt to v7 to remediate JWT advisory"
2026-03-08 12:32:31 +00:00
snipe 0a724cc49a Revert "Upgrade Passport to v13 and move php-jwt to v7 to remediate JWT advisory" 2026-03-08 12:29:56 +00:00
snipe a2efcf1ca9 Merge pull request #18601 from ubc-cpsc/chore/security-upgrade-passport13-socialite-jwt7
Upgrade Passport to v13 and move php-jwt to v7 to remediate JWT advisory
2026-03-08 11:51:15 +00:00
snipe c9d73e85e1 Fixed RB-4083 2026-03-08 11:37:19 +00:00
Joël Pittet a956676745 Move Passport 13 key permission normalization to upgrade.php to cover existing installs with incorrect permissions 2026-03-07 22:22:46 -08:00
Joël Pittet 5800d08202 normalize key permissions 2026-03-07 21:59:07 -08:00
Joël Pittet b8958bad72 Test failure fixes from 'Key Files Permissions Validation' from https://github.com/laravel/passport/blob/13.x/UPGRADE.md#key-files-permissions-validation 2026-03-07 21:59:00 -08:00
Joël Pittet 0508d7558e OAuth Client Table Changes (Optional) from https://github.com/laravel/passport/blob/13.x/UPGRADE.md#oauth-client-table-changes-optional 2026-03-07 21:59:00 -08:00
Joël Pittet 98c3192a13 Bump laravel/socialite to latest patch release, upgrade laravel/passport to 13 which is compatible with Laravel 11, and security release for firebase/php-jwt 2026-03-07 21:59:00 -08:00
snipe b886e11670 Fixed user API tests 2026-03-07 20:50:18 +00:00
snipe 92bb1df82e Removed error log in test 2026-03-07 11:05:33 +00:00
snipe 1c4549dd8e Added word break on history 2026-03-07 11:04:25 +00:00
snipe 83e61ec8cd Unset admin if auth user is not admin 2026-03-07 10:45:57 +00:00
snipe 7ea549df4a Added .journal to gitignore 2026-03-06 21:57:23 +00:00
snipe af1c55cd7e Small code re-org for clarity 2026-03-06 21:55:46 +00:00
snipe 7822c0bc3e Merge pull request #18644 from uberbrady/fix_location_observer
Re-add Location Observer with null-safe companyable check
2026-03-06 21:47:58 +00:00
Brady Wetherington 2b7ed1f7fa Re-add Location Observer with null-safe companyable check 2026-03-06 21:43:19 +00:00
snipe 45ddaf1b72 Temp comment out location observer 2026-03-06 20:14:56 +00:00
snipe 7595c922fa Fixed #18043 - added location logging 2026-03-06 14:03:37 +00:00
snipe 4d4513d936 Fixed #18642 - view-assets mobile 2026-03-06 13:40:01 +00:00
snipe e378c99923 Layout tweakas 2026-03-06 13:29:38 +00:00
snipe 078c870970 Merge pull request #18162 from Godmartinz/status_bulk_select_fix
Adds #17685 better warning for bulk status change to undeployable type
2026-03-06 09:58:41 +01:00
snipe 4a91d37423 Merge pull request #17666 from akemidx/xxdays-since-last-update
Feature: Added xx days since last update to Custom Report
2026-03-06 09:54:32 +01:00
snipe 96befb2aef Merge branch 'develop' into xxdays-since-last-update 2026-03-06 09:54:01 +01:00
snipe 925452e106 Merge pull request #18248 from iryadifarhan/fix/depreciation-report-displays-incorrect-monthly-depreciation-value
Fixes inaccurate monthly depreciation value displayed at depreciation report when using a floor value
2026-03-06 09:40:39 +01:00
snipe 1678330f0c Merge pull request #18426 from akemidx/global-report-templates
Fixed: Added global report templates
2026-03-06 09:30:15 +01:00
snipe 58cf3fc2ea Dev assets 2026-03-06 08:17:48 +00:00
snipe 13ce17ffc6 Merge pull request #18494 from marcusmoore/livewire4
Upgraded Livewire to v4
2026-03-06 09:16:25 +01:00
snipe 70e020ee0c Merge pull request #18637 from marcusmoore/test-updates
Fixed some minor issues in tests
2026-03-06 09:16:00 +01:00
snipe c6a167ef40 Removed min-height (this might go badly) 2026-03-06 07:42:28 +00:00
snipe 420ba5f261 Fixed #18341 - remove action button from user profile view 2026-03-06 06:45:46 +00:00
snipe 7aa1f3ae1d Merge pull request #18639 from grokability/#18282-add-user-notes-to-custom-export
Fixed #18282 - added original notes to checkout target in custom report
2026-03-06 07:23:58 +01:00
snipe 556d638136 Fixed #18282 - added original notes to checkout target in custom report 2026-03-06 06:19:16 +00:00
snipe c4aa10baf8 Added user company to custom asset report 2026-03-06 05:59:11 +00:00
snipe 86f647e714 Improved bulk audit bg 2026-03-06 05:39:09 +00:00
snipe 64f5d40fd9 Removed duplicate code 2026-03-06 05:21:59 +00:00
snipe 1a0869c2ca Merge pull request #18628 from marcusmoore/fixes/fd-54108-checkin-all-button
Fixed Checkin All Seats button
2026-03-06 06:17:12 +01:00
snipe 1fb32ee461 Merge pull request #18638 from grokability/experiments/compact-nav
Compacted nav UI, components for buttons
2026-03-06 05:45:02 +01:00
snipe 68a863e63e Updated test, fixed route 2026-03-06 04:38:48 +00:00
snipe a1e62ccd46 Fixed (shimmed) #18636 - added shim roite for fieldsets 2026-03-06 03:54:33 +00:00
snipe dae66c0fa4 Fixed audit background 2026-03-06 03:48:45 +00:00
snipe 1d6b21e1c7 Switched to newer button model 2026-03-06 03:33:04 +00:00
snipe 455be12058 Gate fix for admin 2026-03-06 03:05:40 +00:00
snipe 2ebb3ecb96 Update views with new button blades 2026-03-06 01:17:00 +00:00
Marcus Moore b29056dddf Delete duplicate test 2026-03-05 16:35:15 -08:00
Marcus Moore 3ed09f304c Fix method name 2026-03-05 16:30:05 -08:00
Marcus Moore 4875283ed1 Fix test route 2026-03-05 16:20:27 -08:00
Marcus Moore 261231c09e Fix method and variable names 2026-03-05 15:55:59 -08:00
snipe 180dad6ee6 Use updated infobox 2026-03-05 14:36:12 +00:00
snipe d7121ad82f Removed class active (this is handled in jquery) 2026-03-05 11:46:55 +00:00
snipe f121c61524 Added hidden-print class to action buttons column 2026-03-05 11:45:21 +00:00
Marcus Moore 93bf91869b Fix checkin all seats button 2026-03-03 14:57:43 -08:00
snipe 2ed32612b9 Fixed typo in comments 2026-03-02 18:35:57 +01:00
snipe 314bcbe208 Added assets upload url singleton 2026-03-02 18:35:57 +01:00
snipe 6290e34623 Exclude show route for settings API [RB-17718] 2026-03-02 18:35:57 +01:00
snipe 5e5521a128 Merge pull request #18623 from grokability/dependabot/github_actions/develop/actions/upload-artifact-7
Bump actions/upload-artifact from 6 to 7
2026-03-02 12:51:02 +00:00
dependabot[bot] 1e8bed86d3 Bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 08:43:13 +00:00
snipe 8bc7d50e35 Updated to check gate on object 2026-02-26 16:03:57 +00:00
Marcus Moore aa420e5006 Bump Livewire to v4.1.4 2026-02-25 13:30:30 -08:00
Marcus Moore 4babaa85fc Merge branch 'develop' into livewire4
# Conflicts:
#	composer.lock
2026-02-25 13:29:07 -08:00
snipe 378d9e008e Removed truncate that ran after maintenance seeder 2026-02-25 19:46:09 +00:00
snipe 340fabe4f6 Merge pull request #18614 from spencerrlongg/eof-rollbar
Fixes (hopefully) RB #19772 Unexpected EOF
2026-02-25 19:05:30 +00:00
spencerrlongg 29331b8b06 missing semicolon - kind of a shot in the dark 2026-02-25 12:54:43 -06:00
snipe 6fbcaa0959 Make sure manager_id is an integer 2026-02-25 18:41:53 +00:00
snipe 52e10205c5 Add license ID to tabke cookie 2026-02-25 18:40:29 +00:00
snipe f419ae3f4a Fixed license history target 2026-02-25 17:41:26 +00:00
snipe 84a6b8e012 Add display_name to asset advanced search filter 2026-02-25 16:54:54 +00:00
snipe f234aee691 Use display_name in transformUserCompact() 2026-02-25 16:41:30 +00:00
snipe 623d516595 Merge pull request #18095 from marcusmoore/5947-bulk-checkout-rollup
#5947 - roll up bulk asset checkout email
2026-02-25 15:36:58 +00:00
snipe 156eaf53d5 Fix for #18408 2026-02-25 15:30:37 +00:00
snipe 678a7c1437 Removed BS class 2026-02-25 15:04:24 +00:00
snipe ce2a760093 Merge pull request #17964 from Godmartinz/add-License-checkedout-to-user
Adds #11741 currently assigned license table to license checkout
2026-02-25 14:50:39 +00:00
snipe 34b02d8534 Merge pull request #18485 from marcusmoore/debugbar
Bumped Debugbar to v4
2026-02-25 14:43:03 +00:00
snipe 42e0e691dd Merge pull request #18609 from ubc-cpsc/bugfix/php-deprecated-string-interpolation
Fix deprecated string interpolation in controllers
2026-02-25 14:41:24 +00:00
snipe ccfda99060 Merge pull request #18613 from grokability/add-sorting-on-model-name-and-number-for-maintenances
Added model number as a separate field, added sorting
2026-02-25 14:22:48 +00:00
snipe 57f02bdc57 Fixed typo 2026-02-25 14:22:27 +00:00
snipe 7056656eab Added model number as a separate field, added sorting 2026-02-25 14:19:01 +00:00
snipe 2d899fc772 Merge pull request #18612 from grokability/added-maintenances-seeder
Added maintenances seeder
2026-02-25 14:16:44 +00:00
snipe 80ee0835aa Added maintenances seeder 2026-02-25 14:12:13 +00:00
snipe 7b7e6c7971 Fixed maintence image display 2026-02-25 12:39:29 +00:00
snipe 025fb30335 Added user email to exportable field in custom report 2026-02-25 12:03:23 +00:00
snipe fcace4a192 Override the table search with the asset tag search if one is passed 2026-02-25 11:50:52 +00:00
snipe 619e1c70ea Fixed #18603 - set unique cookie ID on asset history table 2026-02-25 11:48:42 +00:00
Joël Pittet 7a95bdfba6 Fix deprecated string interpolation in controllers 2026-02-24 14:57:29 -08:00
Marcus Moore 89962de161 Move to Traits directory 2026-02-24 11:37:07 -08:00
Marcus Moore d9328bd0ce Merge branch 'develop' into debugbar 2026-02-24 11:27:04 -08:00
snipe 81306df06a Merge pull request #18606 from grokability/#18600-check-filesystem-on-healthcheck
Fixed #18600 - add filesystem check on health checker
2026-02-24 14:00:14 +00:00
snipe 75abe9eb3e Re-add try/catch for DB for cleaner output 2026-02-24 13:23:12 +00:00
snipe 1f26bf1125 Fixed #18600 - add filesystem check on health checker 2026-02-24 13:17:15 +00:00
snipe 14b9c06d77 Remove optional qualifier 2026-02-24 12:00:06 +00:00
snipe 4f3d23d1ef Merge pull request #18576 from marcusmoore/fixes/rb-20713-license-seat-assigned-to
Fixed RB-20713: Improved validation of license seat update api endpoint
2026-02-24 07:29:41 +00:00
Marcus Moore cada43f34d Allow checking in from unfound targets 2026-02-23 17:23:42 -08:00
snipe 9188f89a2a Tagged v8.4.0 2026-02-23 20:40:40 +00:00
Marcus Moore 1cc7ef0543 Organization 2026-02-23 10:51:34 -08:00
snipe a86ffc29d1 Added display name to search term in advanced search 2026-02-23 14:43:55 +00:00
snipe 9ed5540c49 Merge pull request #18597 from uberbrady/improve_ldap_binding
Improve LDAP escaping to better reflect modern PHP standards
2026-02-23 13:38:28 +00:00
Brady Wetherington 3a7e00ccc3 Improve LDAP escaping to better reflect modern PHP standards 2026-02-23 13:01:00 +00:00
snipe f82cdabccd More info anel refinements 2026-02-23 11:41:45 +00:00
snipe b9c9cc0046 Merge pull request #18596 from grokability/add-with-trashed-to-formattednamelink
Adds withTrashed() to `adminuser` and updates presenter to show strikethrough
2026-02-23 11:00:43 +00:00
snipe 27ece84d52 Adds withTrashed() to adminuser and updates presenter to show strikethrough 2026-02-23 10:49:44 +00:00
snipe 1299186fb8 Merge pull request #18591 from grokability/#18017-show-more-info-in-bulk-audit
Fixed #18017 - show status on bulk audit
2026-02-21 13:17:31 +00:00
snipe d837e4845b Show note 2026-02-21 13:16:58 +00:00
snipe 39be0c5590 Fixed #18017 - show status on bulk audit 2026-02-21 13:08:23 +00:00
snipe d2bb10e96d Merge pull request #18590 from grokability/optionally-update-audit-dates
Added checkbox to determine if existing audit dates should be changed
2026-02-21 12:53:12 +00:00
snipe f139a616c7 Added checkbox to determine if existing audit dates should be changed 2026-02-21 12:49:36 +00:00
snipe d5099d973b Move copy icon to right side 2026-02-20 15:00:04 +00:00
snipe 82b18a3207 Updated requestable icon 2026-02-20 14:15:50 +00:00
snipe 10aee6bb5f Fixed tab pane name 2026-02-20 13:10:14 +00:00
snipe c1b11ab9bf Small fixes for accessories/checkedout tab 2026-02-20 13:05:31 +00:00
snipe c226a061f5 Tagged pre-release 2026-02-20 12:42:09 +00:00
snipe c0be738aef Removed unneeded return check 2026-02-20 12:13:01 +00:00
snipe e6987ec148 Early return on non-existent acceptance 2026-02-20 12:11:49 +00:00
snipe 973fa7a58b Merge pull request #18575 from spencerrlongg/encrypted-custom-fields-api
Adds field_encrypted To Fields API Response
2026-02-20 12:11:00 +00:00
snipe b6195ba3ae Default active tab fix 2026-02-20 11:56:23 +00:00
snipe d3bd213f29 Fixed typo in tooltip text 2026-02-20 09:49:06 +00:00
snipe 9502dd8bd7 Back out :class 2026-02-20 09:36:38 +00:00
Marcus Moore e81ccf4280 Add more failing assertions 2026-02-19 16:53:22 -08:00
Marcus Moore 9730f2c0f3 Add some failing assertions 2026-02-19 16:40:45 -08:00
Marcus Moore 1de34c0656 Some controller clean up 2026-02-19 16:40:34 -08:00
Marcus Moore bdce6b1387 Add test case 2026-02-19 13:51:20 -08:00
Marcus Moore ce0c2ecd8f Populate new tests 2026-02-19 13:47:52 -08:00
Marcus Moore 40f07e3d72 Scaffold addition test cases 2026-02-19 11:50:04 -08:00
snipe 3c3ccae7c9 Allow context overwrites in FCO specific tables 2026-02-19 19:14:26 +00:00
Godfrey M be211f6f86 fix apostophes 2026-02-19 09:57:29 -08:00
snipe 9789ca42f8 Highlight inventory in red if below min 2026-02-19 15:11:27 +00:00
snipe da26bc5165 Updated JS assets 2026-02-19 14:55:45 +00:00
snipe 0b67626961 Merge pull request #18580 from grokability/fixes-for-alert-menu
Quick fix for alert menu 1001 queries
2026-02-19 14:54:50 +00:00
snipe 546c8ce7d5 Quick fix for alert menu 1001 queries 2026-02-19 14:52:01 +00:00
snipe c76a888d11 Merge pull request #18579 from uberbrady/fix_npm_install_jspdf
Careful upgrade to minimize too many version changes
2026-02-19 14:25:34 +00:00
Brady Wetherington 38c495a4ac Careful upgrade to minimize too many version changes 2026-02-19 14:11:15 +00:00
snipe d0ce5e0c57 Merge pull request #18578 from grokability/#18435-fixed-currency-selection-selectbox
Fixed #18435 - correct option value for currency format selector
2026-02-19 12:05:01 +00:00
snipe 7a0e5b57db Fixed currency selector 2026-02-19 12:01:56 +00:00
snipe c31f1d2cce Nicer row selected color 2026-02-19 11:42:45 +00:00
snipe ea98ee07e5 Fixed #18577 - set url back parameter as default in form component 2026-02-19 11:30:53 +00:00
snipe a38bfada57 Merge pull request #18572 from uberbrady/fix_cross_company_created_by_in_asset_acceptance
Fixed #18571 - fix cross-company created_by in asset acceptance
2026-02-19 11:24:28 +00:00
snipe e5ff19aec4 Fixd required field color 2026-02-19 11:02:12 +00:00
Marcus Moore 504f63066f Add note 2026-02-18 16:34:22 -08:00
Marcus Moore 41b327529b Reference rollbar 2026-02-18 16:31:55 -08:00
Marcus Moore e84279c402 Implement remaining tests 2026-02-18 16:17:34 -08:00
spencerrlongg 65e6519f97 adds field_encrypted to fields api response 2026-02-18 17:53:04 -06:00
Marcus Moore dbbb3beacc Clean up 2026-02-18 15:35:57 -08:00
Marcus Moore e8a73a8de6 Organization 2026-02-18 15:29:44 -08:00
Marcus Moore 411cbc0962 Avoid overwriting created_by and timestamps 2026-02-18 15:20:28 -08:00
Marcus Moore b487c0e3e9 Scaffold additional tests 2026-02-18 15:20:18 -08:00
Marcus Moore fa0e8f6e01 Improve validation 2026-02-18 15:11:52 -08:00
Marcus Moore e25e405f74 Scaffold additional test cases and organize 2026-02-18 14:52:43 -08:00
Marcus Moore 302c4a6414 Organize test 2026-02-18 14:35:06 -08:00
Marcus Moore d1e7f6e55e Inline 2026-02-18 14:03:28 -08:00
Marcus Moore d12f0974df Add test for permission 2026-02-18 14:01:53 -08:00
snipe b751fe7903 Fixed incorrect translation string 2026-02-18 16:30:37 +00:00
snipe a6e34522eb Partially convert maintenances into new blade components 2026-02-18 16:26:13 +00:00
snipe 735e6f3471 Merge pull request #18570 from grokability/convert-consumables-to-new-blade-components
Converted consumables to new blade component
2026-02-18 16:02:32 +00:00
snipe c9f41c950a Added copy+paste, added additional fields 2026-02-18 15:57:40 +00:00
Brady Wetherington 091c710940 Make User parameter nullable 2026-02-18 15:25:05 +00:00
snipe 276f412a3c Small tweaks to info panel 2026-02-18 15:22:46 +00:00
snipe 6f6e5c847a Converted consumables to new blade component 2026-02-18 14:44:08 +00:00
snipe 6ef0274bb3 Merge pull request #18569 from grokability/updated-csvs
Small importer improvements, added larger CSV samples
2026-02-18 14:22:23 +00:00
snipe 7ef6a72ec4 Updated CSVs 2026-02-18 14:06:33 +00:00
snipe e2b8c69cf6 Use iconhelper for dynamic icon 2026-02-18 14:06:24 +00:00
snipe 1254d12d83 Tweaks to info panel 2026-02-18 14:06:14 +00:00
snipe b68642a827 Added alias strings 2026-02-18 14:04:51 +00:00
snipe c5214b976b Added string 2026-02-18 14:04:44 +00:00
snipe e611121244 Added tag color to main importer, added aliases 2026-02-18 14:04:38 +00:00
snipe 3aa7f0b7fe Added tag_color to importer 2026-02-18 14:04:19 +00:00
snipe 3d38eee71d Added/updated icons 2026-02-18 14:04:00 +00:00
snipe 90e6e309f9 Removed redundant icon helper 2026-02-18 14:03:53 +00:00
snipe 885314b87a Added large components sample CSV 2026-02-18 12:01:59 +00:00
snipe ef013a7026 Updated components sample 2026-02-18 12:01:48 +00:00
snipe 0353ade90e Trim off whitespace for headers 2026-02-18 12:01:34 +00:00
snipe d90c9282ac Updated strings 2026-02-18 11:13:52 +00:00
Marcus Moore 5055aafe1d Scaffold tests 2026-02-17 17:37:24 -08:00
Marcus Moore e68b9ea267 Validate assigned_to is integer 2026-02-17 16:51:31 -08:00
Marcus Moore 1d8ba2ec61 Add failing test 2026-02-17 16:49:32 -08:00
snipe d56970eaa3 Merge pull request #18566 from grokability/fixed-new-vs-update-on-custom-fieldset-creation
Fixed new vs update button label on custom fieldset creation, fixed breadcrumbs on new custom fieldset creation screen
2026-02-17 22:23:06 +00:00
snipe 8b3f18ec59 Better breadcrumbs for fieldset creation 2026-02-17 22:17:38 +00:00
snipe 170d20ddb5 Fixed new vs update button on custom fieldset create vs edit 2026-02-17 22:17:27 +00:00
snipe 70407dac85 Merge pull request #18565 from grokability/#18555-component-clone
Added #18555 - ability to clone components
2026-02-17 16:22:02 +00:00
snipe c2334d87de Merge pull request #18564 from uberbrady/fix_component_counts
Fixed #18557 - Tweak numRemaining to not re-query for un-checked-out components
2026-02-17 15:44:00 +00:00
snipe 0662e3351e Added #18555 - ability to clone components 2026-02-17 15:43:43 +00:00
Brady Wetherington 6b90b3743e Tweak numRemaining to not re-query for un-checked-out components 2026-02-17 15:27:04 +00:00
snipe dd1d456106 Alphabetize dictionary 2026-02-17 15:21:24 +00:00
snipe dceead8302 Show footer by default if the transformer asks for it 2026-02-17 15:09:29 +00:00
snipe 832f449868 Merge pull request #18561 from grokability/#18512-comment-out-first-checkout
Fixes #18512 (first checkout) temporarily
2026-02-17 14:18:05 +00:00
snipe 33a104f142 Fixes #18512 temporarily 2026-02-17 14:11:14 +00:00
snipe 28f19689ec Fixed focus color for input fields 2026-02-17 13:38:43 +00:00
snipe ce6ee32e89 Made seats a numeric field 2026-02-17 13:38:29 +00:00
snipe fdf42ba321 Add class to checkedout blade 2026-02-17 13:18:27 +00:00
snipe aadd158108 Added active class to accessory tab 2026-02-17 13:18:14 +00:00
snipe d6592819e8 Show only active licenses 2026-02-17 13:17:49 +00:00
snipe 8f34c06196 Added order_number to API for accessories, consumables, components 2026-02-17 13:08:09 +00:00
snipe 9cdc73917b Merge pull request #18537 from grokability/more-refactoring-tables-and-tabs
WIP - More refactoring of tabs, tab panes, and tables
2026-02-17 12:39:31 +00:00
snipe 2976159499 Check for parent (only really matters in locations) 2026-02-17 12:32:07 +00:00
snipe e302ccf985 Added plain clone translation 2026-02-17 12:31:35 +00:00
snipe 08915e8607 Use isDeletable in transformers 2026-02-17 12:31:26 +00:00
snipe 32b2131bff Fixed asset model deletable check 2026-02-17 12:31:12 +00:00
snipe 50cf15fb71 Added isDeletable() method 2026-02-17 12:31:01 +00:00
snipe f07ef6d7c5 Added restore and clone blade 2026-02-17 12:28:48 +00:00
snipe 666025d7f6 Use more generic text 2026-02-17 12:28:27 +00:00
snipe 671601365c Use new button blade components 2026-02-17 12:28:16 +00:00
snipe 263b04cd69 Merge pull request #13506 from matmair/develop-1
Fixed: wrong index reference in MoveUploadsToNewDisk.php
2026-02-17 11:08:24 +00:00
snipe 57b98ca782 Fixed copypasta 2026-02-13 19:03:44 +00:00
snipe 46df19d7cd Don’t use fixed columns on unaccepted asset report 2026-02-13 18:27:18 +00:00
snipe fa18524223 Fixed unaccepted report to account for much older items that might not exist 2026-02-13 18:23:59 +00:00
snipe 3ebc0532ca Added isDeleteable to Accessories 2026-02-13 18:07:22 +00:00
snipe 4f59752b8b Added rtd tab icon 2026-02-13 17:30:11 +00:00
snipe 668ab221cc Removed uploads for manufacturers since we don’t have them? 2026-02-13 17:29:56 +00:00
snipe ad90e005c4 Added history tabs 2026-02-13 17:28:07 +00:00
snipe 952b3d6884 Converted components view to blade component
This effing sucked
2026-02-13 17:27:45 +00:00
snipe 19ca1f7578 Converted to object-specific elements in indexes 2026-02-13 17:27:14 +00:00
snipe ef02fab94b Added checkout blade 2026-02-13 17:22:35 +00:00
snipe 144b5e7558 Switch to accessory table on accessory view 2026-02-13 17:22:22 +00:00
snipe aff0a60138 Added presenter for component history 2026-02-13 17:22:07 +00:00
snipe 92a641f01a Added snipe_component because wtf laravel 2026-02-13 17:21:59 +00:00
snipe 929132ba07 Merge pull request #18544 from uberbrady/fix_1001_query_on_available_models_for_notification
Added some withCount() params to tweak the 'minimum number of assets' notification
2026-02-12 21:37:10 +00:00
Brady Wetherington 2a1a4a1c72 Added some withCount() params to tweak the 'minimum number of assets' notification 2026-02-12 21:32:30 +00:00
snipe 4d73894cd2 Merge pull request #18542 from marcusmoore/fixes/fd-53577-reminder-subject-2
Updated subject line of acceptance reminder emails
2026-02-12 21:22:55 +00:00
snipe e49e805b2b Merge pull request #18539 from Godmartinz/L7163_title_adjustment
Fixes title alignment to fit label L7163_A
2026-02-12 20:43:52 +00:00
snipe 05f3bf633e Fixed typeError for isManagerOf in manager view of EULAs 2026-02-12 20:21:15 +00:00
Marcus Moore 4d5b3548d6 Update introduction line 2026-02-11 16:28:17 -08:00
Marcus Moore 00408f0103 Revert "Put checkout_qty on checkoutable so accessory and consumable emails are formatted correctly"
This reverts commit 548cceee18.
2026-02-11 16:06:27 -08:00
Marcus Moore 548cceee18 Put checkout_qty on checkoutable so accessory and consumable emails are formatted correctly 2026-02-11 13:39:37 -08:00
Marcus Moore 7eb536f80e Bump debugbar to v4.0.7 2026-02-11 11:37:57 -08:00
Marcus Moore d91ce88e9a Merge branch 'develop' into debugbar 2026-02-11 11:37:02 -08:00
Godfrey M 76d1a20e21 adjust label L7163_A to fit title properly 2026-02-11 11:02:49 -08:00
snipe 5b6951b88d Tightened up licenses index list 2026-02-11 15:22:58 +00:00
snipe 56313e4436 Covert assets table 2026-02-11 14:54:30 +00:00
snipe 7810ae74d1 Fixed translation string 2026-02-11 14:30:42 +00:00
snipe 3ac1012757 Set fixed number for assets 2026-02-11 14:30:32 +00:00
snipe 91aec08ce0 More refactoring of tabs, tab panes, and tables 2026-02-11 13:18:32 +00:00
snipe 30970cc7f2 A few more disabled/readonly state color fixes 2026-02-11 11:21:53 +00:00
snipe 985c027b04 Fixed disabled select2 color 2026-02-11 11:12:33 +00:00
snipe d1c4b055a9 Merge pull request #18535 from grokability/rename-field-in-infobox
Update the name of the object within the side panel
2026-02-11 10:20:23 +00:00
snipe 3f2f508e49 Fixed failing test 2026-02-11 10:16:44 +00:00
snipe 07513cb559 Update the name of the object within the side panel
It’s not always a contact/person, so…
2026-02-11 10:02:20 +00:00
snipe 66022902b7 Small style tweaks to info-box 2026-02-11 09:57:43 +00:00
snipe 613efe963a Use consistent box icon 2026-02-11 09:48:44 +00:00
snipe 5a0affcd8e Updated categories view 2026-02-11 09:47:38 +00:00
snipe 79150edb92 Fixed variable name 2026-02-11 07:41:13 +00:00
snipe 6cca24be8e Fixed typo 2026-02-11 07:27:16 +00:00
snipe c2ca51e8ef Added edit buttons to supplier view 2026-02-11 07:27:08 +00:00
snipe b9908d5665 Cast the DB_PORT to integer 2026-02-11 07:01:03 +00:00
snipe cbca420217 Added tooltips 2026-02-11 06:42:15 +00:00
snipe 017183e3fe Merge pull request #18529 from grokability/consolidate-customfieldset-edit
Consolidated custom fieldset edit/show
2026-02-11 05:25:27 +00:00
snipe 0236527f05 Use correct seeding color for links 2026-02-10 15:27:05 +00:00
snipe e946c4bf8c Consolidated custom fieldset edit/show 2026-02-10 15:20:57 +00:00
snipe 8a0414cef6 Merge remote-tracking branch 'origin/master' into develop 2026-02-10 14:16:42 +00:00
snipe ee1ad692a4 Tweaked disabled button color 2026-02-10 13:40:14 +00:00
snipe fdd5f6b0e1 Fixed #18497 - Added model name to aliases 2026-02-10 13:07:41 +00:00
snipe dca6df3244 Fixed #18527 - disable sticky column on dashboard tables 2026-02-10 11:31:25 +00:00
snipe 0874b853a0 Fixed #18520 - use plain_text_company 2026-02-10 11:10:48 +00:00
snipe 3393916b5e Merge pull request #18524 from ubc-cpsc/fix/subpath-livewire-prefix
Fix Livewire and Passport routes for subpath hosting
2026-02-10 08:34:18 +00:00
Joël Pittet b2728b4eb1 Fix Livewire routes for subpath hosting 2026-02-10 00:22:13 -08:00
snipe f3cc3ed682 Merge pull request #18470 from bilias/pr-clean
Do not delete asset name if update request does not have a name
2026-02-09 20:57:07 +00:00
snipe f8cfb8833f Merge pull request #18514 from grokability/update-models-with-new-components
Update models with new components
2026-02-09 19:53:40 +00:00
snipe c2c2332e83 Removed erroneous icon 2026-02-09 13:05:22 +00:00
snipe 9f69c36426 Added depreciations and departments 2026-02-09 13:03:07 +00:00
snipe ea9de35a3b Show/hide on companies and depts 2026-02-07 18:44:37 +00:00
snipe a50a16fb01 Nicer toggle, nicer show/hide info button 2026-02-07 18:39:42 +00:00
snipe f27c0206de Expand/contract info tab (still looks a little junky) 2026-02-07 17:52:22 +00:00
snipe 6791ddd911 Merge remote-tracking branch 'origin/develop' 2026-02-07 15:55:20 +00:00
snipe ce95060d60 Fixed #18516 - added kits for sticky columns 2026-02-07 15:55:11 +00:00
snipe 6d9bbe1ddf Small tweaks to EULA API 2026-02-07 15:41:51 +00:00
snipe 5b1507f4b7 Updated license presenter 2026-02-07 15:16:32 +00:00
snipe 53e985aaab Tweaked color for alt striping again 2026-02-07 15:08:51 +00:00
snipe f5c2119122 Merge remote-tracking branch 'origin/develop' 2026-02-07 15:03:58 +00:00
snipe 5fd6918948 Fixed text color in light mode on alt striping 2026-02-07 15:03:50 +00:00
Marcus Moore 7423d13bdd Accept $firstTimeSending in mails 2026-02-05 17:06:36 -08:00
Marcus Moore fb8586f186 Properly pass parameter for asset emails 2026-02-05 16:59:49 -08:00
Marcus Moore 8b9ebf736b Add assertions for subject line 2026-02-05 16:59:36 -08:00
Marcus Moore 5abcdb8c5a Fix relationships in AccessoryCheckout model 2026-02-05 16:46:51 -08:00
Marcus Moore e549f67fcc Create AccessoryCheckout factory 2026-02-05 16:45:25 -08:00
Marcus Moore d68576c2fa Make test name more accurate 2026-02-05 13:57:51 -08:00
Marcus Moore a6042b6a03 Clean up 2026-02-05 13:52:15 -08:00
Marcus Moore d1a3afd992 More test clean up 2026-02-05 13:39:47 -08:00
Marcus Moore 34b00f7dba More test clean up 2026-02-05 13:38:05 -08:00
Marcus Moore 14b6c6861f Test clean up 2026-02-05 13:34:09 -08:00
Marcus Moore fe4bb4209c Split out tests 2026-02-05 13:07:41 -08:00
Marcus Moore e997eb2012 Inline method 2026-02-05 12:57:42 -08:00
snipe 8337473f5a Added box arrows 2026-02-05 19:27:36 +00:00
Marcus Moore 7ada7fb327 Merge branch 'develop' into livewire4 2026-02-05 10:39:56 -08:00
Marcus Moore 9d632e39bf Merge branch 'develop' into debugbar 2026-02-05 10:33:13 -08:00
snipe 1f50eada6d Added bulk edit to department user listing 2026-02-05 14:38:46 +00:00
snipe 38e1114dad Convert manufacturers and departments 2026-02-05 14:35:01 +00:00
snipe 11be73a578 Removed stray closing div 2026-02-05 14:14:25 +00:00
snipe 13a00df73c Use new components for company view 2026-02-05 14:13:51 +00:00
snipe 7739690bf5 Extend SnipeModel for custom fieldsets 2026-02-05 13:41:26 +00:00
snipe 389eb9e05d Bumped hash 2026-02-05 13:25:53 +00:00
snipe 98fe94aa24 Bumped hash 2026-02-05 13:25:21 +00:00
snipe 4ee378bf8e Merge remote-tracking branch 'origin/master' into develop
# Conflicts:
#	public/css/dist/bootstrap-table.css
#	public/js/dist/bootstrap-table.js
#	resources/views/blade/table/index.blade.php
#	resources/views/models/custom_fields_form.blade.php
2026-02-05 13:24:27 +00:00
snipe a8c268760b Fixed #18500 - skip encoding on API url for table component 2026-02-05 13:19:16 +00:00
snipe c684b8ab1e Fixed #18502 - custom fields disappearing on smaller resolution breakpoints 2026-02-05 12:25:49 +00:00
snipe 333d0e2391 Fixed #18510 - use correct GH link in social footer 2026-02-05 11:28:29 +00:00
snipe c22c5993fc Fixed #18503 - alt row colors on striped tables 2026-02-05 11:27:10 +00:00
snipe 1c239cc7cf Fixed typo in icon name 2026-02-05 10:16:24 +00:00
snipe 6ee5aa3e12 Fixed checkout to all modal 2026-02-04 20:55:38 +00:00
snipe 0d5ceb1e90 Merge pull request #18492 from marcusmoore/bump-phpunit
Bumped PHPUnit version
2026-02-04 20:48:06 +00:00
snipe 166a241761 Merge pull request #18484 from spencerrlongg/undefined_var_files
Fixes `Undefined variable $files` Exception
2026-02-04 20:47:02 +00:00
snipe f6f5e806e4 More flexibility in blade components for labels vs icons vs icon types 2026-02-04 20:31:11 +00:00
snipe f0f42240f3 Still more icons 2026-02-04 20:30:20 +00:00
snipe 686afc0974 Gate check linking in presenters 2026-02-04 20:30:12 +00:00
Marcus Moore 5d085469ec Bump debugbar to v4.0.6 2026-02-04 10:46:34 -08:00
Marcus Moore 7c54429c13 Bump Livewire to v4.1.2 2026-02-03 09:14:41 -08:00
Marcus Moore 8dfd04573e Bump debugbar to v4.0.5 2026-02-02 09:53:17 -08:00
snipe 7e5fb3c9ae Added more icons 2026-02-02 15:20:21 +00:00
snipe 65e4c0b8d1 Renamed contact box to info-panel 2026-02-02 12:22:20 +00:00
snipe b1301237f1 Removed double lines in table 2026-02-02 11:35:09 +00:00
snipe cf82c12708 Fixed custom fieldset table colors 2026-02-02 11:27:18 +00:00
snipe 52f8697d91 Added icons, more additions to sidebar 2026-02-02 11:16:53 +00:00
Marcus Moore 1aee8679d2 Fix updating bug in oauth clients 2026-01-28 12:46:57 -08:00
Marcus Moore 3169c5b503 Bump phpunit 2026-01-28 10:15:11 -08:00
snipe 741f0d69ab Merge pull request #18490 from Godmartinz/add-label-preview-translation
Add translation and template name for Label Preview
2026-01-28 13:14:54 +00:00
Marcus Moore 8829ea9b29 Update category edit form 2026-01-27 17:08:21 -08:00
Godfrey M 53b283eac5 add seperate brackets for template name 2026-01-27 16:21:06 -08:00
Godfrey M 1234b6297e add Label preview translation, also which label is previewed 2026-01-27 14:59:44 -08:00
Marcus Moore 6ba99e8199 Bump livewire to v4.1.0 2026-01-27 14:03:52 -08:00
Marcus Moore b4eb603f22 Switch blur to change 2026-01-27 13:58:42 -08:00
Marcus Moore 62f9ce979d Fix js 2026-01-27 13:41:57 -08:00
Marcus Moore f5928fc707 Fix tags in importer 2026-01-27 12:33:46 -08:00
Marcus Moore f7040e3616 Remove console.log 2026-01-26 17:43:47 -08:00
Marcus Moore a70f1cc1ef Migrate request hook 2026-01-26 17:38:01 -08:00
Marcus Moore a86b738231 Update route 2026-01-26 17:13:56 -08:00
Marcus Moore 8a9ddba208 Use traditional component naming 2026-01-26 17:02:32 -08:00
Marcus Moore 4c727966e7 Migrate config 2026-01-26 16:59:08 -08:00
Marcus Moore c2ac53029a Update to v4 2026-01-26 16:58:58 -08:00
Marcus Moore 5d300d85bd Update content-type assertion 2026-01-26 14:52:53 -08:00
Marcus Moore 1dc181797b Move trait to base controller 2026-01-26 14:39:48 -08:00
Marcus Moore 30d932e2e0 Re-enable exception collector 2026-01-26 14:30:03 -08:00
Marcus Moore 9fb32d33af Migrate to v4 config 2026-01-26 14:28:18 -08:00
Marcus Moore 70baa60521 Install v4 of laravel debugbar 2026-01-26 14:02:01 -08:00
Marcus Moore 8ea2784d44 Uninstall debugbar 2026-01-26 14:01:29 -08:00
Marcus Moore 2b56d82577 Move disabling debugbar to trait 2026-01-26 14:01:21 -08:00
spencerrlongg 8ff34baafa wrap $files in isset to avoid null errors 2026-01-26 15:44:23 -06:00
snipe eeae534a37 Added a few more helpers 2026-01-25 14:33:54 +00:00
snipe 8b71049a3d Merge pull request #18482 from grokability/move-form-components
Move form components into their own directory
2026-01-24 21:13:07 +00:00
snipe 373361dab0 Move form components into their own directory 2026-01-24 21:06:02 +00:00
snipe 60a1141b9d Merge pull request #18480 from grokability/#more-table-components
Moves more indexes to blade components
2026-01-24 20:55:51 +00:00
snipe a38c8a0235 Merge more into contact card 2026-01-24 20:37:08 +00:00
snipe c39d165a3d Added space 2026-01-24 20:36:50 +00:00
snipe 2238f8e8ad More icons 2026-01-24 20:36:43 +00:00
snipe e0c53c3ead Merge classes 2026-01-24 18:32:13 +00:00
snipe 1074bc2d3b More conversions 2026-01-24 18:27:06 +00:00
snipe 4b22b1c115 Better defaults in nav-item component 2026-01-24 17:41:02 +00:00
snipe 141794caf7 Use new contact panel in suppliers view 2026-01-24 17:40:41 +00:00
snipe d5997e2394 Added new classes 2026-01-24 17:40:29 +00:00
snipe 429ca8dd34 Added info components 2026-01-24 17:40:18 +00:00
snipe c2ad0defe0 Added contact component 2026-01-24 17:39:53 +00:00
snipe b4658f2696 Added address presenter 2026-01-24 17:39:44 +00:00
snipe 11b7dfc9b0 Added icons 2026-01-24 17:39:33 +00:00
snipe f7b7ef850d Updated suppliers view 2026-01-24 12:10:58 +00:00
snipe bb9b145519 Updated helper icon 2026-01-24 12:10:50 +00:00
snipe d75f5b8fd3 Fixed label 2026-01-23 19:14:29 +00:00
snipe 34fe64b27c Fixed spacing 2026-01-23 19:09:48 +00:00
snipe 2151595b45 Fixed a few typos 2026-01-23 18:28:49 +00:00
snipe b2c94386b3 Switched from stupid layouts/edit-form to new form component 2026-01-23 18:26:09 +00:00
snipe b99ae9be88 Form component (will move the other things later) 2026-01-23 18:25:49 +00:00
snipe ffbc831071 Include the box footer if there is a route passed 2026-01-23 18:25:34 +00:00
snipe 302a60fbe7 Better breadcrumb 2026-01-23 18:25:06 +00:00
snipe 3ec8ba01d0 Updated manufacturer with new tabs 2026-01-23 18:24:50 +00:00
snipe 2048eb7d5a Removed unneeded presenter methods 2026-01-23 17:08:25 +00:00
snipe 4d605927f3 Fixed typos 2026-01-23 17:07:38 +00:00
snipe b7ee3a2f1d Updated groups index to use new components 2026-01-23 17:05:28 +00:00
snipe 44fa9e2d1e Handle status label index 2026-01-23 16:58:42 +00:00
snipe a5e818f970 More moved to new box+table method 2026-01-23 16:47:27 +00:00
snipe 066cf81233 Added model bulk component 2026-01-23 16:46:52 +00:00
snipe 1b4552cf30 Refactorered bulk action component 2026-01-23 16:46:42 +00:00
snipe 0ba8ee1c5f Added bulkactions named slot in box index 2026-01-23 16:45:48 +00:00
snipe 5c4f3a5aec Removed unneeded body blade 2026-01-23 16:45:30 +00:00
snipe c963c0b25b Updated path for new table component location 2026-01-23 15:45:36 +00:00
snipe b3a4fb2676 Use new user bulk edit blade 2026-01-23 15:45:19 +00:00
snipe 0b82902248 Fixed background loading color 2026-01-23 15:45:06 +00:00
snipe e7f70ccd1f Moved to new component structure 2026-01-23 15:44:52 +00:00
snipe 683395599f Moved files into table directory 2026-01-23 15:44:12 +00:00
snipe 755d7c2351 Added printIgnore, reordered fields 2026-01-23 15:43:35 +00:00
snipe 17d91bcd8e Merge pull request #18479 from grokability/improved-tab-components
Removed font-size override on tabs
2026-01-23 13:53:33 +00:00
snipe 2633ec10dc Removed font-size override on tabs 2026-01-23 13:52:00 +00:00
snipe c4f772c8d9 Merge pull request #18478 from grokability/refined-sticky-columns
Added sticky column
2026-01-23 13:50:49 +00:00
snipe 18addf2a87 Added sticky column 2026-01-23 13:49:09 +00:00
snipe eee826248d Merge pull request #18476 from marcusmoore/fixes/18475-action-log-checkin-during-import
Fixed #18475 - reference the correct model when checking in an asset via import
2026-01-23 09:37:50 +00:00
Marcus Moore 96a817753c Pass the assigned model to the event instead of always passing a User 2026-01-22 14:24:37 -08:00
snipe 600b06e66b Merge pull request #18474 from grokability/move-box-index
Renamed box blade component wrapper to index
2026-01-22 21:10:53 +00:00
snipe cd680daa4c Merge remote-tracking branch 'origin/develop' 2026-01-22 20:58:42 +00:00
snipe 315b716e87 Removed advanced search from files table
It’s not wired up on the backend, so…
2026-01-22 20:50:24 +00:00
snipe b2f1966a78 Renamed box blade component wrapper to index 2026-01-22 20:41:25 +00:00
snipe 6305f87037 Merge pull request #18473 from grokability/added-tab-component
Added tab components
2026-01-22 20:37:11 +00:00
snipe ab02b67d3c Merge remote-tracking branch 'origin/develop' 2026-01-22 20:34:51 +00:00
snipe 0f4086eaf0 Added gates to the tab panes as well 2026-01-22 20:32:33 +00:00
snipe e7c5329e57 Merge pull request #18472 from Godmartinz/fix-n+1-issue
Moved helper query, to eager load through Asset API index
2026-01-22 20:27:44 +00:00
snipe ad82ea86c8 Added tab components 2026-01-22 20:25:33 +00:00
Godfrey M c310e1e3b7 update blade 2026-01-22 11:37:24 -08:00
Godfrey M aaf9372474 move helper query to eager load in the asset api 2026-01-22 11:12:49 -08:00
snipe 2cf169359e Missed updating the closing tags 2026-01-22 15:45:53 +00:00
snipe 7f67f8c20d Merge pull request #18471 from grokability/move-box-blades-into-own-directory
Moved anonymous box blade components into their own directory
2026-01-22 15:44:10 +00:00
snipe 5607dfcecb Moved anonymous box blade components into their own dir 2026-01-22 15:38:20 +00:00
Giannis Kapetanakis ca99f525c9 Do not delete asset name if update request does not have a name 2026-01-22 12:40:59 +02:00
snipe 7e92517d13 Merge remote-tracking branch 'origin/develop' 2026-01-22 08:46:46 +00:00
snipe 1ebb67b2e7 Nicer route formatting for maintenances 2026-01-22 08:45:38 +00:00
snipe d9ef7b43b0 Merge remote-tracking branch 'origin/develop' 2026-01-21 19:59:59 +00:00
snipe c074fae885 Merge pull request #18459 from Godmartinz/moar_label_fixes
Fixes #18353 Label title scales with label fields. adjusted label row allotment.
2026-01-21 19:58:32 +00:00
snipe c5ada0fc2f Merge pull request #18466 from marcusmoore/eager-load-expiring-alerts-command
Added eager load to Expiring Alerts command
2026-01-21 19:57:44 +00:00
snipe 5268b0f67f Merge pull request #18456 from grokability/container-component-phase-2
Next step in container+box component
2026-01-21 19:57:32 +00:00
Marcus Moore 7e10089c13 Eager load assignedTo and supplier 2026-01-21 11:49:02 -08:00
Godfrey M 1dab36da2d replaced extracted variables with array 2026-01-21 10:32:26 -08:00
snipe fab50e53b8 Merge pull request #18458 from marcusmoore/fixes/17309-asset-eol-in-custom-report
Fixed #17309 - include EOL date in custom asset report
2026-01-21 11:45:08 +00:00
Godfrey M 10cfe6d37a remove duplicate parameter 2026-01-20 16:23:36 -08:00
Godfrey M 3718ce9749 if label is null make room for value 2026-01-20 16:18:13 -08:00
Godfrey M fd39c8bf11 update title help text 2026-01-20 16:09:04 -08:00
Godfrey M 2e122fa8d8 update LW 11354 2026-01-20 16:00:56 -08:00
Godfrey M 66d85d17d9 update LW 1933081 2026-01-20 16:00:24 -08:00
Godfrey M 61bc570d59 add title to layout helper, update lw2112283 2026-01-20 15:59:40 -08:00
Marcus Moore 62dbd400a4 Use asset_eol_date in custom asset report 2026-01-20 15:07:19 -08:00
snipe 74af52d29d Merge pull request #18457 from marcusmoore/fixes/api-image-upload
Fixed storing image for accessory and consumable creation via api
2026-01-20 22:28:04 +00:00
Marcus Moore 59f377b058 Prepare images prior to validation in accessory and asset creation requests 2026-01-20 13:38:01 -08:00
snipe 949f65b210 Merge pull request #18356 from Godmartinz/add-first-checkout-to-asset
Adds #17210 1st checkout to asset view and index
2026-01-20 20:36:21 +00:00
snipe 4046fbae89 Merge pull request #18256 from iryadifarhan/fix/audit-log-displays-negative-number-incorrectly-when-next-audit-date-filled-with-current-date
Fixes inconsistent negative symbol at Audit Log Report when Next Audit Date is set to the current date
2026-01-20 20:35:16 +00:00
snipe da8776c2f1 Larger group select box 2026-01-20 20:20:39 +00:00
snipe 4043df1d02 Sort groups by name, asc 2026-01-20 20:13:22 +00:00
snipe 3bc5ab593a Next step in container+box component 2026-01-20 14:49:09 +00:00
snipe 6dad0d669f Merge pull request #18454 from grokability/container-components
Use basic container+box for primary index pages with one column
2026-01-19 16:54:45 +00:00
snipe 39f581a826 Use basic container+box for primary index pages with one column 2026-01-19 16:47:55 +00:00
snipe 2034b25b25 Set footer to default false 2026-01-19 16:20:48 +00:00
snipe 468a7aa911 Added button component 2026-01-19 16:19:07 +00:00
snipe ec50643d96 Basic container+box components 2026-01-19 16:17:47 +00:00
snipe 3b98fb666f Added tel as form type to make look right 2026-01-19 16:15:14 +00:00
snipe f17eeed579 Added fax icon 2026-01-19 15:09:28 +00:00
snipe 452d0b6f6e Merge remote-tracking branch 'origin/develop' 2026-01-19 11:44:04 +00:00
snipe 41450b6e1b Merge pull request #18368 from marcusmoore/feature/group-create-edit-load-improvement
Improved loading speeds on group create and edit page
2026-01-19 11:38:00 +00:00
snipe 30029462b1 Merge remote-tracking branch 'origin/develop' 2026-01-17 11:40:22 +00:00
snipe e109879cac Small autolabeler improvements 2026-01-17 11:40:08 +00:00
snipe 90ad4e9abf Merge remote-tracking branch 'origin/develop' 2026-01-17 11:27:43 +00:00
snipe fd0e2b1a96 Fixed indenting and page footer 2026-01-17 11:27:34 +00:00
snipe 19d637efea Merge remote-tracking branch 'origin/develop' 2026-01-16 13:03:26 +00:00
snipe 6c18b35276 Added barcode class 2026-01-16 13:03:17 +00:00
snipe 184eddb5cf Merge remote-tracking branch 'origin/develop' 2026-01-16 12:52:43 +00:00
snipe 1d577b0171 Merge pull request #18450 from grokability/#18449-small-sidenav-fixes
Fixed #18449 - small sidenav improvements for selected contexts
2026-01-16 12:51:52 +00:00
snipe 05c998227a Fixed #18449 - small sidenav improvements for selected contexts 2026-01-16 12:48:28 +00:00
snipe 53ed810d86 Merge remote-tracking branch 'origin/develop' 2026-01-16 11:38:09 +00:00
snipe 7c136b6f57 Use fa-fw in footer icons 2026-01-16 11:37:57 +00:00
snipe 4e510d9d8c Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	resources/views/models/index.blade.php
2026-01-16 11:36:35 +00:00
snipe e1f30f96c9 Remove stray closing divs 2026-01-16 11:36:10 +00:00
snipe 146d0cefc0 Removed extra closing divs 2026-01-16 11:35:18 +00:00
snipe 0103bd58e1 Merge pull request #18434 from Godmartinz/label_lay_fix
Fixes Label text scaling
2026-01-15 21:13:01 +00:00
Godfrey M dbc1e7d6ab remove scaling from label title 2026-01-15 12:03:45 -08:00
snipe ea706863b5 Merge remote-tracking branch 'origin/develop' 2026-01-15 11:42:18 +00:00
snipe 04f4f5b57c Merge pull request #18439 from grokability/#18135-better-handle-cli-importer-permissions
Fixed #18135 - only unset sensitive variables in the web UI importer
2026-01-15 11:39:03 +00:00
snipe 98b9246c10 Merge pull request #18442 from uberbrady/fewer_scim_exceptions
Fixed - throw fewer exceptions on SCIM misconfigurations
2026-01-15 11:38:49 +00:00
snipe 1e158721ee Merge remote-tracking branch 'origin/develop' 2026-01-15 11:10:09 +00:00
snipe ab4eefcac2 Added links to snipeit-mcp (by @jameshgordy) and SnipeScheduler (by @JSY-Ben) 2026-01-15 11:09:50 +00:00
snipe cf3b36f124 Merge remote-tracking branch 'origin/develop' 2026-01-15 09:45:40 +00:00
snipe 8da01b8569 Check for ID in ldap sync UI screen 2026-01-15 09:45:29 +00:00
snipe c90afaa312 Fixed quotes in cli purge eula command 2026-01-15 09:10:31 +00:00
snipe 9f20bb89c1 Merge remote-tracking branch 'origin/develop' 2026-01-14 21:37:54 +00:00
snipe dfe664b779 Merge pull request #18437 from grokability/#18409-purge-signature-pdfs
Fixed #18409 - command line option to purge signatures
2026-01-14 21:37:35 +00:00
snipe 88bd1fd6ef Merge pull request #18387 from vasiliyplotnikov/mysql_rds_ssl_support_18312
Fixed #18312: support aws rds mysql with force tls
2026-01-14 21:23:18 +00:00
snipe 33f8823edb Merge remote-tracking branch 'origin/develop' 2026-01-14 21:17:28 +00:00
snipe 06d5c0a86c Merge pull request #18440 from ManiacTwister/fix-importer-logfile
Fixed #8740: Use log instance with configured log path
2026-01-14 21:17:09 +00:00
snipe e12a5eda01 Merge remote-tracking branch 'origin/develop' 2026-01-14 20:56:44 +00:00
snipe b3c59b2cc3 Use readonly style with light/dark on LDAP settings 2026-01-14 20:56:35 +00:00
Brady Wetherington 24d5969ce8 Bump versions of our branch of laravel-scim-server to reduce reollbar usage 2026-01-14 14:37:47 +00:00
snipe 6ad42a02ad Merge remote-tracking branch 'origin/develop' 2026-01-14 14:21:50 +00:00
snipe 90b1ee4805 Check that signature exists on the server before trying to display 2026-01-14 14:21:41 +00:00
snipe 0c9d5ca9da Merge remote-tracking branch 'origin/develop' 2026-01-14 14:13:53 +00:00
snipe e05ecd0c3e Add created_by on acceptance logging to action_log 2026-01-14 14:13:43 +00:00
snipe 441657cbfb Merge remote-tracking branch 'origin/develop' 2026-01-14 13:48:54 +00:00
snipe 0fb6b02fc4 Merge pull request #18418 from Godmartinz/rb20479
Fix [RB-20479] Checkin/Checkout notifications from logging as errors
2026-01-14 13:37:48 +00:00
snipe f83df0d651 Merge remote-tracking branch 'origin/develop' 2026-01-14 13:34:29 +00:00
snipe e667cd20a8 Set require_signature to default to 0 2026-01-14 13:30:54 +00:00
ManiacTwister d06c56367f Fixed #8740: Use log instance with configured log path 2026-01-14 14:23:42 +01:00
snipe 039b4cf19f Fixed #18135 - only unset variables if the user is authenticated (Web UI) 2026-01-14 13:10:46 +00:00
snipe ca0961bd49 Clarified text 2026-01-14 12:50:57 +00:00
snipe 79528fa87b Fixed #18409 - command line option to purge signatures 2026-01-14 12:45:18 +00:00
Godfrey M d96d0b1bcb applied to LabelWriter_11354 2026-01-13 12:11:30 -08:00
Godfrey M 02c237404e applied to TZe_24mm_E 2026-01-13 12:07:39 -08:00
Godfrey M c56f5282ff applied to TZe_241 2026-01-13 12:02:05 -08:00
Godfrey M d62f10cb46 apply helper to L4736_A 2026-01-13 11:57:53 -08:00
Godfrey M 4c6f123cda apply helper to L6009_A 2026-01-13 11:55:10 -08:00
Godfrey M b0b1829426 add Helper function for label layout, applied to 1933081 2112283" 2026-01-13 11:50:14 -08:00
snipe 68b590c263 Merge pull request #18419 from Godmartinz/rb20638
Refactor the exceptions for Audit log (again)
2026-01-13 18:20:20 +00:00
Godfrey M b45efddd9a readd connection and throwable 2026-01-13 08:56:12 -08:00
snipe d8f5d8c6ec Merge remote-tracking branch 'origin/develop' 2026-01-13 13:34:18 +00:00
snipe 56baa8ac9f Fixed manager view label alignment 2026-01-13 13:34:09 +00:00
snipe 15aa64ed28 Merge remote-tracking branch 'origin/develop' 2026-01-13 13:30:52 +00:00
snipe 6d0bfeb420 Fixed #18422 - Hide edit buttons if the manager is looking at a different profile 2026-01-13 13:30:42 +00:00
snipe ceff334420 Merge remote-tracking branch 'origin/develop' 2026-01-13 12:43:25 +00:00
snipe 4ee2db68fc Fixed #18429 - corrected string for requestable vs requested items 2026-01-13 12:43:07 +00:00
snipe 1f04d0023b Merge remote-tracking branch 'origin/develop' 2026-01-13 12:38:43 +00:00
snipe 4427fdcaec Use theme button for LDAP sync 2026-01-13 12:38:33 +00:00
snipe 4de642c6a4 Merge pull request #18430 from grokability/#18415-null-ldap-display-name
Fixed #18415 - LDAP sync improvements to allow null display_name and bulk editing display_name
2026-01-13 12:37:48 +00:00
snipe d3eb89a97c Fixed display name in user table 2026-01-13 12:01:34 +00:00
snipe d7362a3785 Nicer light/dark for ldap sync page 2026-01-13 12:01:12 +00:00
snipe 26347ac41e Add display name to bulk user edit 2026-01-13 12:00:46 +00:00
snipe b396da3f33 Use null instead of blank if value is empty 2026-01-13 12:00:27 +00:00
snipe 306a0bf6de Merge remote-tracking branch 'origin/develop' 2026-01-13 10:22:38 +00:00
snipe 8000e274c6 Fixed #18424 - adds BYOD to view-assets page 2026-01-13 10:22:19 +00:00
akemidx 5d91e0fa0a name box alignment/icons for shared or not 2026-01-12 07:48:53 -05:00
akemidx eb41247f2d changing 'shared_report_template' to 'is_shared' in DB part 2, also last commit deleted an unused request file 2026-01-08 18:54:44 -07:00
akemidx d53eafe87a changing 'shared_report_template' to 'is_shared' in DB 2026-01-08 18:52:36 -07:00
Godfrey M e48c40e5af remove use statements 2026-01-08 15:58:15 -08:00
Godfrey M df51318fb9 remove throwable exception..too vague 2026-01-08 15:55:30 -08:00
Marcus Moore 065c17002e Remove unused test cases 2026-01-08 15:18:16 -08:00
Marcus Moore 0e46a54013 Improve input alignment 2026-01-08 15:08:56 -08:00
Marcus Moore 0e35fb941b Improve assertions 2026-01-08 14:17:10 -08:00
Marcus Moore 71c9b927c6 Allow using same template name 2026-01-08 14:17:00 -08:00
Godfrey M 0327d01287 typo fixes" 2026-01-08 10:52:27 -08:00
Godfrey M 0ab206ca13 log warnings instead of errors for 4xx status codes 2026-01-08 09:47:35 -08:00
akemidx e42960ea15 sharing a report template test 2026-01-07 18:49:34 -07:00
Marcus Moore 636d42a52e Make test more explicit 2026-01-07 16:42:47 -08:00
Marcus Moore 1451843f84 Implement test for viewing shared template 2026-01-07 16:39:10 -08:00
Marcus Moore f8af21306a Handle attempting to delete another user's template 2026-01-07 16:37:34 -08:00
Marcus Moore 4e874cdb1b Fix update logic 2026-01-07 16:13:22 -08:00
akemidx dbd9a844dc scaffold test cases around sharing templates 2026-01-07 16:29:32 -07:00
snipe 542cdef0bd Merge remote-tracking branch 'origin/develop' 2026-01-07 15:55:44 +00:00
snipe f5955e14ff Merge pull request #18410 from grokability/#18402-saml-fields-readonly-display
Fixed #18402 - Clean up SAML readonly display
2026-01-07 15:55:25 +00:00
snipe a0df7adbd1 Override readonly styles on user edit page 2026-01-07 15:50:01 +00:00
snipe 82a4398ef6 Removed @disabled on textarea 2026-01-07 15:37:05 +00:00
snipe 9271930ba8 Clean up SAML readonly display 2026-01-07 15:27:33 +00:00
snipe f149f0d994 Merge remote-tracking branch 'origin/develop' 2026-01-07 13:36:31 +00:00
snipe 14ff325608 Merge pull request #18404 from kenchan0130/patch-displayname
fixed: Prefer display name of user on UI
2026-01-07 13:29:56 +00:00
Tadayuki Onishi 8e37fcc71e Since displayname should be referenced in the UI, we've changed it to use displayname 2026-01-07 21:59:27 +09:00
snipe bf910bc708 Merge pull request #18406 from dbakan/patch-1
Fixed #18405: Clean up closing divs to fix footer position
2026-01-07 12:55:54 +00:00
Daniel Albertsen 1136ea0779 Clean up stray closing div tags
Removed redundant closing div tags in the edit view.
2026-01-07 12:43:03 +01:00
Daniel Albertsen 9ad94b6562 Fix white space indention 2026-01-07 12:43:03 +01:00
akemidx c5284ed195 works fully, just needs to restrict editing for non template creator 2026-01-06 17:21:22 -07:00
Marcus Moore 5cd92dd794 Remove redundant display of "Category" 2026-01-05 15:13:27 -08:00
snipe 5ddb0b4a55 Merge remote-tracking branch 'origin/develop' 2026-01-05 22:29:17 +00:00
snipe 990858ba41 Merge pull request #18398 from grokability/fixes-rb-#20282-set-files-variable
Set `$files` in UploadedFilesController API POST endpoint only if there are results
2026-01-05 22:28:48 +00:00
snipe 406951fc84 Set $files only if there are results 2026-01-05 22:17:06 +00:00
snipe 41c7bf4aaa Merge pull request #18397 from Godmartinz/rb-20474-fix
Adds [RB-20474] a try/catch to ms teams audit notification
2026-01-05 20:39:42 +00:00
Godfrey M 1543634cb0 remove line break 2026-01-05 12:39:28 -08:00
Godfrey M b935752ec0 reference endpoint appropriately" 2026-01-05 12:35:20 -08:00
Godfrey M 2a60b7b7b2 move endpoint 2026-01-05 12:31:00 -08:00
snipe cef78687b3 Merge remote-tracking branch 'origin/develop' 2026-01-05 20:27:37 +00:00
snipe 2f29edc01f Changed “reset to default” button to theme button style 2026-01-05 20:27:25 +00:00
snipe 134491b59e Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/js/dist/all.js
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2026-01-05 20:20:51 +00:00
snipe 812ff0bfd2 Bumped jspdf to >=4.0.0 2026-01-05 20:20:09 +00:00
Godfrey M 201c4fa0d9 remove use path 2026-01-05 12:15:37 -08:00
Godfrey M ced83b9bfc add a try catch to ms teams audit notification 2026-01-05 12:14:42 -08:00
akemidx bd98ee62e1 something something prebreak 2026-01-05 12:16:16 -07:00
snipe e67c5273d7 Merge remote-tracking branch 'origin/develop' 2026-01-05 17:22:50 +00:00
snipe b4b9339065 Merge pull request #18396 from uberbrady/fix_select2_break_on_modal
Fixed #17652 - don't break company drop-down with modals
2026-01-05 17:22:29 +00:00
snipe f3cd68eb3f Merge remote-tracking branch 'origin/develop' 2026-01-05 17:21:44 +00:00
snipe ad57dea0e5 Suppress refresh button on client-side tables
Refresh only works on server-side tables
2026-01-05 17:21:32 +00:00
snipe 1e0a348b8e Merge remote-tracking branch 'origin/develop' 2026-01-05 17:19:56 +00:00
snipe 9c06a2126d Update sha.js to 2.4.12 2026-01-05 17:19:43 +00:00
snipe dab030e95d Merge remote-tracking branch 'origin/develop' 2026-01-05 16:56:02 +00:00
snipe 4995bc0d0d Small tweaks to select2 values 2026-01-05 16:55:53 +00:00
Brady Wetherington d2369893c8 Remove ID from company select in partial 2026-01-05 16:30:47 +00:00
snipe e48adf6443 Merge remote-tracking branch 'origin/develop' 2026-01-05 16:06:08 +00:00
snipe 505bca8386 Fixed :focus on theme buttons 2026-01-05 16:05:59 +00:00
snipe 31ca93a259 Merge remote-tracking branch 'origin/develop' 2026-01-05 15:40:59 +00:00
snipe 71523b7038 Merge pull request #18393 from grokability/#18291-bump-alpine
Attempted fix of #18291 - update PHP versions and alpine versions
2026-01-05 15:36:40 +00:00
snipe 9885dc9c8a Merge remote-tracking branch 'origin/develop' 2026-01-05 15:34:19 +00:00
snipe 00d9d9f132 Merge pull request #18395 from grokability/#18394-ldap-text-dark-mode
Fixed #18394 - LDAP sync test table background in dark mode
2026-01-05 15:34:00 +00:00
snipe 320dc3fac6 Fixed LDAP sync test table background in dark mode 2026-01-05 15:29:50 +00:00
Brady Wetherington 6e68e43a25 In this version of Alpine, php84 is default, so it's easier. 2026-01-05 15:11:08 +00:00
snipe a19c391aa1 Check can edit on demo in bulk user edit 2026-01-05 14:58:14 +00:00
snipe 32f0101c1b Attempted fix of #18291 - update PHP versions and alpine versions 2026-01-05 14:05:47 +00:00
snipe 12b9fdced5 Merge remote-tracking branch 'origin/develop' 2026-01-05 13:39:48 +00:00
snipe 0af45a53a9 Merge pull request #18359 from marcusmoore/17816-qty-in-activity-report
Fixed #17816 - added quantity to activity reports
2026-01-05 13:03:56 +00:00
snipe 37773d35f2 Merge pull request #18391 from ubc-cpsc/fix/PKSA-8x19-j2j3-bn67-sodium_compat
fix: paragonie/sodium_compat - Missing check that a point is on the prime subgroup for Edwards25519
2026-01-05 12:58:24 +00:00
snipe fca3eb4b7b Merge pull request #18392 from grokability/#18377-min-value-for-name
Fixes #18377 - make min value for names consistent
2026-01-05 12:56:29 +00:00
snipe 5e9e0b70db Fixes #18377 - make min value for names consistent 2026-01-05 12:51:49 +00:00
Joël Pittet 5242ffc04b fix: Missing check that a point is on the prime subgroup for Edwards25519 2026-01-02 13:55:18 -08:00
snipe f0c9a5b2dc Merge remote-tracking branch 'origin/develop' 2025-12-31 05:31:06 +00:00
snipe b3902e82fc Merge pull request #18388 from dylang3/fix/unintended-shell-exec-call
Fixed #18384: Remove backticks in bulk-actions.blade.php to avoid unintentional shell_exec() call
2025-12-31 05:30:06 +00:00
Dylan Guthrie d70eff6fc4 Fix: Remove backticks in bulk-actions.blade.php to avoid unintentional shell_exec() call 2025-12-30 22:37:49 -06:00
Vasily Plotnikov 5b5695ffe1 Fixed #18312: support aws rds mysql with force tls 2025-12-30 11:59:31 +00:00
snipe deb44cad30 Merge remote-tracking branch 'origin/develop' 2025-12-23 13:34:36 +00:00
snipe 35fdca3607 Added logo hover color 2025-12-23 13:33:53 +00:00
snipe 93080523d0 Merge remote-tracking branch 'origin/develop' 2025-12-21 08:34:42 +00:00
snipe b7b8f5a7e7 Merge pull request #18348 from grokability/dependabot/github_actions/develop/actions/cache-5
Bump actions/cache from 4 to 5
2025-12-21 08:16:48 +00:00
snipe 59ee55a6b2 Merge pull request #18349 from grokability/dependabot/github_actions/develop/actions/upload-artifact-6
Bump actions/upload-artifact from 5 to 6
2025-12-21 08:16:29 +00:00
snipe d85b25d683 Merge pull request #18363 from ubc-cpsc/task/request-get
fix: replace deprecated Symfony Request::get() usage
2025-12-21 08:09:50 +00:00
snipe 0108006fab Merge remote-tracking branch 'origin/develop' 2025-12-19 06:32:09 +00:00
snipe 755bb8f189 Merge pull request #18326 from dylang3/fix/users-groups-sync
Fixes #18325: Ensure existing permission group users are maintained when editing a group
2025-12-19 06:26:44 +00:00
Marcus Moore f53dcbc64f Only pull needed fields from database 2025-12-18 13:37:56 -08:00
Joël Pittet 0f215bbcf8 fix: replace deprecated Symfony Request::get() usage 2025-12-17 18:44:04 -08:00
snipe 9770775770 Merge remote-tracking branch 'origin/develop' 2025-12-17 16:27:42 +00:00
snipe 0b63bcc056 Derp. Copypasta 2025-12-17 16:25:03 +00:00
snipe 03116f5ece Fixed tests 2025-12-17 16:16:58 +00:00
snipe 5c091d8690 DIsable delete button if user cannot delete the user 2025-12-17 15:31:57 +00:00
snipe da1ca24190 Remove avatar delete - should be done in purge 2025-12-17 15:29:07 +00:00
akemidx 33f7a8d356 save copy 2025-12-16 21:38:13 -05:00
akemidx 64c3fe9099 notes and new direction? 2025-12-16 21:27:51 -05:00
Marcus Moore fa7382851f Use enum 2025-12-16 15:00:07 -08:00
Marcus Moore ab363596fd Replace qty with quantity 2025-12-16 14:55:00 -08:00
Marcus Moore b05970acf4 Add command to migrate license seat quantities in action log table 2025-12-16 13:48:23 -08:00
snipe aa6b70c296 Merge remote-tracking branch 'origin/develop' 2025-12-16 20:31:58 +00:00
snipe 00171e6d16 Fixed api docs url 2025-12-16 20:31:50 +00:00
snipe 061e0ded72 Merge remote-tracking branch 'origin/develop' 2025-12-16 20:26:18 +00:00
snipe 81bdd86fb7 Merge pull request #18358 from grokability/add-scim-url
Add SCIM url to admin settings
2025-12-16 20:06:50 +00:00
snipe 60ff06bcf0 Fixed nav footer label color 2025-12-16 20:02:20 +00:00
snipe a3e3f48d47 Added scim and API links to admin page 2025-12-16 19:59:52 +00:00
Godfrey M a3a79a696f adds first checkout to asset view and index 2025-12-16 08:48:08 -08:00
Marcus Moore 6947672046 Add seats to transformer 2025-12-15 17:31:56 -08:00
Marcus Moore 6f77e96998 Log quantity when adding or removing seats from license 2025-12-15 17:26:05 -08:00
Marcus Moore 8a595aa269 Display qty in history 2025-12-15 15:12:34 -08:00
Marcus Moore 1ccf38221f Set qty when accepting or declining checkout 2025-12-15 14:45:05 -08:00
Marcus Moore 18c639e6c0 WIP - adding qty to more event calls 2025-12-15 14:38:13 -08:00
Marcus Moore 06224371b3 Begin passing qty in CheckoutableCheckedOut event 2025-12-15 12:47:36 -08:00
dependabot[bot] d06105f410 Bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 08:02:41 +00:00
dependabot[bot] 3226340b08 Bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 08:02:30 +00:00
snipe 25ef5d64b4 Merge remote-tracking branch 'origin/develop' 2025-12-13 13:44:34 +00:00
snipe f286558065 Fixed #18344 2025-12-13 13:44:23 +00:00
snipe 9586c50cb5 Merge pull request #18347 from grokability/added-reset-for-theme
Added reset for theme, preview for effects in profile
2025-12-13 13:43:23 +00:00
snipe 4658bf38c5 Removed user migration for colors 2025-12-13 13:34:10 +00:00
snipe 951a4e37f3 New strings 2025-12-13 13:33:59 +00:00
snipe 6ad3154035 Added confetti, sounds test on checkboxes 2025-12-13 13:33:49 +00:00
snipe d7fa4a0df2 Tweaked colors 2025-12-13 13:33:13 +00:00
snipe 1112a40f0f Added reset button 2025-12-13 13:33:06 +00:00
snipe 78d1256b74 Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	config/version.php
2025-12-12 08:47:16 +00:00
snipe a3f9aad418 Bumped version 2025-12-12 08:46:25 +00:00
snipe dba8cb83bf Merge remote-tracking branch 'origin/develop' 2025-12-12 07:49:53 +00:00
snipe 1954c607cd #18339 - removed heroku support 2025-12-12 07:49:01 +00:00
snipe 744124f407 Merge remote-tracking branch 'origin/develop' 2025-12-12 07:14:51 +00:00
snipe 3c14921a8c #18340 - jfc swift 2025-12-12 07:14:36 +00:00
snipe b595fe7488 Merge remote-tracking branch 'origin/develop' 2025-12-12 06:41:18 +00:00
snipe b0b194cef7 Fixed missing comma 2025-12-12 06:41:06 +00:00
snipe eb0a3a27d3 Merge remote-tracking branch 'origin/develop' 2025-12-12 05:03:09 +00:00
snipe 72fbcd72e0 Fix for fullscreen with dark/light 2025-12-12 05:02:56 +00:00
snipe 09e660a38c Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/js/dist/all.js
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2025-12-12 04:26:35 +00:00
snipe add1810fcc Added more options and validation to suppliers “new” modal 2025-12-12 04:18:42 +00:00
snipe eead2ce93e Adds cursor pointer to checkboxes and radios and their labels 2025-12-12 03:51:20 +00:00
akemidx 193fba71f3 fixing validation 2025-12-11 17:21:48 -05:00
snipe 5e60d96614 Simpler footer link color 2025-12-11 20:15:57 +00:00
snipe 85c721da99 Better alert color on dark 2025-12-11 20:14:33 +00:00
snipe f3f09dd9a5 Upgraded jquery to 3.7.1 2025-12-11 20:12:23 +00:00
snipe 29ad804ca8 Merge pull request #18332 from Godmartinz/add-category-to-account-accept-table
Fixed #18316 - Adds #18316 category to pending acceptance index table
2025-12-11 19:12:15 +00:00
Godfrey M a8c77d6e26 update accessor to laravel 11.x standards 2025-12-11 11:07:36 -08:00
snipe b949380db8 Fixed highlighted button in license toolbar 2025-12-11 18:38:40 +00:00
Godfrey M b7f6137a63 add accessor for category name on checkout acceptance model 2025-12-11 10:08:00 -08:00
snipe 181cd7f0dc Merge remote-tracking branch 'origin/develop' 2025-12-11 18:03:12 +00:00
snipe 10692dc587 Removed back button on history importer, themed upload button 2025-12-11 17:49:56 +00:00
snipe 8d0793e004 Merge remote-tracking branch 'origin/develop' 2025-12-11 17:45:23 +00:00
snipe 02da163ee0 Fixed modal header color in light mode 2025-12-11 17:45:14 +00:00
snipe 3199e94b3c Made icons fixed width via fa-fw 2025-12-11 16:49:02 +00:00
snipe ac2a1503e2 Merge remote-tracking branch 'origin/develop' 2025-12-11 16:45:48 +00:00
snipe ea10167607 Merge pull request #18333 from Godmartinz/fix-checkin-notification
Fixes [FD-52267] Expected Checkin Notification Shows Overdue instead of Reminder
2025-12-11 16:44:45 +00:00
snipe e617b913cd Merge remote-tracking branch 'origin/develop' 2025-12-11 16:44:01 +00:00
snipe 8f6208a3c9 Removed text-blue in bootstrap-tables 2025-12-11 16:42:56 +00:00
snipe 39c71481c9 Added breaks 2025-12-11 16:34:55 +00:00
snipe a38e49290e Added external link indicator to help text 2025-12-11 16:31:12 +00:00
snipe f974427964 Merge remote-tracking branch 'origin/develop' 2025-12-11 16:05:41 +00:00
snipe 1f311c8657 One more nullsafe 2025-12-11 16:05:31 +00:00
snipe c0406734bc Merge remote-tracking branch 'origin/develop' 2025-12-11 16:01:15 +00:00
snipe 66e80628f6 Account for no created_by value 2025-12-11 16:01:06 +00:00
akemidx 8a14800ef2 fixes/beginning validation 2025-12-11 03:42:10 -05:00
akemidx a46d73f562 spacing/hiding when no template loaded 2025-12-11 03:21:34 -05:00
Marcus Moore e908838376 Add failing tests 2025-12-10 15:44:51 -08:00
Marcus Moore 4b1339a11c Add qty to action_logs table 2025-12-10 13:30:34 -08:00
Godfrey M 620c43fd6d fixes expected checkin Notification 2025-12-10 11:33:08 -08:00
Godfrey M dfb9d5622a adds category to account accept index table 2025-12-10 10:43:59 -08:00
snipe af0aa7da4e Merge remote-tracking branch 'origin/develop' 2025-12-10 10:27:49 +00:00
snipe 75ddb50738 Use theme color for logo uploads 2025-12-10 10:27:40 +00:00
snipe 600238dd9b Merge remote-tracking branch 'origin/develop' 2025-12-10 09:38:51 +00:00
snipe 5a88e98ad9 Merge pull request #18330 from grokability/bigger-double-scrollbar-issue
Fixed #18317 - Longer double scrollbar issue
2025-12-10 09:38:37 +00:00
snipe 84a0544621 Removed <div class="table-responsive"> 2025-12-10 09:33:08 +00:00
snipe 8a1c7ee448 Remove <div class="table table-responsive"> 2025-12-10 09:09:12 +00:00
Dylan Guthrie c978be5cab send associated_users to view as collection 2025-12-09 19:47:42 -06:00
snipe 2fb29dad0a Merge remote-tracking branch 'origin/develop' 2025-12-10 00:09:17 +00:00
snipe 7d160abdaf Merge pull request #18323 from grokability/#18317-removed-responsive-table-divs
Fixed #18317 - Double scrollbars on some screens
2025-12-10 00:08:55 +00:00
snipe 6c5d2c6716 Merge pull request #18328 from grokability/#17197-download-importer-files
Added #17197: Ability to download uploaded import CSVs
2025-12-10 00:07:01 +00:00
snipe f3feff7988 Disable delete button if not owner of super admin 2025-12-09 23:59:22 +00:00
snipe 7d24f50cdc Removed extra whitespace 2025-12-09 23:49:21 +00:00
snipe 7c7375ed43 Undo temp delete commented out 2025-12-09 23:48:34 +00:00
snipe e2e4adca4e Generic message if the user tries to delete a file they don’t have access to 2025-12-09 23:46:53 +00:00
snipe a350b9bc3d Handle redirect if the user does not have permission to view results 2025-12-09 23:46:33 +00:00
snipe 7854543122 Added import-download controller 2025-12-09 23:46:13 +00:00
snipe 8b5636c0ab Override progress bar text color 2025-12-09 23:45:58 +00:00
snipe 9f948fd2ba Link to download and user 2025-12-09 23:45:49 +00:00
snipe 60fb67461a Added downnload route 2025-12-09 23:45:38 +00:00
snipe 5c896fc965 Merge pull request #18314 from Godmartinz/adjust-fonts-on-labelwriter-211xxx
Fixes [FD-52064] LabelWriter label font choice for fields, adds scaling to all labels.
2025-12-09 22:08:35 +00:00
Dylan Guthrie 242201ca91 Fix: Ensure existing permission group users are maintained when editing group 2025-12-09 16:06:49 -06:00
akemidx 865392d1f7 margin 2025-12-09 14:10:32 -05:00
akemidx b880ed2371 front end clarity 2025-12-09 13:14:09 -05:00
snipe c779988771 Fixed gallery cards in dark mode 2025-12-09 16:53:02 +00:00
snipe e6eb15d053 Removed the duplicate table-responsive in BS tables 2025-12-09 16:52:51 +00:00
snipe 05b957df19 Merge pull request #18322 from grokability/bulk-checkin-delete-UI-fixes-dark-mode
Bulk checkin delete UI fixes dark mode
2025-12-09 15:38:25 +00:00
snipe 96da8a5fab Updated route 2025-12-09 15:28:22 +00:00
snipe 62bf61402e Updated strings 2025-12-09 15:24:16 +00:00
snipe 227be798f6 Visual tweaks to bulk 2025-12-09 15:24:10 +00:00
snipe 53f304d137 Updated view with tooltip on disabled 2025-12-09 15:23:59 +00:00
snipe 137d362369 Added breadcrumbs 2025-12-09 15:21:50 +00:00
snipe 5b2cf54f50 Fixed accessory buttons on mobile 2025-12-09 14:06:53 +00:00
snipe b4bc785f7c Merge remote-tracking branch 'origin/develop' 2025-12-09 13:13:59 +00:00
snipe 98a8e4c2ec Merge pull request #18319 from uberbrady/fix_component_edit
Fixed [RB-4066], #18308 - Fix error on saving component after create
2025-12-09 13:13:45 +00:00
Brady Wetherington bed6b04c3d Unset the 'sum_unconstrained_assets' attribute before saving 2025-12-09 13:00:41 +00:00
Marcus Moore def04017e0 Improve readability in tests 2025-12-08 14:10:44 -08:00
Marcus Moore 9eeb916796 Improve clarity in test 2025-12-08 14:04:18 -08:00
Marcus Moore b06a0c5d83 Use value already computed 2025-12-08 13:59:07 -08:00
Marcus Moore 90541ba349 Use foreach instead of reduce 2025-12-08 13:57:38 -08:00
Marcus Moore 0cc346259b Use foreach instead of reduce 2025-12-08 13:51:30 -08:00
Marcus Moore 98c343b438 Improve method ordering 2025-12-08 13:48:22 -08:00
akemidx 7ee0cdc6c7 hide edit and delete for non report creators 2025-12-08 16:42:38 -05:00
akemidx 6a770832ba only creator can see share checkbox 2025-12-08 16:32:11 -05:00
akemidx ef0ff65162 scoping shared templates 2025-12-08 16:29:14 -05:00
akemidx 8c0e7e1bb3 share option saved in db column 2025-12-08 15:55:26 -05:00
snipe babb3ffb9c Merge remote-tracking branch 'origin/develop' 2025-12-08 20:24:31 +00:00
snipe 15c96f753c Revert hasMany “fix” 2025-12-08 20:22:55 +00:00
snipe 354bdeffbf Merge remote-tracking branch 'origin/develop' 2025-12-08 20:19:54 +00:00
snipe 512af90d31 Re-removed non-asset models from kits
These still do not work as expected.
2025-12-08 20:18:39 +00:00
Marcus Moore 8cb2ef7cac Add typehint 2025-12-08 12:17:04 -08:00
Marcus Moore 046b38e5c2 Improve method name 2025-12-08 12:10:25 -08:00
Marcus Moore d50d7fd631 Account for null value 2025-12-08 12:09:58 -08:00
snipe ed837b7527 Merge remote-tracking branch 'origin/develop' 2025-12-08 19:33:07 +00:00
snipe fa5dd99f00 Only try to print status name if it exists 2025-12-08 19:32:57 +00:00
Godfrey M a19282710b fix up 11354 label, and readd 1d barcode to 2112283 2025-12-08 10:44:21 -08:00
Godfrey M 2f3cfb0a4e fix labelwriter fonts and add scaling 2025-12-08 10:20:53 -08:00
snipe af4db94d17 Merge pull request #18306 from fdiaz3000/change-import-assetmodel
Change title_class to ucfirst in Object Import Command to allow AssetModel
2025-12-08 17:05:23 +00:00
snipe bcbf27acca Merge remote-tracking branch 'origin/develop' 2025-12-08 16:51:44 +00:00
snipe 80b037c5a5 Fixed label url 2025-12-08 16:51:36 +00:00
snipe 20bacfeecf Merge remote-tracking branch 'origin/develop' 2025-12-07 21:40:19 +00:00
snipe 8a128ae8c2 Fixed RB-4062 - double-braces 2025-12-07 21:40:02 +00:00
snipe beacfbb082 Merge remote-tracking branch 'origin/develop' 2025-12-07 15:02:17 +00:00
snipe df0d565ae5 Set audit button back to btn-primary 2025-12-07 15:02:07 +00:00
snipe 9ee755c112 More consistent bulk action labels 2025-12-07 14:58:08 +00:00
snipe 130aca2943 Merge remote-tracking branch 'origin/develop' 2025-12-07 14:53:10 +00:00
snipe 5ea76ecb66 Fixed checkmark class on model buk edit 2025-12-07 14:52:49 +00:00
snipe b8ff3ef41a Merge remote-tracking branch 'origin/develop' 2025-12-07 14:49:44 +00:00
snipe 3e8156be54 Merge pull request #18307 from grokability/bulk-model-edit-dark-light-fix
Fixed unreadable table on dark mode for asset model bulk edit, added breadcrumbs
2025-12-07 14:49:31 +00:00
snipe 47e192b530 Fixed breadcrumb title 2025-12-07 14:43:47 +00:00
snipe b33f222fc0 Smaller bulk delete form 2025-12-07 14:39:00 +00:00
snipe 20eab1f403 Fixed updated route names 2025-12-07 14:37:22 +00:00
snipe a283fdb75a Removed back button 2025-12-07 14:37:10 +00:00
snipe a29a115846 Nicer layout on asset bulk delete form 2025-12-07 14:36:59 +00:00
snipe 05ff9183fb Fixed weird top border 2025-12-07 14:31:47 +00:00
snipe 793d299c1d Fixed breadcrumbs 2025-12-07 14:31:01 +00:00
snipe 7d5f862f34 Use striped mode 2025-12-07 14:13:17 +00:00
snipe b0ab900a0f Fixed new dark mode for bulk model edit 2025-12-07 14:10:09 +00:00
Felix 0ea5012ba2 fix(import-command): 18305 change title_class to ucfirst 2025-12-06 15:00:43 -05:00
snipe 7ecb96d45a Merge remote-tracking branch 'origin/develop' 2025-12-06 01:08:46 +00:00
snipe 5f0d7fde39 Better selected color 2025-12-06 01:08:31 +00:00
snipe fe3c301ca2 Prod assets 2025-12-06 00:59:55 +00:00
snipe 3adf8847b0 Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/js/dist/all.js
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2025-12-06 00:59:47 +00:00
snipe 9ae68f0000 More color tweaks 2025-12-06 00:58:32 +00:00
snipe 36415a1f7a Updated buttons 2025-12-05 23:43:56 +00:00
snipe 39d1aa932c Moved clear button 2025-12-05 21:35:47 +00:00
snipe 82b37c3b58 Removed back button 2025-12-05 21:25:17 +00:00
snipe c73c2b003a Merge remote-tracking branch 'origin/develop' 2025-12-05 20:52:48 +00:00
snipe 65b39d3a30 Use the themed buttons 2025-12-05 20:51:47 +00:00
snipe 6a59119c58 Merge pull request #18303 from grokability/update-users-api-with-disallowed-fields-list
Update users api with disallowed fields list
2025-12-05 20:32:55 +00:00
snipe 8b4387ec32 Fixed weird indenting 2025-12-05 19:52:31 +00:00
snipe dc82f8f077 Updated tests 2025-12-05 19:27:05 +00:00
snipe 07e1f67e13 Disallow fill for sensitive fields 2025-12-05 19:27:01 +00:00
snipe 412f4c65c8 Fixed dark mode footer links 2025-12-05 18:35:18 +00:00
snipe a6d9c1f882 Merge remote-tracking branch 'origin/develop' 2025-12-05 18:07:13 +00:00
snipe bb5c142f52 Nicer footer color for light/dark 2025-12-05 18:06:48 +00:00
snipe a5e1528c0d Merge remote-tracking branch 'origin/develop' 2025-12-05 17:46:30 +00:00
snipe 904c20e879 Removed blue text override 2025-12-05 17:46:20 +00:00
snipe 612daa6824 Merge pull request #18302 from grokability/preflight-quickstart-cleanup
Preflight quickstart cleanup
2025-12-05 17:20:29 +00:00
snipe 02b6de2385 Added tests 2025-12-05 17:03:10 +00:00
snipe da5db1920e Use generic email address domains 2025-12-05 17:03:02 +00:00
snipe d20545741e Nicer views 2025-12-05 17:02:51 +00:00
snipe 03b42d2c6c Nicer styles 2025-12-05 17:02:19 +00:00
snipe e9dbeebbc4 Updated some strings 2025-12-05 17:02:09 +00:00
snipe bb53fa245b Removed email domain from required setup fields 2025-12-05 17:01:36 +00:00
snipe bc796498a3 Moved setup controller methods out of settings controller 2025-12-05 17:01:24 +00:00
snipe c25266054b Merge remote-tracking branch 'origin/develop' 2025-12-05 10:55:59 +00:00
snipe 0204414196 Handle /setup link colors via middleware 2025-12-05 10:55:49 +00:00
snipe c6b2017494 Merge pull request #18297 from Godmartinz/tze_24mm_E_2d-adjustment-and-scaling
Fixes [FD-50838] adjust `Tze_24mm_E` 2D barcode 20% bigger, scales fields, center label more
2025-12-05 08:57:20 +00:00
snipe 84fd48602e Merge pull request #18298 from Godmartinz/add-TZe_241
Adds [FD-52267] TZe_241 based on TZe_18mm sizes
2025-12-05 08:56:55 +00:00
Marcus Moore a34ea0804d Separate out info and prompt 2025-12-04 17:22:23 -08:00
Marcus Moore 0fbf4ce443 Move singular eula to bottom of email 2025-12-04 17:08:48 -08:00
Marcus Moore d062cc45df Add translation 2025-12-04 15:18:24 -08:00
Marcus Moore da790136ff WIP 2025-12-04 14:40:51 -08:00
Marcus Moore 134f374ada WIP 2025-12-04 14:39:37 -08:00
Marcus Moore df304a894f WIP 2025-12-04 14:38:13 -08:00
Marcus Moore 2d1d90e38c Add comment 2025-12-04 14:34:50 -08:00
Marcus Moore dcbdc6fcb8 WIP 2025-12-04 14:27:53 -08:00
Marcus Moore 5a4ef15de5 Avoid rendering rule if last item in loop 2025-12-04 14:22:13 -08:00
Marcus Moore affc4c8bd9 Styling 2025-12-04 14:07:10 -08:00
Marcus Moore bc5d6e89ba Readability 2025-12-04 14:05:22 -08:00
Marcus Moore c17e6811d2 Group categories visually 2025-12-04 14:03:11 -08:00
Marcus Moore 7f097c029a Fix indent 2025-12-04 13:52:26 -08:00
Godfrey M c8e8eb58aa adds TZe_241 based on TZe_18mm sizes 2025-12-04 12:41:57 -08:00
akemidx 60f5affe43 more sharing framewor. tests outlines 2025-12-04 13:48:36 -05:00
Godfrey M 0b087ca77d adjust 2d barcode 20% bigger, scale fields, center label more 2025-12-04 09:55:47 -08:00
snipe 444083ec5d Merge remote-tracking branch 'origin/develop' 2025-12-04 17:54:31 +00:00
snipe bf01a11fec Fixed label colors in dark/light 2025-12-04 17:54:19 +00:00
snipe 8f232421d2 Merge remote-tracking branch 'origin/develop' 2025-12-04 17:37:58 +00:00
snipe dbc688ad6e Set '#ffffff' as default 2025-12-04 17:37:44 +00:00
snipe 6217a721ac Merge remote-tracking branch 'origin/develop' 2025-12-04 15:11:38 +00:00
snipe c2ba937ac6 Merge pull request #18295 from grokability/#18288-allow-reference-editing-if-edit-profile-is-disabled
Fixed #18288: Allow users to edit display preferences even if profile editing is not enabled
2025-12-04 15:11:11 +00:00
snipe d860786221 Re-add button 2025-12-04 13:21:54 +00:00
snipe 621ce1777f Fixed #18288: Allow users to change preferences even if profile editing is not permitted 2025-12-04 13:21:04 +00:00
snipe 4f610ac1af Added tag color to importer 2025-12-04 11:07:59 +00:00
snipe 7341cd1712 Merge pull request #18294 from grokability/#18278-import-company-in-locations
Fixed #18278: Import companies into locations on initial import
2025-12-04 11:07:18 +00:00
snipe bf112b7b4b Fixed #18278: Import companies into locations on initial import 2025-12-04 10:59:45 +00:00
snipe ad9e0cc39a Merge remote-tracking branch 'origin/develop' 2025-12-04 10:50:09 +00:00
snipe 1439681113 Alert message link text 2025-12-04 10:34:58 +00:00
snipe 3b750541c9 Prod assets 2025-12-04 10:19:12 +00:00
snipe 79765201ac Merge remote-tracking branch 'origin/develop' 2025-12-04 10:18:37 +00:00
snipe 0086b9d848 mix 2025-12-04 10:17:17 +00:00
snipe c8ddb44783 Fixed button color 2025-12-04 10:16:44 +00:00
snipe d4829a4bac Fixed #18286 - :user in declined email 2025-12-04 10:16:35 +00:00
snipe 486f0c0035 Re-running assets for prod 2025-12-04 08:46:53 +00:00
snipe dc3a695ab0 Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	config/version.php
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/css/dist/skins/_all-skins.css
#	public/css/dist/skins/_all-skins.css.map
#	public/css/dist/skins/_all-skins.min.css
#	public/css/dist/skins/skin-black-dark.css
#	public/css/dist/skins/skin-black-dark.css.map
#	public/css/dist/skins/skin-black-dark.min.css
#	public/css/dist/skins/skin-black.css
#	public/css/dist/skins/skin-black.css.map
#	public/css/dist/skins/skin-black.min.css
#	public/css/dist/skins/skin-blue-dark.css
#	public/css/dist/skins/skin-blue-dark.css.map
#	public/css/dist/skins/skin-blue-dark.min.css
#	public/css/dist/skins/skin-blue.css
#	public/css/dist/skins/skin-blue.css.map
#	public/css/dist/skins/skin-blue.min.css
#	public/css/dist/skins/skin-contrast.css
#	public/css/dist/skins/skin-contrast.css.map
#	public/css/dist/skins/skin-contrast.min.css
#	public/css/dist/skins/skin-green-dark.css
#	public/css/dist/skins/skin-green-dark.css.map
#	public/css/dist/skins/skin-green-dark.min.css
#	public/css/dist/skins/skin-green.css
#	public/css/dist/skins/skin-green.css.map
#	public/css/dist/skins/skin-green.min.css
#	public/css/dist/skins/skin-orange-dark.css
#	public/css/dist/skins/skin-orange-dark.css.map
#	public/css/dist/skins/skin-orange-dark.min.css
#	public/css/dist/skins/skin-orange.css
#	public/css/dist/skins/skin-orange.css.map
#	public/css/dist/skins/skin-orange.min.css
#	public/css/dist/skins/skin-purple-dark.css
#	public/css/dist/skins/skin-purple-dark.css.map
#	public/css/dist/skins/skin-purple-dark.min.css
#	public/css/dist/skins/skin-purple.css
#	public/css/dist/skins/skin-purple.css.map
#	public/css/dist/skins/skin-purple.min.css
#	public/css/dist/skins/skin-red-dark.css
#	public/css/dist/skins/skin-red-dark.css.map
#	public/css/dist/skins/skin-red-dark.min.css
#	public/css/dist/skins/skin-red.css
#	public/css/dist/skins/skin-red.css.map
#	public/css/dist/skins/skin-red.min.css
#	public/css/dist/skins/skin-yellow-dark.css
#	public/css/dist/skins/skin-yellow-dark.css.map
#	public/css/dist/skins/skin-yellow-dark.min.css
#	public/css/dist/skins/skin-yellow.css
#	public/css/dist/skins/skin-yellow.css.map
#	public/css/dist/skins/skin-yellow.min.css
#	public/js/dist/all.js
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2025-12-04 08:45:19 +00:00
akemidx e769239213 only user can change report sharing 2025-12-04 00:39:47 -05:00
akemidx 45923a74f6 only user can change report sharing 2025-12-04 00:34:30 -05:00
Marcus Moore 7bf7a87f8a Begin to display EULAs for all categories 2025-12-03 16:59:57 -08:00
Marcus Moore 8396e27a2c Revert "Avoid showing EULA"
This reverts commit 8c89eb6650.
2025-12-03 16:16:30 -08:00
Marcus Moore 5153c68b8b Remove old todos 2025-12-03 16:15:43 -08:00
snipe 3feee682b6 Fixed import view with light/dark 2025-12-03 21:38:57 +00:00
snipe 4ea5cb9538 Merge pull request #18284 from Godmartinz/fix-alert-threshold-bug
Fixes Low Inventory Alerts not sending when threshold is `null`
2025-12-03 21:32:29 +00:00
Godfrey M f6461a755a revert again 2025-12-03 13:22:46 -08:00
Godfrey M 39cf5ce66e revert some changes 2025-12-03 13:22:03 -08:00
Godfrey M c68d9892b5 fix threshold bug, include minimum to available 2025-12-03 13:20:01 -08:00
snipe 33b20b6268 Fixed sidemenu hover 2025-12-03 19:27:44 +00:00
snipe 280df20a0b Better default link colors for skin migration 2025-12-03 18:18:24 +00:00
snipe 7027cd80d4 Updated modal for light/dark 2025-12-03 18:18:14 +00:00
snipe bfbcfe7bae Updated text string 2025-12-03 17:36:48 +00:00
snipe e1f64b6d2b Modal title color fix 2025-12-03 17:36:39 +00:00
snipe 6944c438dd Inline tooltip improvements 2025-12-03 16:04:23 +00:00
snipe 49b7ff1192 Fixed email field color 2025-12-03 15:58:50 +00:00
snipe 662cdbaa0e Datepicker color fixes 2025-12-03 15:55:04 +00:00
snipe e912eb5ef8 Merge pull request #18279 from uberbrady/new_allowlist_restore_cleaner
Loosen regex allowlist for setting character sets
2025-12-03 13:31:01 +00:00
Brady Wetherington 5ab68d83a5 Loosen regex allowlist for setting character sets 2025-12-03 13:06:02 +00:00
snipe f7c432f7fd Bumped hash 2025-12-03 10:09:23 +00:00
snipe 592ccb6ebe Merge pull request #18244 from Godmartinz/receive-all-expired-licenses-on-report
adds #17422 [FD-49345] `--expired-licenses` command parameter to `snipeit:expiring-alerts`
2025-12-03 09:49:49 +00:00
snipe d22d70dd92 Merge pull request #18257 from iryadifarhan/fix/manager-view-not-displaying-subordinates-eulas-properly
Fixes Manager View not displaying subordinates EULAs properly in View Assets page
2025-12-03 09:49:35 +00:00
snipe 7ec5606ce4 Merge pull request #18247 from uberbrady/multi_create_fixes
Fixed #18160 - Multi-create fixes
2025-12-03 09:49:14 +00:00
snipe 476bf95edf Merge pull request #18272 from spencerrlongg/add-null-checks
Adds Null Checks
2025-12-03 09:48:43 +00:00
Marcus Moore 391495dd86 Remove some assertions 2025-12-02 17:41:22 -08:00
Marcus Moore 8c89eb6650 Avoid showing EULA 2025-12-02 17:37:21 -08:00
Marcus Moore 4167c6ea70 Add some translations 2025-12-02 17:36:05 -08:00
Marcus Moore ca3151ce29 Improve naming 2025-12-02 16:10:53 -08:00
akemidx cc20844eff share template bits 2025-12-02 18:34:13 -05:00
Marcus Moore d876e710e4 Be more specific in tests 2025-12-02 13:48:59 -08:00
Marcus Moore 5c1290425b Improve variable name 2025-12-02 13:28:59 -08:00
Marcus Moore 2043488c67 Cleanups 2025-12-02 12:31:04 -08:00
Marcus Moore e7e48c8f03 Cleanups 2025-12-02 12:30:16 -08:00
Marcus Moore dad650b804 Readability 2025-12-02 12:28:51 -08:00
Marcus Moore d0e73714c6 Implement test 2025-12-02 11:57:02 -08:00
Marcus Moore d8b95d3a20 Organization 2025-12-02 11:53:37 -08:00
Marcus Moore 559d8cc0db Implement test 2025-12-02 11:53:01 -08:00
Marcus Moore 7a804aa576 Implement test 2025-12-02 11:51:28 -08:00
snipe cdf036ed7b Merge pull request #18274 from spencerrlongg/check-that-email-exists-on-recipient
Check That Email Exists on Recipient in Checkout Acceptance
2025-12-02 19:35:28 +00:00
snipe 639a3b9295 Merge pull request #18273 from Godmartinz/fix-null-assignee-bug-in-checkoutable
Refactor `assignee` in Checkoutable to accept null
2025-12-02 19:31:22 +00:00
spencerrlongg e4f8c3bef7 add null check and check for email 2025-12-02 13:17:13 -06:00
Godfrey M 462945022c allow null for assignee in checkoutable 2025-12-02 11:05:07 -08:00
spencerrlongg aa57687df0 add null checks to license 2025-12-02 12:13:38 -06:00
snipe 3237a3b9de One more button fix 2025-12-02 16:44:31 +00:00
snipe ff30e109cc Small button fixes 2025-12-02 16:42:32 +00:00
snipe 2d291f843a Fixed create new on table buttons 2025-12-02 16:36:54 +00:00
snipe 7219fc1c3c Added style to border on colorpicker 2025-12-02 16:22:53 +00:00
snipe ed6bfa7810 Disable branding colopickers on demos 2025-12-02 16:16:33 +00:00
snipe ad15090c34 Nicer search highlight box 2025-12-02 15:58:34 +00:00
snipe 9d34bf4a19 Fixed table header colors 2025-12-02 15:54:18 +00:00
snipe 7c9b1a52af Swapped link colors 2025-12-02 15:39:32 +00:00
snipe dd297dca31 Merge pull request #18249 from grokability/proper-dark-toggle
Experiment: simpler light/dark toggle
2025-12-02 14:37:52 +00:00
snipe 1409d01078 Tweaked color 2025-12-02 14:19:53 +00:00
snipe c9a03cf9b7 Final color tweaks 2025-12-02 14:03:08 +00:00
snipe 6a99132e76 More tweaks 2025-12-02 13:29:12 +00:00
Marcus Moore 0bca66b671 Send email if asset has checkin_email set to true 2025-12-01 16:58:02 -08:00
Marcus Moore 24e5cf8121 Improve readability 2025-12-01 16:51:09 -08:00
Marcus Moore ee7c4ce0f3 Improve assertion 2025-12-01 16:47:23 -08:00
Marcus Moore 428b511687 Send if eula is set 2025-12-01 16:41:50 -08:00
Marcus Moore 49497996d5 Fix template 2025-12-01 16:41:39 -08:00
Marcus Moore bccd65e2fc Add failing test 2025-12-01 16:33:46 -08:00
Marcus Moore f2158843ce Avoid attempting to loop over null 2025-12-01 16:25:37 -08:00
Marcus Moore 87fc4a4f22 Scaffold scenarios 2025-12-01 16:01:34 -08:00
Marcus Moore 27291f9ee9 Add todo 2025-12-01 14:23:33 -08:00
Marcus Moore cba963110e Remove unused import 2025-12-01 14:22:06 -08:00
Marcus Moore aa014e3706 Improve wording 2025-12-01 14:19:54 -08:00
akemidx 58d577f67a shared/notshared marker 2025-12-01 16:48:39 -05:00
Marcus Moore cd3678841b Fix intro line to locations 2025-12-01 13:41:23 -08:00
Marcus Moore 425e0c33df Add tests for introduction line 2025-12-01 12:55:39 -08:00
Marcus Moore 2018407782 Avoid error by pre-checking if user has email address 2025-12-01 12:04:53 -08:00
snipe 2e269d2e63 Removed old skin less files 2025-11-29 10:42:10 +00:00
snipe 7820636c9f Nicer defaults 2025-11-29 10:41:58 +00:00
snipe db1b35ccf6 Fixed style 2025-11-28 19:20:41 +00:00
snipe fadfe0a782 Removed old skin references 2025-11-28 19:17:52 +00:00
snipe 255a2ecdd9 Sigh 2025-11-28 19:09:38 +00:00
snipe 97ffe33fc8 Check that a setting record exists 2025-11-28 19:07:46 +00:00
snipe 56d97a1f59 Updated map 2025-11-28 19:05:25 +00:00
snipe 28d5d24617 Migration to handle skins 2025-11-28 19:05:19 +00:00
snipe d97f6903d6 Save settings controller 2025-11-28 19:05:03 +00:00
snipe 3bf84d96d9 Update language 2025-11-28 19:04:49 +00:00
snipe 8df643a2ab Removed user skin option 2025-11-28 19:04:40 +00:00
snipe 2d001c4fa1 Added colorpickers for link colors 2025-11-28 19:04:20 +00:00
snipe cbd6b57445 Removed skin from user profile update 2025-11-28 19:04:00 +00:00
snipe dac684c08a Update demo settings 2025-11-28 19:03:48 +00:00
snipe 772c29791a Use css variable 2025-11-28 17:48:08 +00:00
snipe 89a232ae14 Merge pull request #18266 from Valinwolf/develop
Added endpoint & use_path_style_endpoint configs for public/private S3
2025-11-28 17:39:23 +00:00
snipe 4e4b8ddb77 And more updates 2025-11-28 17:33:30 +00:00
Patrick Thomas 6eaefa0bdd Added endpoint & use_path_style_endpoint configs for public/private S3 2025-11-28 17:29:02 +00:00
snipe 20a75bbbb7 More styles 2025-11-28 15:54:24 +00:00
snipe 5cc261dd3c Smaller LDAP screen 2025-11-28 15:50:37 +00:00
snipe 6d958b6f65 Added fa-fw to arrow class 2025-11-28 15:50:25 +00:00
snipe 8ddac4d7c7 More select2 styling :( 2025-11-28 15:16:22 +00:00
snipe a321ad9dbe Handle select2 stuff 2025-11-28 14:13:19 +00:00
snipe 4dff66253c Added contrast-color to dynamically pick white/black for topnav 2025-11-27 16:15:35 +00:00
snipe 9a1e9f90bc Better preview for header color, updated text 2025-11-27 15:56:06 +00:00
snipe c54724919c Show header color preview 2025-11-27 15:29:24 +00:00
snipe 139d1cdcf8 Added a few more classes 2025-11-27 14:48:28 +00:00
snipe 490c50a182 Added fa-fw to action buttons 2025-11-27 14:48:16 +00:00
snipe af1e496eab Added correct box class 2025-11-27 13:30:11 +00:00
snipe efea043549 Added dark/light text 2025-11-27 13:29:59 +00:00
snipe d4ee91f013 Removed a few classes 2025-11-27 13:29:50 +00:00
iryadifarhan d4561581ad Apply fix around view-assets to pass request parameter and profile controller to address request parameter 2025-11-27 13:42:47 +07:00
snipe a17f167952 Fix button overrides 2025-11-26 15:41:38 +00:00
snipe 5beb068cde More updates for dark and light switches 2025-11-26 15:35:43 +00:00
snipe a272bdc796 Merge pull request #18251 from uberbrady/improve_component_asset_counts
Optimize queries for Components listing
2025-11-26 14:40:14 +00:00
snipe 30a43089a0 More variablization 2025-11-26 13:50:39 +00:00
Brady Wetherington 416b32cbc8 Optimize queries for Components listing 2025-11-26 12:36:44 +00:00
iryadifarhan a3a49e47b7 Apply toDateString so that the it equally compare date only, evades including time/hour comparing 2025-11-26 13:21:15 +07:00
snipe d203cece0e Removed indicidual skins 2025-11-26 04:08:31 +00:00
snipe 9f6f0f04c7 Few more tweaks 2025-11-26 04:06:20 +00:00
iryadifarhan a4729a7de8 Fix implemented by using existing function from Depreciable trait, hence addressing the floor value 2025-11-26 10:17:21 +07:00
snipe a974c6d4cd Moved icon-med 2025-11-26 02:56:05 +00:00
snipe 34612acdcf Experiment: light/dark simplifcation 2025-11-26 02:49:40 +00:00
snipe 9e23117f9c Merge remote-tracking branch 'origin/develop' 2025-11-26 00:35:26 +00:00
snipe b3996f1970 In the sea, @uberbrady! (fixed missing semicolon) 2025-11-26 00:35:16 +00:00
snipe e143017432 Tinkering with CSS/JS dark more 2025-11-26 00:34:46 +00:00
Brady Wetherington c6c0a14ee0 Whoops, used PHP's equal signs instead of MySQL's :/ 2025-11-25 20:23:15 +00:00
Brady Wetherington 9b8768dbdd Tighten everything up, remove logging, fix last bits of functionality 2025-11-25 19:34:10 +00:00
snipe a3bfcc962d Merge remote-tracking branch 'origin/develop' 2025-11-25 18:50:23 +00:00
snipe ca4ed605a8 Merge pull request #18246 from Godmartinz/resize-label-fields-conditionally-L6009_A
Fixes [FD-52064] Avery `L6009_A` & `L4736_A` label field overflow with scaling
2025-11-25 18:37:06 +00:00
Godfrey M d3e6d7442f typo bonanza 2025-11-25 10:30:56 -08:00
Godfrey M b558bc5334 change variable" 2025-11-25 10:30:06 -08:00
Godfrey M 204d7b5be6 added scaling to L4736_A 2025-11-25 10:23:08 -08:00
Godfrey M 7dccfec332 adds notes 2025-11-25 10:12:27 -08:00
snipe 0b694bfd0b Merge remote-tracking branch 'origin/develop' 2025-11-25 18:02:23 +00:00
snipe dfb59d8a55 Added link to rudder2snipe repo 2025-11-25 18:01:29 +00:00
Brady Wetherington 3cd191210c Merge branch 'develop' into multi_create_fixes 2025-11-25 13:58:56 +00:00
snipe 56a44ad421 Merge remote-tracking branch 'origin/develop' 2025-11-25 13:38:54 +00:00
snipe a12ee3c0da Merge pull request #18245 from uberbrady/redirect_upgrader
Add new 'git remote' management to change Snipe-IT URLs
2025-11-25 13:38:31 +00:00
Brady Wetherington a657c479be Add new 'git remote' management to change Snipe-IT URL's 2025-11-25 13:17:56 +00:00
Godfrey M bb7dabc73b adds expired-licenses command parameter 2025-11-24 11:41:50 -08:00
Godfrey M ab82c5fd88 resizes label field box to size if needed 2025-11-24 11:04:23 -08:00
snipe 78cfb19f69 Merge remote-tracking branch 'origin/develop' 2025-11-24 17:40:26 +00:00
snipe f2334082ee Added tag color to location query 2025-11-24 17:40:13 +00:00
snipe 58a47cb52b Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	config/version.php
2025-11-24 12:32:17 +00:00
snipe c7cb4674f5 Bumped version 2025-11-24 12:31:50 +00:00
snipe 523df21d83 Merge remote-tracking branch 'origin/develop' 2025-11-24 12:30:22 +00:00
snipe 82314076a9 Updated translations 2025-11-24 12:30:07 +00:00
snipe d04bf2e8f2 Merge remote-tracking branch 'origin/develop' 2025-11-24 11:37:44 +00:00
snipe a1c67b5154 Fixed user details toggle 2025-11-24 11:37:14 +00:00
snipe 3e343fe8b7 Merge pull request #18236 from grokability/dependabot/github_actions/develop/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-11-24 08:33:46 +00:00
dependabot[bot] e288c942ee Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 08:04:19 +00:00
snipe 7a1ccb0d53 Nicer query 2025-11-23 14:09:55 +00:00
snipe 89656c7e65 Merge remote-tracking branch 'origin/develop' 2025-11-23 14:05:39 +00:00
snipe 7f76198139 Fixed RB-20501 - correctly return error response when license+seat don’t match
TypeError: App\Http\Transformers\LicenseSeatsTransformer::transformLicenseSeat(): Argument #1 ($seat) must be of type App\Models\LicenseSeat, bool given, called in /snipe-it/app/Http/Controllers/Api/LicenseSeatsController.php on line 92
2025-11-23 14:05:24 +00:00
snipe 5ff813f9b7 Merge pull request #18233 from grokability/develop
Merging from dev
2025-11-22 14:24:36 +00:00
snipe 69994e0c11 Merge pull request #18232 from grokability/check-for-valid-json-on-filter
Added filter form request to validate JSON
2025-11-22 14:22:33 +00:00
snipe aa959fbe92 Added filter form request to validate JSON 2025-11-22 14:03:58 +00:00
snipe 948b7cda15 Merge remote-tracking branch 'origin/develop' 2025-11-22 12:04:28 +00:00
snipe 644ef040d0 Merge pull request #18226 from Godmartinz/audit-notification-rb-19608
Fixes RB-19608 adds safe guards, adds Audit notification for google workspace, adds tests
2025-11-22 11:41:57 +00:00
snipe 33839d7244 Re-added the horizontal class 2025-11-22 11:14:59 +00:00
Godfrey M 48270cb9b4 url trim 2025-11-21 10:11:28 -08:00
Godfrey M cd42760b68 notes 2025-11-20 14:25:13 -08:00
Godfrey M 1116da389e add google chat to audit notification test 2025-11-20 14:24:03 -08:00
Godfrey M 24d7ae4a2f added google chat to audit notifications" 2025-11-20 14:22:08 -08:00
Godfrey M e598ef6e05 adds tests, webhook enable settings 2025-11-20 13:51:14 -08:00
snipe 6601e73069 Merge remote-tracking branch 'origin/develop' 2025-11-20 20:15:44 +00:00
snipe 7f2a80552b Fixed form layout for SAML 2025-11-20 20:15:34 +00:00
Godfrey M eceeb4aa3b return if there is no item for the audit notification 2025-11-20 10:56:43 -08:00
snipe b5c7f374f3 Merge remote-tracking branch 'origin/develop' 2025-11-20 13:44:19 +00:00
snipe 9d08e2d297 Clearer language 2025-11-20 13:44:09 +00:00
snipe f2303ae2dc Merge remote-tracking branch 'origin/develop' 2025-11-20 13:28:00 +00:00
snipe 4d44fd47c3 Merge pull request #18217 from Godmartinz/unaccepted-row-null-fix
Fixes FD-52005 Adds null safe operators to unacceptable items report
2025-11-20 13:27:24 +00:00
snipe 88635cb6c3 Merge pull request #18223 from grokability/#18114-fixed-declined-notification-added-x-header
Fixed #18114 - include declined item name, added app headers
2025-11-20 13:27:01 +00:00
snipe 1687fcc035 Small subject tweaks 2025-11-20 13:23:17 +00:00
snipe 39e7937458 Fixed #18114 - include declined item name, added app headers 2025-11-20 12:58:53 +00:00
Marcus Moore 54f065f42c Improve test 2025-11-19 17:11:39 -08:00
Marcus Moore 333ebb88b9 Enable sending to manager 2025-11-19 17:08:00 -08:00
Marcus Moore 53ff367473 Add failing tests 2025-11-19 16:31:48 -08:00
Marcus Moore 33a7de9448 Add custom fields to email 2025-11-19 13:31:30 -08:00
Godfrey M 60ff2970f0 safeguard null rows by filtering rows in query 2025-11-19 11:35:24 -08:00
Godfrey M 212cd026a3 add null safe operators to company and csv plain values 2025-11-18 12:36:07 -08:00
snipe cf47f8fea9 Merge pull request #18216 from Godmartinz/fix-license-patch-to-allow-update-of-other-fields
Fixed #18024 License Seat update/patch method
2025-11-18 20:29:18 +00:00
akemidx c797472bcc migration, and front end 2025-11-18 15:03:28 -05:00
Godfrey M 4b45ffd841 updating fields of checkout now works 2025-11-18 11:35:26 -08:00
snipe 69a57b77c9 Merge remote-tracking branch 'origin/develop' 2025-11-18 13:13:48 +00:00
snipe 5daba6034d Merge pull request #18213 from grokability/#18202-copy-to-clipboard-spaces
Fixed #18202 - copy to clipboard adding spaces in FF
2025-11-18 13:13:29 +00:00
snipe 4e5c19e932 Fixed #18202 - copy to clipboard adding spaces in FF 2025-11-18 13:11:40 +00:00
snipe 5eb73baf5e Removed duplicate formatter 2025-11-18 12:54:21 +00:00
snipe d819f31bdd Merge remote-tracking branch 'origin/develop' 2025-11-18 12:01:01 +00:00
snipe a97c453706 Small fixes for user profiles 2025-11-18 11:59:54 +00:00
snipe 5ee955a713 Merge remote-tracking branch 'origin/develop' 2025-11-18 11:40:23 +00:00
snipe 57224f7304 Fixed #18209 - translate asset filters 2025-11-18 11:40:15 +00:00
snipe 270d145466 Merge remote-tracking branch 'origin/develop' 2025-11-18 11:15:31 +00:00
snipe c564ee6093 Fixed #18211 - limit regex field to 191 characters 2025-11-18 11:15:15 +00:00
snipe caaa9ab23e Merge pull request #18122 from chruoss/master
Renamed L6009 -> L4736 and added correct L6009
2025-11-17 21:40:08 +00:00
snipe b6d2c6d28c Merge pull request #18208 from marcusmoore/fixes/null-array-filter
Fixed potential exception while filtering in users index endpoint
2025-11-17 21:39:08 +00:00
Marcus Moore c36c8968d3 Avoid passing null to array_filter in user controller 2025-11-17 10:26:29 -08:00
snipe b019af1851 Merge remote-tracking branch 'origin/develop' 2025-11-17 17:50:30 +00:00
snipe bcd32da2bc Remove loading user count since we don’t use it for depts 2025-11-17 17:34:43 +00:00
snipe 6f97a40372 Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/js/dist/all.js
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2025-11-17 13:37:16 +00:00
snipe c56c7ddd03 Merge pull request #18201 from grokability/#18185-added-tag-color-to-companies
Added #18185 added tag color to companies
2025-11-17 13:33:17 +00:00
snipe 9a0dd604c9 Small layout tweaks 2025-11-17 13:21:01 +00:00
snipe a1dec176a1 Added mobile 2025-11-17 13:09:13 +00:00
snipe 67032d068d Removed space 2025-11-17 13:09:08 +00:00
snipe 401c83945d Updated gate check on presenters 2025-11-17 13:07:28 +00:00
snipe ed6d020edb Fixed permission name for locations 2025-11-17 12:31:18 +00:00
snipe 8eb5600b1e Fixed manufacturer link 2025-11-17 12:19:27 +00:00
snipe 9c4cd69106 Updated checkout blades 2025-11-17 12:15:38 +00:00
snipe 64cbe0c960 Use dept presenter 2025-11-17 12:15:25 +00:00
snipe be70b217d9 Updated presenters 2025-11-17 12:11:38 +00:00
snipe b4f5260dda Updated controllers to use tag_color 2025-11-17 11:36:11 +00:00
snipe 83e446f99c Updated formatters to use tag_color if given 2025-11-17 11:18:38 +00:00
snipe 767139cf0b Updated select2 to use tag_color if available 2025-11-17 11:18:21 +00:00
snipe e3ac60111f Added tag_color to factories 2025-11-17 11:18:02 +00:00
snipe c694c11724 Added presenter reference 2025-11-15 14:58:02 +00:00
snipe b50765a151 Added more to the views 2025-11-15 14:50:51 +00:00
snipe 590f77bdb4 Updated controllers and presenters 2025-11-14 17:58:27 +00:00
snipe 09575e5312 Added tag color to transformer 2025-11-14 15:58:07 +00:00
snipe 1693825dd0 Added translations 2025-11-14 15:57:57 +00:00
snipe 5a89056112 Added migration 2025-11-14 15:57:47 +00:00
snipe 2ee51bb282 Added tag color to transformer 2025-11-14 15:57:39 +00:00
snipe ea7cffc1a3 Made tag_color fillable 2025-11-14 15:57:27 +00:00
snipe c20d1b82ae Added tag color to controllers 2025-11-14 15:57:18 +00:00
snipe 607781382f Merge remote-tracking branch 'origin/develop' 2025-11-14 14:10:05 +00:00
snipe 4e8c1e853b Fixed markdown in upcoming audits email 2025-11-14 14:09:53 +00:00
snipe 2c6869501e Merge remote-tracking branch 'origin/develop' 2025-11-13 16:46:32 +00:00
snipe 4509e1d1dc Merge pull request #18195 from grokability/#18189-day-of-week
Fixed #18189 - added option to pick the day the week starts on
2025-11-13 16:46:15 +00:00
snipe 6b9d4941be Language tweaks 2025-11-13 16:35:40 +00:00
snipe 308cd6b91d Fixed #18189 - added option to pick the day the week starts on 2025-11-13 16:33:08 +00:00
snipe 23b54de8bd Merge remote-tracking branch 'origin/develop' 2025-11-13 15:37:42 +00:00
snipe bd742aec9c Merge pull request #18192 from Godmartinz/fix-requestable-asset-model-query
Fixes FD51910 requestable asset model available quantity count
2025-11-13 15:37:24 +00:00
snipe d2ab3071a6 Merge pull request #18193 from ubc-cpsc/bugfix/CVE-2025-64500
Fixes CVE-2025-64500: Incorrect parsing of PATH_INFO can lead to limited authorization
2025-11-13 09:52:50 +00:00
Joël Pittet 1dd4c161f0 Fixes CVE-2025-64500 2025-11-12 18:09:37 -08:00
Godfrey M 5e48dd45b2 remove unnecessary code 2025-11-12 16:27:04 -08:00
Godfrey M 601d6e7377 fixes requestable models query 2025-11-12 16:25:08 -08:00
snipe 3934b40282 Merge remote-tracking branch 'origin/develop' 2025-11-12 20:59:15 +00:00
snipe d43be271e6 Small tweaks to user group listings 2025-11-12 20:59:06 +00:00
snipe d3c9963051 Merge remote-tracking branch 'origin/develop' 2025-11-12 20:25:00 +00:00
snipe 92a3bdf4e9 Merge pull request #18191 from grokability/limit-adding-users-to-group-if-over-limit
Set a limit on number of users for group user loading
2025-11-12 20:24:24 +00:00
snipe fa2aafe41f Set a limit on number of users for group user loading 2025-11-12 20:19:52 +00:00
snipe bc5da2532c Merge remote-tracking branch 'origin/develop' 2025-11-12 16:02:01 +00:00
snipe a7be1acbd8 Merge pull request #18190 from grokability/improved-checkin-reminders
Improved overdue checkin alert
2025-11-12 16:01:32 +00:00
snipe fbe2ae03ff Use due_checkin_days setting instead of audit warning days 2025-11-12 15:52:16 +00:00
snipe d1a492f953 Improved overdue checkin alert 2025-11-12 15:38:47 +00:00
snipe ac6ea8bdcc Merge remote-tracking branch 'origin/develop' 2025-11-11 18:43:54 +00:00
snipe 352807c2d7 Fixed RB-20498 - Check that logo file exists on acceptance 2025-11-11 18:43:39 +00:00
Brady Wetherington 70d79c1890 Merge branch 'develop' into multi_create_fixes 2025-11-11 18:20:49 +00:00
Brady Wetherington fb1fde26ce Use a FormRequest to better handle multiple-asset-creation by GUI 2025-11-11 18:00:22 +00:00
snipe dc4cf8496a Merge remote-tracking branch 'origin/develop' 2025-11-11 14:01:54 +00:00
snipe 3aa046bfa7 Include trashed in other acceptance tasks 2025-11-11 14:00:48 +00:00
snipe ed79d21e1b Merge remote-tracking branch 'origin/develop' 2025-11-11 13:53:55 +00:00
snipe 9454ff677b Get deleted objects in unaccepted asset report 2025-11-11 13:53:43 +00:00
snipe 743d340bca Merge remote-tracking branch 'origin/develop' 2025-11-11 13:18:18 +00:00
snipe a5c7b8f609 Small unaccepted items report fixes 2025-11-11 13:17:56 +00:00
snipe 3d1398ab97 Merge remote-tracking branch 'origin/develop' 2025-11-11 12:29:44 +00:00
snipe 9365d1adc6 Merge pull request #18164 from Godmartinz/fix-accessories-redirect-previous-page
Fixes #18138 redirect to Previous Page from accessories edit screen
2025-11-11 12:29:23 +00:00
snipe ddd2a96ac5 Clearer message on no inventory to report 2025-11-11 11:52:05 +00:00
Godfrey M 6faf171007 update asset_deployable translation 2025-11-10 16:58:42 -08:00
snipe 7a680ce4ff Merge remote-tracking branch 'origin/develop' 2025-11-10 21:31:28 +00:00
snipe bf8ff51234 Make suppliers notes nullable by default 2025-11-10 21:31:14 +00:00
snipe 2ac4456f4e Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	config/version.php
2025-11-10 20:58:15 +00:00
snipe 7891f52bd4 Merge pull request #18177 from grokability/add-files-to-suppliers
Fixed #12451: Add file upload support to suppliers, changed notes field to text (from varchar)
2025-11-10 20:57:21 +00:00
snipe baa4a8a461 Migration to change notes on suppliers to text from varchar 2025-11-10 20:53:21 +00:00
snipe 34daffcdf4 Added supplier file uploads 2025-11-10 20:47:45 +00:00
snipe 21baea27a8 Merge pull request #18141 from uberbrady/actiontype_enum_redux
Actiontype enum redux
2025-11-10 18:55:01 +00:00
snipe e1d3714445 Add @MarvelousAnything as a contributor 2025-11-10 14:15:10 +00:00
snipe 8b7e0a0d78 Add @mohammad-ahmadi1 as a contributor 2025-11-10 14:15:01 +00:00
snipe a1cc427c9c Add @smarsching as a contributor 2025-11-10 14:14:51 +00:00
snipe 391aa30da2 Bumped version 2025-11-10 13:49:26 +00:00
snipe 5dc675040d Bumped version 2025-11-10 13:48:59 +00:00
snipe 1258eb6533 Merge pull request #17913 from Godmartinz/add_types_to_unaccepted_asset_report
Fixed #15664 - Adds Accessories, Components, Consumables, and Licenses to Unaccepted Assets report
2025-11-10 12:24:13 +00:00
snipe f92f76b48a Merge remote-tracking branch 'origin/develop' 2025-11-07 13:42:04 +00:00
snipe 83abfc9ca6 Updated language files 2025-11-07 13:41:46 +00:00
snipe 61b6d4dc47 Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/css/dist/skins/_all-skins.css
#	public/css/dist/skins/_all-skins.css.map
#	public/css/dist/skins/_all-skins.min.css
#	public/css/dist/skins/skin-black-dark.css
#	public/css/dist/skins/skin-black-dark.css.map
#	public/css/dist/skins/skin-black-dark.min.css
#	public/css/dist/skins/skin-blue-dark.css
#	public/css/dist/skins/skin-blue-dark.css.map
#	public/css/dist/skins/skin-blue-dark.min.css
#	public/css/dist/skins/skin-green-dark.css
#	public/css/dist/skins/skin-green-dark.css.map
#	public/css/dist/skins/skin-green-dark.min.css
#	public/css/dist/skins/skin-orange-dark.css
#	public/css/dist/skins/skin-orange-dark.css.map
#	public/css/dist/skins/skin-orange-dark.min.css
#	public/css/dist/skins/skin-purple-dark.css
#	public/css/dist/skins/skin-purple-dark.css.map
#	public/css/dist/skins/skin-purple-dark.min.css
#	public/css/dist/skins/skin-red-dark.css
#	public/css/dist/skins/skin-red-dark.css.map
#	public/css/dist/skins/skin-red-dark.min.css
#	public/css/dist/skins/skin-yellow-dark.css
#	public/css/dist/skins/skin-yellow-dark.css.map
#	public/css/dist/skins/skin-yellow-dark.min.css
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2025-11-07 13:26:23 +00:00
snipe b42d6677cc Updated assets 2025-11-07 13:25:45 +00:00
snipe e8c644a600 Merge pull request #18165 from Godmartinz/border-color-create-new-button-fix
Fixes #18140 Changes border color of create New in Dark modes
2025-11-07 13:21:52 +00:00
snipe aa041e39eb Merge remote-tracking branch 'origin/develop'
# Conflicts:
#	public/js/dist/all.js
#	public/js/dist/all.js.map
#	public/mix-manifest.json
2025-11-06 21:12:42 +00:00
snipe 9f6a73b290 Merge pull request #18170 from grokability/fix-for-groups
Fixed #18157 - only partial information stored on group save if lower `max_input_vars` and/or `max_multipart_body_parts`
2025-11-06 21:09:42 +00:00
snipe 4d38bd1c62 Renamed variable 2025-11-06 21:07:52 +00:00
snipe 138262114d Added enctype back in 2025-11-06 21:04:55 +00:00
snipe 4073c9e638 Updated ordering 2025-11-06 21:01:20 +00:00
snipe f1b4877a98 A few small tweaks for new groups 2025-11-06 20:36:51 +00:00
snipe c39c92d0d7 Mash the ids into a string, fixed disclosure arrows 2025-11-06 20:12:25 +00:00
snipe 90fc48d959 Shim workaround to avoid max_input_vars and max_multipart_body_parts limits
max_input_vars = 2000
max_multipart_body_parts = 2048
2025-11-06 18:17:40 +00:00
Godfrey M 7f10a53105 dark blue 2025-11-05 12:47:10 -08:00
Godfrey M 7ae1b7a765 change border-color of create new 2025-11-05 12:40:29 -08:00
Godfrey M 22a43a0463 adds redirect option fix to accessories update method 2025-11-05 12:19:18 -08:00
Godfrey M 9348204987 remove spacing adjustment 2025-11-05 11:22:11 -08:00
Godfrey M 776c7caaa5 get warning/success status check to display 2025-11-05 11:19:05 -08:00
Godfrey M 2216b83ca7 get warning/success status check to display 2025-11-05 11:16:35 -08:00
snipe dea399398a Merge pull request #18161 from Godmartinz/TZE_24mm_E_adjustment
FD-50838: Fixes 24mm_E label sizing
2025-11-05 18:50:42 +00:00
Godfrey M 7434dd9458 final adjustments to 24mm_E label 2025-11-05 09:32:21 -08:00
snipe 723abca34a Merge remote-tracking branch 'origin/develop' 2025-11-05 15:28:40 +00:00
snipe 88e532dbc4 Fixed #18157 - reports permission glitch 2025-11-05 15:24:15 +00:00
snipe 32b28327e9 Merge remote-tracking branch 'origin/develop' 2025-11-04 21:42:05 +00:00
snipe c5ad451c39 Merge pull request #18156 from grokability/#18119-fix-double-helpering-on-dates-for-asset-acceptance
Fixed #18119 - double formatting for acceptance/decline date
2025-11-04 21:40:58 +00:00
snipe 44bfceeb0f Fixed #18119 - double formatting for acceptance/decline date 2025-11-04 21:36:27 +00:00
snipe c30131275f Merge remote-tracking branch 'origin/develop' 2025-11-04 21:23:14 +00:00
snipe 37eb63837b Merge pull request #18155 from grokability/#18021-fix-patch-api-with-unique-serial
Override unique_undeleted in the form request
2025-11-04 21:23:00 +00:00
snipe 4ada47e3b0 Use new setting variable since we already have it 2025-11-04 21:12:49 +00:00
snipe e5c55c9ab3 Fixed typo in the comment 2025-11-04 21:11:54 +00:00
snipe 547b3df7b4 Added more commentary on why we’re intefering with the request 2025-11-04 21:08:48 +00:00
snipe 4100f2600c Override unique_undeleted in the form request 2025-11-04 20:43:31 +00:00
snipe 56218dfcb2 Merge remote-tracking branch 'origin/develop' 2025-11-04 19:39:52 +00:00
snipe a9574e8fd6 Fixed #18133 - make the disabled toggle JS so it’s clearer 2025-11-04 19:39:35 +00:00
snipe 0d2a75db0a Merge remote-tracking branch 'origin/develop' 2025-11-04 19:04:25 +00:00
snipe c6269d6bbc Merge pull request #18151 from MarvelousAnything/fixes/test_webhook_content_type
Fix Content-Type Header not being set correctly for testWebhook
2025-11-04 19:03:53 +00:00
snipe eb9d066844 Merge remote-tracking branch 'origin/develop' 2025-11-04 18:56:15 +00:00
snipe c1204a5301 Merge pull request #18152 from grokability/#18020-rework-pr
Re-apply #18020, fixed #15107 (mostly) - added prefix and more options to 2D barcodes
2025-11-04 18:55:54 +00:00
snipe cc5ac65909 Re-apply #18020, fixed #15107 (mostly) - added prefix and more options to label 2D tags 2025-11-04 18:43:35 +00:00
Owen Voskuhl Hayes 9ddc48e3d7 fix the headers field for Guzzle request 2025-11-04 12:03:21 -05:00
snipe 029f3030a7 Merge remote-tracking branch 'origin/develop' 2025-11-04 16:06:35 +00:00
snipe 4a39d7c67a Use transformer for dept update responses 2025-11-04 16:06:19 +00:00
snipe b7a6706591 Merge pull request #18150 from grokability/#18148-dept-api-request-user-count
Fixed #18148 and #17451 - return int for user_count, fixed validation
2025-11-04 16:05:42 +00:00
snipe ddb031f091 Fixed #18148 and #17451 - return int for user_count, fixed validation 2025-11-04 15:56:23 +00:00
snipe e906d25776 Merge pull request #16973 from spencerrlongg/bug/sc-29245
Adds Form Request for Creating Departments
2025-11-04 15:09:38 +00:00
Brady Wetherington 27d7449459 Fix comment to properly reflext the current state of the database 2025-11-04 14:04:37 +00:00
snipe 8c59a8d405 Merged clipboard PR and generated prod assets 2025-11-03 17:39:41 +00:00
snipe 5d8905c997 Merge pull request #18143 from grokability/#18101-make-copy-to-clipboard-more-consistent
Fixed #18101: Make copy to clipboard alignment more consistent
2025-11-03 17:36:54 +00:00
snipe 517f4ce121 Fixed #18101: Make copy to clipboard alignment more consistent 2025-11-03 17:29:39 +00:00
snipe 6809bbd3d5 Merge pull request #18142 from grokability/#18136-copy-asset-name
Fixed #18136 - adds copy to clipbpard to asset name
2025-11-03 15:48:59 +00:00
snipe ab555d05e1 Fixed #18136 - adds copy to clipbpard to asset name 2025-11-03 15:47:32 +00:00
Brady Wetherington f7bc538fdf Intorduce ActionType enum and ensure all logactions are using it 2025-11-03 15:39:31 +00:00
Brady Wetherington 2de66ad5db Merge branch 'fix_incorrect_action_types' into actiontype_enum_redux 2025-11-03 15:31:14 +00:00
snipe 30e16b6213 Added int 2025-11-03 14:10:36 +00:00
snipe 92b50ca7ae Addresses #17994, #16925 2025-11-03 14:10:27 +00:00
snipe 9ee36df979 Merge pull request #18139 from grokability/#18082-add-company-to-seat-view
Fixed #18082: Added user company to checked out licenses
2025-11-03 12:51:29 +00:00
snipe 46d1c14e1a Added user company to checked out licenses 2025-11-03 12:45:33 +00:00
snipe 61895011fb Merge pull request #18131 from Godmartinz/fix-inventory-alert-notification-misfire
Adds a null check for items and threshold in inventory alert notification
2025-10-30 20:11:24 +00:00
Godfrey M 32d43034bd add a null check for items and threshold in inventory alert notification 2025-10-30 10:47:17 -07:00
snipe dfc6cdc127 Merge pull request #18128 from marcusmoore/fixes/17738-category-edit-form-fix
Fixed #17738 - accurately represent checkbox on category edit screen
2025-10-30 16:43:17 +00:00
snipe 16e93f9e18 Merge remote-tracking branch 'origin/master' into develop 2025-10-30 14:03:23 +00:00
snipe 7395b1a4eb Track permission changes 2025-10-30 13:40:24 +00:00
snipe fa98557225 Check that the permissions are really an array
This accounts for weird data in the permissions column
2025-10-30 13:34:50 +00:00
Marcus Moore 894606b62e Remove old tests 2025-10-29 13:16:03 -07:00
Marcus Moore f0a6a0026a Improve phrasing 2025-10-29 13:03:37 -07:00
Marcus Moore 070e0c93be Improve phrasing 2025-10-29 12:46:26 -07:00
Marcus Moore 2b27b733e5 Improve wording 2025-10-29 12:45:45 -07:00
Marcus Moore 0355c2b642 Dynamically adjust checkbox wording 2025-10-29 12:44:34 -07:00
Marcus Moore 3bad19fb56 Improve translation key name 2025-10-29 12:40:39 -07:00
Marcus Moore 0f84d51a48 Improve property name 2025-10-29 12:37:17 -07:00
Marcus Moore 2e8572d9c5 Use v3 syntax for computed properties 2025-10-29 12:35:30 -07:00
Marcus Moore df53d5d966 Skip computing sendCheckInEmail 2025-10-29 12:32:01 -07:00
Marcus Moore 23838959ca Never disable email checkbox 2025-10-29 12:27:12 -07:00
chruoss 6904ad02a2 Renamed L6009.php -> L4736.php
Renamed L6009_A.php -> L4736_A.php
Added correct L6009.php
Added correct L6009_A.php
2025-10-29 15:13:12 +01:00
Marcus Moore 777872d41f Add notification group 2025-10-23 13:53:38 -07:00
Marcus Moore b85d1f184a Remove redundant test 2025-10-23 13:52:52 -07:00
Marcus Moore 02129eeddb Add try/catch 2025-10-23 13:51:53 -07:00
Marcus Moore 2612e0bbc8 Remove unused import 2025-10-23 12:43:12 -07:00
Marcus Moore 3670efacb4 Implement test 2025-10-23 12:39:36 -07:00
Marcus Moore 476611b70f Remove redundant test 2025-10-23 12:27:20 -07:00
Brady Wetherington b96e2fb52c Added migration to fix incorrect action_type in action_log 2025-10-23 15:40:54 +01:00
Marcus Moore 60df2a17f8 Check context when sending to alert address 2025-10-22 16:41:36 -07:00
Marcus Moore f64f4795c1 Send request instead of firing event 2025-10-22 16:39:34 -07:00
Marcus Moore e036f756d5 Improve setup 2025-10-22 16:24:47 -07:00
Marcus Moore 92fd121cae Clean up 2025-10-22 16:12:35 -07:00
Marcus Moore 6307337892 Add scenario 2025-10-22 16:09:26 -07:00
Marcus Moore fc2e35cd32 Improve assertions 2025-10-22 14:23:07 -07:00
Marcus Moore 1811e061aa Populate scenario 2025-10-22 14:21:24 -07:00
Marcus Moore 59037f0d83 Move scenario 2025-10-22 14:18:45 -07:00
Marcus Moore 67edb7d396 Send to alert email 2025-10-22 14:17:32 -07:00
Marcus Moore 0da393f950 Populate scenario 2025-10-22 14:02:16 -07:00
Marcus Moore abd30e551e Clean up 2025-10-22 13:46:19 -07:00
Marcus Moore 6fb2889a92 Clean up 2025-10-22 13:45:00 -07:00
Marcus Moore 54125d27e0 Add scenario 2025-10-22 13:44:14 -07:00
Marcus Moore 41efda5f82 Add todos 2025-10-21 13:22:41 -07:00
Marcus Moore 2aee14a800 Only send mail to target if they have an email address 2025-10-21 13:14:50 -07:00
Marcus Moore be69da0a0d Add test case 2025-10-21 13:13:45 -07:00
Marcus Moore 31a247b55b Add test case 2025-10-21 12:44:50 -07:00
Marcus Moore 33c156be16 Add failing test 2025-10-21 12:43:23 -07:00
Marcus Moore fd66a083d6 Fix assertion 2025-10-21 12:41:51 -07:00
Marcus Moore d276f50fdf Fix assertion 2025-10-21 12:30:27 -07:00
Marcus Moore 4cb748e124 Improve test assertions 2025-10-21 12:29:25 -07:00
Marcus Moore 503e6898c3 WIP 2025-10-20 16:53:36 -07:00
Marcus Moore f34056fe2e Scaffold some testing changes 2025-10-20 16:35:27 -07:00
Marcus Moore 8ff3575442 Add test for listener registration 2025-10-20 16:31:52 -07:00
Marcus Moore b5e3358bbd Add todos 2025-10-20 14:29:05 -07:00
Marcus Moore 047a1197be Add failing conditions 2025-10-20 14:18:11 -07:00
Marcus Moore 0e87843446 WIP: start testing 2025-10-16 14:30:48 -07:00
Marcus Moore 4f1ff328ad Display eula if it is the same for all items 2025-10-14 17:10:57 -07:00
Marcus Moore ad5bbb9b37 Add divider 2025-10-14 16:34:18 -07:00
Marcus Moore c6e2fd2cab Add note 2025-10-14 16:33:37 -07:00
Marcus Moore d3a7e25b86 Move expected checking and only show once 2025-10-14 16:29:59 -07:00
Marcus Moore 062445a48e Add expected checkin 2025-10-14 16:25:32 -07:00
Marcus Moore 3c32be6181 Make introduction line dynamic 2025-10-14 16:22:19 -07:00
Marcus Moore e2f4a9bf9f Make subject dynamic 2025-10-14 16:20:01 -07:00
Marcus Moore 2db4c1b2e4 Add todo 2025-10-14 16:19:42 -07:00
Marcus Moore 6ed93f4a4f Add asset details 2025-10-14 14:02:21 -07:00
Marcus Moore 9dcee71baf wip 2025-10-08 16:18:54 -07:00
Marcus Moore 9bdd0d1d1e Add admin name 2025-10-08 16:05:55 -07:00
Marcus Moore 13b51d8608 Make acceptance section dynamic 2025-10-08 15:46:36 -07:00
Marcus Moore 19969fee39 Update closing 2025-10-08 15:32:38 -07:00
Marcus Moore 28dc4bf52e Move template to correct directory 2025-10-08 14:13:04 -07:00
Marcus Moore 9a380ac3d4 Extract intro text 2025-10-08 14:12:35 -07:00
Marcus Moore 17a26b43f0 Naively send email 2025-10-08 14:01:07 -07:00
Marcus Moore 3c42acebf0 Scaffold new listener 2025-09-30 15:11:12 -07:00
Marcus Moore 3327b2ce3c Add assertions 2025-09-30 13:03:24 -07:00
Godfrey M 8c56aa9575 changed the wrong language file 2025-09-30 12:42:46 -07:00
Godfrey M d528126f15 right hand table loading, need styling and translations 2025-09-30 12:31:44 -07:00
akemidx 64d0e3928c clean erroneous added space 2025-09-30 13:11:38 -04:00
Marcus Moore 7017a0cae1 WIP: introduce event 2025-09-25 16:23:43 -07:00
Marcus Moore a40e4d7d04 Begin experimenting with context 2025-09-25 15:43:04 -07:00
Marcus Moore 9e3b56f4bc Add failing test 2025-09-25 15:41:37 -07:00
Godfrey Martinez ca44ee94a4 Merge pull request #28 from Godmartinz/add_types_to_command
update the snipeit reminder command
2025-09-25 10:44:53 -07:00
Godfrey M 3ae7a77032 update the snipeit reminder command 2025-09-25 10:43:41 -07:00
Godfrey M 881c789a75 add usuage of withoutactionlog 2025-09-24 15:45:01 -07:00
Godfrey M fea0189479 Merge branch 'fix-factory-auto-gen-action-logs' into add_types_to_unaccepted_asset_report 2025-09-24 15:43:27 -07:00
Godfrey M 6ca0e19819 uncomment code 2025-09-24 13:50:41 -07:00
Godfrey M bf6964ee62 tests passing, CheckoutAcceptance Factory needs eyes though 2025-09-24 13:41:04 -07:00
Godfrey M 9ac2ea2a52 fix test to check all mailable types for reminders 2025-09-24 13:22:33 -07:00
Godfrey M 0ce20c1edd fix send reminder method to handle other types 2025-09-23 18:40:03 -07:00
Godfrey M d31d99b40f remove else and blank lines 2025-09-23 17:00:08 -07:00
Godfrey M 3527c357cc whoops 2025-09-23 16:57:06 -07:00
Godfrey M 2b5254e68f fixed, eager loading, variable names, clean up 2025-09-23 16:55:10 -07:00
Godfrey M 6f3323c195 fix data in view model 2025-09-23 12:50:26 -07:00
Godfrey M 5af85bfe7d further clean up 2025-09-23 12:10:50 -07:00
Godfrey M 20adad3c6b fix company name link, clean up query 2025-09-23 12:09:21 -07:00
Godfrey M ca8eae4064 updated the csv values to be plain 2025-09-23 11:40:29 -07:00
Godfrey M 7077faaf4a updated the postassetAcceptanceReport query and rows 2025-09-23 11:08:37 -07:00
Godfrey M ab30df10ff remove sorter from blade 2025-09-22 12:20:32 -07:00
Godfrey M a6cb75c481 remove sorter, didnt work 2025-09-22 12:19:34 -07:00
Godfrey M 51ce570eb3 attempt to sort chronologically, can not resort still 2025-09-18 11:01:40 -07:00
Godfrey M dcbb09bbd7 added checkoutable class 2025-09-16 15:59:53 -07:00
Godfrey M 58eac619ea add checkoutable class, rework blade for unaccepted items 2025-09-16 15:52:41 -07:00
akemidx 3492ed54de i'm dumb, fixed query 2025-09-10 19:05:28 -04:00
akemidx 1455281957 updated to outside window 2025-09-10 18:49:04 -04:00
akemidx e1de57384e field for number choice 2025-08-26 18:03:47 -04:00
akemidx c95462328a selected value(s) 2025-08-19 18:35:07 -04:00
akemidx 84fc89250a backend works, no save yet 2025-08-19 14:30:26 -04:00
akemidx a80f52cbf4 putting dropdown back 2025-08-18 19:47:26 -04:00
akemidx 7ce324a3a5 controller 2025-08-14 17:42:19 -04:00
akemidx 7624082b29 dropdown 2025-08-13 18:07:39 -04:00
akemidx 6d3bba696a first front end bit 2025-08-12 19:14:11 -04:00
spencerrlongg 1b397cd780 assertDbHas 2025-05-21 18:17:45 -05:00
spencerrlongg 120316bae0 wrong location import 2025-05-21 13:36:31 -05:00
spencerrlongg 7571ff007f add fillable properties to rules, some tests, move authorization to request 2025-05-21 12:51:21 -05:00
spencerrlongg 7afd7da2b4 initial work, more testing/tests needed 2025-05-21 11:44:54 -05:00
Matthias Mair 68dad1d3ae fixed wrong index reference in MoveUploadsToNewDisk.php
usage of undefined reference fixed
2023-08-22 20:39:21 +02:00
1971 changed files with 239276 additions and 31773 deletions
+27
View File
@@ -4235,6 +4235,33 @@
"contributions": [
"code"
]
},
{
"login": "smarsching",
"name": "Sebastian Marsching",
"avatar_url": "https://avatars.githubusercontent.com/u/2880129?v=4",
"profile": "http://sebastian.marsching.com/",
"contributions": [
"code"
]
},
{
"login": "mohammad-ahmadi1",
"name": "Mo",
"avatar_url": "https://avatars.githubusercontent.com/u/40658372?v=4",
"profile": "https://github.com/mohammad-ahmadi1",
"contributions": [
"code"
]
},
{
"login": "MarvelousAnything",
"name": "Owen V. Hayes",
"avatar_url": "https://avatars.githubusercontent.com/u/20994684?v=4",
"profile": "https://github.com/MarvelousAnything",
"contributions": [
"code"
]
}
]
}
+4
View File
@@ -137,6 +137,8 @@ PUBLIC_AWS_ACCESS_KEY_ID=null
PUBLIC_AWS_DEFAULT_REGION=null
PUBLIC_AWS_BUCKET=null
PUBLIC_AWS_URL=null
PUBLIC_AWS_ENDPOINT=null
PUBLIC_AWS_PATH_STYLE=null
PUBLIC_AWS_BUCKET_ROOT=null
# --------------------------------------------
@@ -147,6 +149,8 @@ PRIVATE_AWS_SECRET_ACCESS_KEY=null
PRIVATE_AWS_DEFAULT_REGION=null
PRIVATE_AWS_BUCKET=null
PRIVATE_AWS_URL=null
PRIVATE_AWS_ENDPOINT=null
PRIVATE_AWS_PATH_STYLE=null
PRIVATE_AWS_BUCKET_ROOT=null
# --------------------------------------------
+4
View File
@@ -144,6 +144,8 @@ PUBLIC_AWS_ACCESS_KEY_ID=null
PUBLIC_AWS_DEFAULT_REGION=null
PUBLIC_AWS_BUCKET=null
PUBLIC_AWS_URL=null
PUBLIC_AWS_ENDPOINT=null
PUBLIC_AWS_PATH_STYLE=null
PUBLIC_AWS_BUCKET_ROOT=null
# --------------------------------------------
@@ -154,6 +156,8 @@ PRIVATE_AWS_SECRET_ACCESS_KEY=null
PRIVATE_AWS_DEFAULT_REGION=null
PRIVATE_AWS_BUCKET=null
PRIVATE_AWS_URL=null
PRIVATE_AWS_ENDPOINT=null
PRIVATE_AWS_PATH_STYLE=null
PRIVATE_AWS_BUCKET_ROOT=null
# --------------------------------------------
+19 -1
View File
@@ -40,12 +40,26 @@ DB_SANITIZE_BY_DEFAULT=false
# --------------------------------------------
# OPTIONAL: SSL DATABASE SETTINGS
# --------------------------------------------
# Enable SSL connection to database (true/false)
DB_SSL=false
# Set to true for cloud databases like AWS RDS, Azure Database, Google Cloud SQL
# Set to false for self-hosted databases with client certificates
DB_SSL_IS_PAAS=false
# Required when DB_SSL_IS_PAAS=false (client certificate authentication)
DB_SSL_KEY_PATH=null
DB_SSL_CERT_PATH=null
# Path to CA certificate bundle (required for SSL connections)
# For AWS RDS, download from: https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
DB_SSL_CA_PATH=null
# SSL cipher (optional, leave null for default)
DB_SSL_CIPHER=null
# Verify server certificate (true/false, defaults to false if not set)
# Set to false for development or when using self-signed certificates
DB_SSL_VERIFY_SERVER=null
# --------------------------------------------
@@ -143,6 +157,8 @@ PUBLIC_AWS_ACCESS_KEY_ID=null
PUBLIC_AWS_DEFAULT_REGION=null
PUBLIC_AWS_BUCKET=null
PUBLIC_AWS_URL=null
PUBLIC_AWS_ENDPOINT=null
PUBLIC_AWS_PATH_STYLE=null
PUBLIC_AWS_BUCKET_ROOT=null
# --------------------------------------------
@@ -153,6 +169,8 @@ PRIVATE_AWS_SECRET_ACCESS_KEY=null
PRIVATE_AWS_DEFAULT_REGION=null
PRIVATE_AWS_BUCKET=null
PRIVATE_AWS_URL=null
PRIVATE_AWS_ENDPOINT=null
PRIVATE_AWS_PATH_STYLE=null
PRIVATE_AWS_BUCKET_ROOT=null
# --------------------------------------------
@@ -197,7 +215,7 @@ REPORT_TIME_LIMIT=12000
API_THROTTLE_PER_MINUTE=120
CSV_ESCAPE_FORMULAS=true
LIVEWIRE_URL_PREFIX=null
MAX_UNPAGINATED=5000
# --------------------------------------------
# OPTIONAL: SAML SETTINGS
+4 -3
View File
@@ -1,10 +1,11 @@
frontend: ["*.js", "*.css", "*.vue", "*.scss", "*.less", "*.blade.*", "resources/views/livewire/*"]
frontend: ["*.js", "*.css", "*.scss", "*.less", "*.blade.*", "resources/views/livewire/*","resources/views/layouts/default.blade.php"]
skins: ["*.js", "*.css", "*.scss", "*.less"]
css: ["*.css","*.scss", "*.less"]
javascript: ["*.js", "package.json", "package.lock"]
backend: ["/app/*", "composer.json", "composer.lock"]
translations: ["/resources/lang"]
translations: ["/resources/lang/*"]
livewire: ["/app/Http/Livewire/*", "resources/views/livewire/*"]
blade-components: ["resources/views/blade/*"]
backups: ["*backup*"]
restore: ["*restore*"]
saml: ["*saml*"]
@@ -16,7 +17,7 @@ api: ["/app/Http/Controllers/Api/*"]
notifications: ["/app/Notifications/*"]
importer: ["/app/Importer/*","/app/Http/Livewire/Importer.php", "resources/views/livewire/importer.php"]
cli / artisan: ["/app/Console/*"]
LDAP: ["*Ldap*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php"]
LDAP: ["*Ldap*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php", "/resources/views/users/ldap.blade.php","/resources/views/settings/ldap.blade.php"]
docker: ["*docker/*", "Dockerfile", "Dockerfile.alpine", "Dockerfile.fpm-alpine", ".dockerignore", ".env.docker"]
tests: ["/tests/*", "/database/factories/*", "/stubs"]
config: .github
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Crowdin push
uses: crowdin/github-action@v2
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v5
uses: actions/checkout@v6
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v5
uses: actions/checkout@v6
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
dockerHubDescription:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Docker Hub Description
uses: grokability/dockerhub-description@7ea9d275c7cdbe2b676a093a0308c50665e3b8b4
+3 -3
View File
@@ -37,13 +37,13 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
@@ -82,7 +82,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |
+3 -3
View File
@@ -34,13 +34,13 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
@@ -81,7 +81,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |
+3 -3
View File
@@ -25,13 +25,13 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
@@ -67,7 +67,7 @@ jobs:
- name: Upload Laravel logs as artifacts
if: always()
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: laravel-logs-php-${{ matrix.php-version }}-run-${{ github.run_attempt }}
path: |
+2 -1
View File
@@ -68,7 +68,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/181059?v=4" width="110px;"/><br /><sub>Juan Font</sub>](https://github.com/juanfont)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [<img src="https://avatars.githubusercontent.com/u/13137708?v=4" width="110px;"/><br /><sub>Juho Taipale</sub>](https://github.com/juhotaipale)<br />[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [<img src="https://avatars.githubusercontent.com/u/1007419?v=4" width="110px;"/><br /><sub>Korvin Szanto</sub>](https://github.com/KorvinSzanto)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [<img src="https://avatars.githubusercontent.com/u/8513053?v=4" width="110px;"/><br /><sub>Lewis Foster</sub>](https://lewisfoster.foo/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [<img src="https://avatars.githubusercontent.com/u/33877541?v=4" width="110px;"/><br /><sub>Logan Swartzendruber</sub>](https://github.com/loganswartz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [<img src="https://avatars.githubusercontent.com/u/1156208?v=4" width="110px;"/><br /><sub>Lorenzo P.</sub>](https://github.com/lopezio)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [<img src="https://avatars.githubusercontent.com/u/33946590?v=4" width="110px;"/><br /><sub>Lukas Jung</sub>](https://github.com/m4us1ne)<br />[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") |
| [<img src="https://avatars.githubusercontent.com/u/10965027?v=4" width="110px;"/><br /><sub>Ellie</sub>](https://leafedfox.xyz/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [<img src="https://avatars.githubusercontent.com/u/20960555?v=4" width="110px;"/><br /><sub>GA Stamper</sub>](https://github.com/gastamper)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [<img src="https://avatars.githubusercontent.com/u/206553556?v=4" width="110px;"/><br /><sub>Guillaume Lefranc</sub>](https://github.com/gl-pup)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [<img src="https://avatars.githubusercontent.com/u/733892?v=4" width="110px;"/><br /><sub>Hajo Möller</sub>](https://github.com/dasjoe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [<img src="https://avatars.githubusercontent.com/u/3420063?v=4" width="110px;"/><br /><sub>Istvan Basa</sub>](https://github.com/pottom)<br />[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [<img src="https://avatars.githubusercontent.com/u/810824?v=4" width="110px;"/><br /><sub>JJ Asghar</sub>](https://jjasghar.github.io/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [<img src="https://avatars.githubusercontent.com/u/40404495?v=4" width="110px;"/><br /><sub>James E. Msenga</sub>](https://github.com/JemCdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") |
| [<img src="https://avatars.githubusercontent.com/u/6865786?v=4" width="110px;"/><br /><sub>Jan Felix Wiebe</sub>](https://github.com/jfwiebe)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [<img src="https://avatars.githubusercontent.com/u/43412008?v=4" width="110px;"/><br /><sub>Jo Drexl</sub>](https://www.nfon.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [<img src="https://avatars.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>Austin Sasko</sub>](https://github.com/austinsasko)<br />[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [<img src="https://avatars.githubusercontent.com/u/4875039?v=4" width="110px;"/><br /><sub>Jasson</sub>](http://jassoncordones.github.io)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [<img src="https://avatars.githubusercontent.com/u/76069640?v=4" width="110px;"/><br /><sub>Okean</sub>](https://github.com/Tinyblargon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [<img src="https://avatars.githubusercontent.com/u/6515064?v=4" width="110px;"/><br /><sub>Alejandro Medrano</sub>](https://www.lst.tfo.upm.es/alejandro-medrano/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | [<img src="https://avatars.githubusercontent.com/u/58696401?v=4" width="110px;"/><br /><sub>Lukas Kraic</sub>](https://github.com/lukaskraic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukaskraic "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [<img src="https://avatars.githubusercontent.com/u/15015119?v=4" width="110px;"/><br /><sub>Johannes Pollitt</sub>](https://github.com/FlorestanII)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [<img src="https://avatars.githubusercontent.com/u/14185442?v=4" width="110px;"/><br /><sub>Michael Strobel</sub>](https://strobelm.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [<img src="https://avatars.githubusercontent.com/u/634790?v=4" width="110px;"/><br /><sub>Nicky West</sub>](http://nickwest.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [<img src="https://avatars.githubusercontent.com/u/1347327?v=4" width="110px;"/><br /><sub>akaspeh1</sub>](https://github.com/akaspeh1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/1571724?v=4" width="110px;"/><br /><sub>Герхард PICCORO Lenz McKAY </sub>](https://github-readme-stats.vercel.app/api?username=mckaygerhard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mckaygerhard "Code") | [<img src="https://avatars.githubusercontent.com/u/15015119?v=4" width="110px;"/><br /><sub>Johannes Pollitt</sub>](https://github.com/FlorestanII)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorestanII "Code") | [<img src="https://avatars.githubusercontent.com/u/14185442?v=4" width="110px;"/><br /><sub>Michael Strobel</sub>](https://strobelm.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=strobelm "Code") | [<img src="https://avatars.githubusercontent.com/u/634790?v=4" width="110px;"/><br /><sub>Nicky West</sub>](http://nickwest.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nickwest "Code") | [<img src="https://avatars.githubusercontent.com/u/1347327?v=4" width="110px;"/><br /><sub>akaspeh1</sub>](https://github.com/akaspeh1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akaspeh1 "Code") | [<img src="https://avatars.githubusercontent.com/u/2880129?v=4" width="110px;"/><br /><sub>Sebastian Marsching</sub>](http://sebastian.marsching.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smarsching "Code") | [<img src="https://avatars.githubusercontent.com/u/40658372?v=4" width="110px;"/><br /><sub>Mo</sub>](https://github.com/mohammad-ahmadi1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mohammad-ahmadi1 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/20994684?v=4" width="110px;"/><br /><sub>Owen V. Hayes</sub>](https://github.com/MarvelousAnything)<br />[💻](https://github.com/snipe/snipe-it/commits?author=MarvelousAnything "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
+30 -30
View File
@@ -1,35 +1,35 @@
FROM alpine:3.19
FROM alpine:3.23
# Apache + PHP
RUN apk add --no-cache \
apache2 \
php82 \
php82-common \
php82-apache2 \
php82-curl \
php82-ldap \
php82-mysqli \
php82-gd \
php82-xml \
php82-mbstring \
php82-zip \
php82-ctype \
php82-tokenizer \
php82-pdo_mysql \
php82-openssl \
php82-bcmath \
php82-phar \
php82-json \
php82-iconv \
php82-fileinfo \
php82-simplexml \
php82-session \
php82-dom \
php82-xmlwriter \
php82-xmlreader \
php82-sodium \
php82-redis \
php82-pecl-memcached \
php82-exif \
php84 \
php84-common \
php84-apache2 \
php84-curl \
php84-ldap \
php84-mysqli \
php84-gd \
php84-xml \
php84-mbstring \
php84-zip \
php84-ctype \
php84-tokenizer \
php84-pdo_mysql \
php84-openssl \
php84-bcmath \
php84-phar \
php84-json \
php84-iconv \
php84-fileinfo \
php84-simplexml \
php84-session \
php84-dom \
php84-xmlwriter \
php84-xmlreader \
php84-sodium \
php84-redis \
php84-pecl-memcached \
php84-exif \
curl \
wget \
vim \
@@ -42,7 +42,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf
# Where apache's PID lives
RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php82/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php84/php.ini
COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf
# Enable mod_rewrite
+3
View File
@@ -78,11 +78,14 @@ Since the release of the JSON REST API, several third-party developers have been
#### Libraries & Modules
- [SnipeScheduler](https://github.com/JSY-Ben/SnipeScheduler) by [@JSY-Ben](https://github.com/JSY-Ben) - An Asset Reservation/Checkout System for Snipe-IT
- [Snipe-IT MCP Server](https://github.com/jameshgordy/snipeit-mcp) by [@jameshgordy](https://github.com/jameshgordy) - A Model Context Protocol (MCP) server for managing Snipe-IT inventory systems
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
- [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
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Rudder2Snipe](https://github.com/norbertoaquino/rudder2snipe) by [@norbertoaquino](https://github.com/norbertoaquino) - Rudder.io integration for Snipe-IT
- [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.
+21 -20
View File
@@ -245,26 +245,26 @@ class LdapSync extends Command
// Assign the mapped LDAP attributes for each user to the Snipe-IT user fields
for ($i = 0; $i < $results['count']; $i++) {
$item = [];
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
$item['display_name'] = $results[$i][$ldap_map["display_name"]][0] ?? '';
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? '';
$item['email'] = $results[$i][$ldap_map["email"]][0] ?? '';
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
$item['location_id'] = $results[$i]['location_id'] ?? '';
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
$item['mobile'] = $results[$i][$ldap_map["mobile"]][0] ?? '';
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? '';
$item['address'] = $results[$i][$ldap_map["address"]][0] ?? '';
$item['city'] = $results[$i][$ldap_map["city"]][0] ?? '';
$item['state'] = $results[$i][$ldap_map["state"]][0] ?? '';
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
$item['zip'] = $results[$i][$ldap_map["zip"]][0] ?? '';
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
$location = $default_location; //initially, set '$location' to the default_location (which may just be `null`)
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? null;
$item['display_name'] = $results[$i][$ldap_map["display_name"]][0] ?? null;
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? null;
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? null;
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? null;
$item['email'] = $results[$i][$ldap_map["email"]][0] ?? null;
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? null;
$item['location_id'] = $results[$i]['location_id'] ?? null;
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? null;
$item['mobile'] = $results[$i][$ldap_map["mobile"]][0] ?? null;
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? null;
$item['address'] = $results[$i][$ldap_map["address"]][0] ?? null;
$item['city'] = $results[$i][$ldap_map["city"]][0] ?? null;
$item['state'] = $results[$i][$ldap_map["state"]][0] ?? null;
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? null;
$item['zip'] = $results[$i][$ldap_map["zip"]][0] ?? null;
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? null;
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? null;
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? null;
$location = $default_location; //initially, set '$location' to the default_location (which may just be null)
// ONLY if you are using the "ldap_location" option *AND* you have an actual result
if ($ldap_map["location"] && $item['location']) {
@@ -464,6 +464,7 @@ class LdapSync extends Command
$errors = '';
if ($user->save()) {
$item['id'] = $user->id;
$item['note'] = $item['createorupdate'];
$item['status'] = 'success';
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use App\Enums\ActionType;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class MigrateLicenseSeatQuantitiesInActionLogs extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:migrate-license-seat-quantities-in-action-logs
{--no-interaction: Do not ask any interactive question}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Updates quantity field in action_logs table for license seats that were added or deleted.';
/**
* Execute the console command.
*/
public function handle()
{
$query = DB::table('action_logs')
->whereIn('action_type', [
ActionType::AddSeats->value,
ActionType::DeleteSeats->value,
])
->where('quantity', '=', 1)
->orderBy('id');
$count = $query->count();
if ($count === 0) {
$this->info('Nothing to update');
return 0;
}
$this->info("{$count} logs to update");
if ($this->option('no-interaction') || $this->confirm('Update quantities in the action log?')) {
$query->chunk(50, function ($logs) {
$logs->each(function ($log) {
$quantityFromNote = Str::between($log->note, "ed ", " seats");
if (!is_numeric($quantityFromNote)) {
$this->error('Could not parse quantity from ID: {id}', ['id' => $log->id]);
}
if ($log->quantity !== (int) $quantityFromNote) {
$this->info(vsprintf('Updating id: %s to quantity %s', [
'id' => $log->id,
'new_quantity' => $quantityFromNote,
]));
DB::table('action_logs')->where('id', $log->id)->update(['quantity' => (int) $quantityFromNote]);
}
});
});
}
return 0;
}
}
@@ -113,7 +113,7 @@ class MoveUploadsToNewDisk extends Command
$filename = basename($private_upload[$x]);
try {
Storage::put($private_type . '/' . $filename, file_get_contents($private_upload[$i]));
Storage::put($private_type . '/' . $filename, file_get_contents($private_upload[$x]));
$new_url = Storage::url($private_type . '/' . $filename, $filename);
$this->info($type_count . '. PRIVATE: ' . $filename . ' was copied to ' . $new_url);
} catch (\Exception $e) {
+13 -6
View File
@@ -33,6 +33,11 @@ class ObjectImportCommand extends Command
*/
protected ProgressIndicator $progressIndicator;
/**
* Logger instance with configurable log path
*/
protected $logger;
/**
* Create a new command instance.
*
@@ -56,7 +61,7 @@ class ObjectImportCommand extends Command
$this->progressIndicator = new ProgressIndicator($this->output);
$filename = $this->argument('filename');
$class = title_case($this->option('item-type'));
$class = ucfirst($this->option('item-type'));
$classString = "App\\Importer\\{$class}Importer";
$importer = new $classString($filename);
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
@@ -65,9 +70,11 @@ class ObjectImportCommand extends Command
->setShouldNotify($this->option('send-welcome'))
->setUsernameFormat($this->option('username_format'));
// This $logFile/useFiles() bit is currently broken, so commenting it out for now
// $logFile = $this->option('logfile');
// Log::useFiles($logFile);
$this->logger = Log::build([
'driver' => 'single',
'path' => $this->option('logfile'),
]);
$this->progressIndicator->start('======= Importing Items from '.$filename.' =========');
$importer->import();
@@ -99,10 +106,10 @@ class ObjectImportCommand extends Command
public function log($string, $level = 'info')
{
if ($level === 'warning') {
Log::warning($string);
$this->logger->warning($string);
$this->comment($string);
} else {
Log::Info($string);
$this->logger->Info($string);
if ($this->option('verbose')) {
$this->comment($string);
}
+132
View File
@@ -0,0 +1,132 @@
<?php
namespace App\Console\Commands;
use App\Models\CheckoutAcceptance;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class PurgeEulaPDFs extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:purge-eula-pdfs
{--older-than-days= : The number of days we should delete before }
{--force : Skip the interactive yes/no prompt for confirmation}
{--dryrun : Show the records that would be deleted but don\'t update the database or delete files from disk}
{--with-output : Display the results in a table in your console}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This purges signature files and EULAs from the system if they are older than the date passed with --older-than-days=.';
/**
* Execute the console command.
*/
public function handle()
{
$before = $this->option('older-than-days');
if (($before=='') || (!is_numeric($before))) {
return $this->error('ERROR: You must pass a valid number for --older-than-days (example: snipeit:purge-eula-pdfs --older-than-days=365.)');
}
$interval_date = Carbon::now()->subDays($before);
$signature_path = 'private_uploads/signatures/';
$eula_path = 'private_uploads/eula-pdfs/';
if (!Storage::exists($eula_path)) {
$this->fail('The storage directory "'.$eula_path.'" does not exist. No EULA files will be deleted.');
}
if (!Storage::exists($signature_path)) {
$this->fail('The storage directory "'.$signature_path.'" does not exist. No signature files will be deleted.');
}
if ($this->option('dryrun')) {
$this->info('This script is being run with the --dryrun option. No files or records will be deleted.');
}
$acceptances = CheckoutAcceptance::HasFiles()->where('updated_at','<', $interval_date)->with('assignedTo')->get();
if (!$this->option('force')) {
if ($this->confirm("\n****************************************************\nTHIS WILL DELETE ALL OF THE SIGNATURES AND EULA PDF FILES SINCE $interval_date. \nThere is NO undo! \n****************************************************\n\nDo you wish to continue? No backsies! [y|N]")) {
}
}
if ($acceptances->count() == 0) {
return $this->warn('There are no item acceptances with signatures or EULA PDFs from before '.$interval_date);
}
$this->info(number_format($acceptances->count()) . ' EULA PDFs from before '.$interval_date.' will be purged');
if (!$this->option('with-output')) {
$this->info('Run this command with the --with-output option to see the full list in the console.');
} else {
$this->table(
[
trans('general.user'),
trans('general.type'),
trans('general.item'),
trans('general.category'),
trans('general.accepted_date'),
trans('general.declined_date'),
trans('general.signature'),
trans('general.filename'),
],
$acceptances->map(fn($acceptance) => [
trans('general.user') => $acceptance->assignedTo->display_name,
trans('general.type') => $acceptance->display_checkoutable_type,
trans('general.item') => $acceptance->checkoutable_type::find($acceptance->checkoutable_id)->display_name,
trans('general.category') => $acceptance->checkoutable_category_name,
trans('general.accepted_date') => $acceptance->accepted_at,
trans('general.declined_date') => $acceptance->declined_at,
trans('general.signature') => $acceptance->signature_filename,
trans('general.filename') => $acceptance->stored_eula_file,
])
);
}
foreach ($acceptances as $acceptance) {
$signature_file = $signature_path.$acceptance->signature_filename;
$eula_file = $eula_path.$acceptance->stored_eula_file;
if (Storage::exists($signature_file)) {
if (!$this->option('dryrun')) {
Storage::delete($signature_file);
}
} else {
$this->error('The file "'. $signature_file.'" does not exist.');
}
if (Storage::exists($eula_file)) {
if (!$this->option('dryrun')) {
Storage::delete($eula_file);
}
} else {
$this->error('The file "'.$eula_file.'" does not exist.');
}
if (!$this->option('dryrun')) {
$acceptance->delete();
}
}
}
}
+5 -2
View File
@@ -49,14 +49,15 @@ class ResetDemoSettings extends Command
$settings->logo = 'snipe-logo.png';
$settings->alert_email = 'service@snipe-it.io';
$settings->login_note = 'Use `admin` / `password` to login to the demo.';
$settings->header_color = null;
$settings->header_color = '#3c8dbc';
$settings->link_dark_color = '#5fa4cc';
$settings->link_light_color = '#296282;';
$settings->label2_2d_type = 'QRCODE';
$settings->default_currency = 'USD';
$settings->brand = 2;
$settings->ldap_enabled = 0;
$settings->full_multiple_companies_support = 0;
$settings->label2_1d_type = 'C128';
$settings->skin = '';
$settings->email_domain = 'snipeitapp.com';
$settings->email_format = 'filastname';
$settings->username_format = 'filastname';
@@ -80,6 +81,8 @@ class ResetDemoSettings extends Command
if ($user = User::where('username', '=', 'admin')->first()) {
$user->locale = 'en-US';
$user->enable_confetti = 1;
$user->enable_sounds = 1;
$user->save();
}
+1 -1
View File
@@ -52,7 +52,7 @@ class SQLStreamer {
/* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with
a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to
assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */
"<^/\*!40101 SET NAMES '?[a-zA-Z0-9_-]+'? \*/;$>" => false, //using weird delimiters (<,>) for readability. allow quoted or unquoted charsets
"<^/\*![0-9]{5} SET NAMES '?[a-zA-Z0-9_-]+'? \*/;$>" => false, //using weird delimiters (<,>) for readability. allow quoted or unquoted charsets
"<^/\*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' \*/;$>" => false, //same, now handle zero-values
];
+27 -11
View File
@@ -3,13 +3,18 @@
namespace App\Console\Commands;
use App\Mail\UnacceptedAssetReminderMail;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CurrentInventory;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Facades\Mail;
class SendAcceptanceReminder extends Command
@@ -26,7 +31,7 @@ class SendAcceptanceReminder extends Command
*
* @var string
*/
protected $description = 'This will resend users with unaccepted assets a reminder to accept or decline them.';
protected $description = 'This will resend users with unaccepted items a reminder to accept or decline them.';
/**
* Create a new command instance.
@@ -45,19 +50,30 @@ class SendAcceptanceReminder extends Command
*/
public function handle()
{
$pending = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')
->whereHas('checkoutable', function($query) {
$query->where('accepted_at', null)
->where('declined_at', null);
})
->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser'])
->get();
$pending = CheckoutAcceptance::query()
->with([
'checkoutable' => function (MorphTo $morph) {
$morph->morphWith([
Asset::class => ['model.category', 'assignedTo', 'adminuser', 'company', 'checkouts'],
Accessory::class => ['category', 'company', 'checkouts'],
LicenseSeat::class => ['user', 'license', 'checkouts'],
Component::class => ['assignedTo', 'company', 'checkouts'],
Consumable::class => ['company', 'checkouts'],
]);
},
'assignedTo',
])
->whereHasMorph(
'checkoutable',
[Asset::class, Accessory::class, LicenseSeat::class, Component::class, Consumable::class],
fn ($q) => $q->whereNull('accepted_at')
->whereNull('declined_at')
)
->pending()
->get();
$count = 0;
$unacceptedAssetGroups = $pending
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
})
@@ -9,6 +9,8 @@ use App\Notifications\ExpectedCheckinAdminNotification;
use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
use App\Helpers\Helper;
class SendExpectedCheckinAlerts extends Command
{
@@ -17,7 +19,7 @@ class SendExpectedCheckinAlerts extends Command
*
* @var string
*/
protected $name = 'snipeit:expected-checkin';
protected $signature = 'snipeit:expected-checkin {--with-output : Display the results in a table in your console in addition to sending the email}';
/**
* The console command description.
@@ -42,19 +44,47 @@ class SendExpectedCheckinAlerts extends Command
public function handle()
{
$settings = Setting::getSettings();
$interval = $settings->audit_warning_days ?? 0;
$interval = $settings->due_checkin_days ?? 0;
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$count = 0;
if (!$this->option('with-output')) {
$this->info('Run this command with the --with-output option to see the full list in the console.');
}
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForCheckin($settings)->orderBy('assets.expected_checkin', 'desc')->get();
$this->info($assets->count().' assets must be checked in on or before '.$interval_date.' is deadline');
$this->info($assets->count().' assets must be checked on or before '.Helper::getFormattedDateObject($interval_date, 'date', false));
foreach ($assets as $asset) {
if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) {
$this->info('Sending User ExpectedCheckinNotification to: '.$asset->assignedTo->email);
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
$count++;
}
}
if ($this->option('with-output')) {
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
$this->table(
[
trans('general.id'),
trans('admin/hardware/form.tag'),
trans('admin/hardware/form.model'),
trans('general.model_no'),
trans('general.purchase_date'),
trans('admin/hardware/form.expected_checkin'),
],
$assets->map(fn($assets) => [
trans('general.id') => $assets->id,
trans('admin/hardware/form.tag') => $assets->asset_tag,
trans('admin/hardware/form.model') => $assets->model->name,
trans('general.model_no') => $assets->model->model_number,
trans('general.purchase_date') => $assets->purchase_date_formatted,
trans('admin/hardware/form.eol_date') => $assets->expected_checkin_formattedDate ? $assets->expected_checkin_formattedDate . ' (' . $assets->expected_checkin_diff_for_humans . ')' : '',
])
);
}
}
@@ -63,10 +93,11 @@ class SendExpectedCheckinAlerts extends Command
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
$this->info('Sending Admin ExpectedCheckinNotification to: '.$settings->alert_email);
\Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
}
$this->info('Sent checkin reminders to to '.$count.' users.');
}
}
@@ -14,11 +14,11 @@ use Illuminate\Support\Facades\Mail;
class SendExpirationAlerts extends Command
{
/**
* The console command name.
*
* The name and signature of the console command.
*
* @var string
*/
protected $name = 'snipeit:expiring-alerts';
protected $signature = 'snipeit:expiring-alerts {--expired-licenses}';
/**
* The console command description.
@@ -55,6 +55,8 @@ class SendExpirationAlerts extends Command
// Expiring Assets
$assets = Asset::getExpiringWarrantyOrEol($alert_interval);
$assets->load(['assignedTo', 'supplier']);
if ($assets->count() > 0) {
Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval));
@@ -85,7 +87,7 @@ class SendExpirationAlerts extends Command
}
// Expiring licenses
$licenses = License::query()->ExpiringLicenses($alert_interval)
$licenses = License::query()->ExpiringLicenses($alert_interval, $this->option('expired-licenses'))
->with('manufacturer','category')
->orderBy('expiration_date', 'ASC')
->orderBy('termination_date', 'ASC')
+3 -1
View File
@@ -52,7 +52,9 @@ class SendInventoryAlerts extends Command
return new AlertRecipient($item);
});
\Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
Notification::send($recipients, new InventoryAlert($items, $settings->alert_threshold));
} else {
$this->info('No low inventory items found. No mail sent.');
}
} else {
if ($settings->alert_email == '') {
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace App\Enums;
enum ActionType: string
{
// General
case Create = 'create';
case Update = 'update';
case Delete = 'delete';
case Restore = 'restore';
// Assets/Accessories/Components/Licenses/Consumables
case Checkout = 'checkout';
case CheckinFrom = 'checkin from';
case Requested = 'requested';
case RequestCanceled = 'request canceled';
case Accepted = 'accepted';
case Declined = 'declined';
case Audit = 'audit';
case NoteAdded = 'note added';
// Users
case TwoFactorReset = '2FA reset';
case Merged = 'merged';
// Licenses
case DeleteSeats = 'delete seats';
case AddSeats = 'add seats';
// File Uploads
case Uploaded = 'uploaded';
case UploadDeleted = 'upload deleted';
}
+3 -1
View File
@@ -15,18 +15,20 @@ class CheckoutableCheckedOut
public $checkedOutBy;
public $note;
public $originalValues;
public int $quantity;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note, $originalValues = [])
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note, $originalValues = [], $quantity = 1)
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedOutBy = $checkedOutBy;
$this->note = $note;
$this->originalValues = $originalValues;
$this->quantity = $quantity;
}
}
@@ -0,0 +1,24 @@
<?php
namespace App\Events;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
class CheckoutablesCheckedOutInBulk
{
use Dispatchable, SerializesModels;
public function __construct(
public Collection $assets,
public Model $target,
public User $admin,
public string $checkout_at,
public string $expected_checkin,
public string $note,
) {
}
}
+2
View File
@@ -162,6 +162,8 @@ class Handler extends ExceptionHandler
$route = 'licenses.index';
} elseif (($route === 'customfieldsets.index') || ($route === 'customfields.index')) {
$route = 'fields.index';
} elseif ($route == 'actionlogs.index') {
$route = 'home';
}
return redirect()
+105 -50
View File
@@ -2,6 +2,7 @@
namespace App\Helpers;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Component;
@@ -13,6 +14,7 @@ use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\License;
use App\Models\Location;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
@@ -773,18 +775,18 @@ class Helper
public static function checkLowInventory()
{
$alert_threshold = \App\Models\Setting::getSettings()->alert_threshold;
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
$consumables = Consumable::withCount('consumableAssignments as consumables_users_count')->whereNotNull('min_amt')->get();
$accessories = Accessory::withCount('checkouts as checkouts_count')->whereNotNull('min_amt')->get();
$components = Component::whereNotNull('min_amt')->get();
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
$licenses = License::where('min_amt', '>', 0)->get();
$components = Component::withCount('assets as sum_unconstrained_assets')->whereNotNull('min_amt')->get();
$asset_models = AssetModel::where('min_amt', '>', 0)->withCount(['availableAssets', 'assets'])->get();
$licenses = License::withCount('availCount as licenses_available')->where('min_amt', '>', 0)->get();
$items_array = [];
$all_count = 0;
foreach ($consumables as $consumable) {
$avail = $consumable->numRemaining();
if ($avail < ($consumable->min_amt) + $alert_threshold) {
if ($avail <= ($consumable->min_amt) + $alert_threshold) {
if ($consumable->qty > 0) {
$percent = number_format((($avail / $consumable->qty) * 100), 0);
} else {
@@ -803,7 +805,7 @@ class Helper
foreach ($accessories as $accessory) {
$avail = $accessory->qty - $accessory->checkouts_count;
if ($avail < ($accessory->min_amt) + $alert_threshold) {
if ($avail <= ($accessory->min_amt) + $alert_threshold) {
if ($accessory->qty > 0) {
$percent = number_format((($avail / $accessory->qty) * 100), 0);
} else {
@@ -822,7 +824,7 @@ class Helper
foreach ($components as $component) {
$avail = $component->numRemaining();
if ($avail < ($component->min_amt) + $alert_threshold) {
if ($avail <= ($component->min_amt) + $alert_threshold) {
if ($component->qty > 0) {
$percent = number_format((($avail / $component->qty) * 100), 0);
} else {
@@ -842,10 +844,10 @@ class Helper
foreach ($asset_models as $asset_model){
$asset = new Asset();
$total_owned = $asset->where('model_id', '=', $asset_model->id)->count();
$avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count();
$total_owned = $asset_model->assets_count; //requires the withCount() clause in the initial query!
$avail = $asset_model->available_assets_count; //requires the withCount() clause in the initial query!
if ($avail < ($asset_model->min_amt) + $alert_threshold) {
if ($avail <= ($asset_model->min_amt) + $alert_threshold) {
if ($avail > 0) {
$percent = number_format((($avail / $total_owned) * 100), 0);
} else {
@@ -863,7 +865,7 @@ class Helper
foreach ($licenses as $license){
$avail = $license->remaincount();
if ($avail < ($license->min_amt) + $alert_threshold) {
if ($avail <= ($license->min_amt) + $alert_threshold) {
if ($avail > 0) {
$percent = number_format((($avail / $license->min_amt) * 100), 0);
} else {
@@ -1384,49 +1386,24 @@ class Helper
* @return string[]
*/
public static function SettingUrls(){
$settings=['#','fields.index', 'statuslabels.index', 'models.index', 'categories.index', 'manufacturers.index', 'suppliers.index', 'departments.index', 'locations.index', 'companies.index', 'depreciations.index'];
$settings=[
'#',
'fields*',
'statuslabels*',
'models*',
'categories*',
'manufacturers*',
'suppliers*',
'departments*',
'locations*',
'companies*',
'depreciations*'
];
return $settings;
}
/**
* Generic helper (largely used by livewire right now) that returns the font-awesome icon
* for the object type.
*
* @author A. Gianotto <snipe@snipe.net>
* @since 6.1.0
*
* @return string
*/
public static function iconTypeByItem($item) {
switch ($item) {
case 'asset':
return 'fas fa-barcode';
case 'accessory':
return 'fas fa-keyboard';
case 'component':
return 'fas fa-hdd';
case 'consumable':
return 'fas fa-tint';
case 'license':
return 'far fa-save';
case 'location':
return 'fas fa-map-marker-alt';
case 'user':
return 'fas fa-user';
case 'supplier':
return 'fa-solid fa-store';
case 'manufacturer':
return 'fa-solid fa-building';
case 'category':
return 'fa-solid fa-table-columns';
}
}
/*
* This is a shorter way to see if the app is in demo mode.
*
@@ -1570,7 +1547,6 @@ class Helper
]) ? 'rtl' : 'ltr';
}
static public function getRedirectOption($request, $id, $table, $item_id = null) : RedirectResponse
{
@@ -1735,4 +1711,83 @@ class Helper
}
return $mismatched;
}
static public function labelFieldLayoutScaling(
$pdf,
iterable|\Closure $fields,
float $currentX,
float $usableWidth,
float $usableHeight,
float $baseLabelSize,
float $baseFieldSize,
float $baseFieldMargin,
?string $title = null,
float $baseTitleSize = 0.0,
float $baseTitleMargin = 0.0,
float $baseLabelPadding = 1.5,
float $baseGap = 1.5,
float $maxScale = 1.8,
string $labelFont = 'freesans',
) : array
{
$fieldCount = count($fields);
$perFieldHeight = max($baseLabelSize, $baseFieldSize) + $baseFieldMargin;
$baseFieldsHeight = $fieldCount * $perFieldHeight;
$hasTitle = is_string($title) && trim($title) !== '';
$baseTitleHeight = $hasTitle ? ($baseTitleSize + $baseTitleMargin) : 0.0;
$baseTotalHeight = $baseTitleHeight + $baseFieldsHeight;
$scale = 1.0;
if ($baseTotalHeight > 0 && $usableHeight > 0) {
$scale = $usableHeight / $baseTotalHeight;
}
$scale = min($scale, $maxScale);
$labelSize = $baseLabelSize;
$fieldSize = $baseFieldSize * $scale;
$fieldMargin = $baseFieldMargin * $scale;
$rowAdvance = max($labelSize, $fieldSize) + $fieldMargin;
$titleSize = $hasTitle ? ($baseTitleSize * $scale) : 0.0;
$titleMargin = $hasTitle ? ($baseTitleMargin * $scale) : 0.0;
$titleAdvance = $hasTitle ? ($titleSize + $titleMargin) : 0.0;
$pdf->SetFont($labelFont, '', $baseLabelSize);
$maxLabelWidthPerUnit = 0;
foreach ($fields as $field) {
$rawLabel = $field['label'] ?? null;
// If no label, do not include it in label-column sizing
if (!is_string($rawLabel) || trim($rawLabel) === '') {
continue;
}
$label = rtrim($field['label'], ':') . ':';
$width = $pdf->GetStringWidth($label);
$maxLabelWidthPerUnit = max($maxLabelWidthPerUnit, $width / $baseLabelSize);
}
$labelPadding = $baseLabelPadding * $scale;
$gap = $baseGap * $scale;
$labelWidth = ($maxLabelWidthPerUnit * $labelSize) + $labelPadding;
$valueX = $currentX + $labelWidth + $gap;
$valueWidth = $usableWidth - $labelWidth - $gap;
return compact(
'scale',
'hasTitle',
'titleSize',
'titleMargin',
'titleAdvance',
'labelSize',
'fieldSize',
'fieldMargin',
'rowAdvance',
'labelWidth',
'valueX',
'valueWidth'
);
}
}
+64 -3
View File
@@ -12,6 +12,7 @@ class IconHelper
case 'checkin':
return 'fa-solid fa-rotate-right';
case 'edit':
case 'update':
return 'fas fa-pencil-alt';
case 'clone':
return 'far fa-clone';
@@ -36,6 +37,8 @@ class IconHelper
return 'fa-solid fa-user';
case 'users':
return 'fas fa-users';
case 'supplier':
return 'fa-solid fa-store';
case 'restore':
return 'fa-solid fa-trash-arrow-up';
case 'external-link':
@@ -46,6 +49,8 @@ class IconHelper
return 'fa-regular fa-envelope';
case 'phone':
return 'fa-solid fa-phone';
case 'fax':
return 'fa-solid fa-fax';
case 'mobile':
return 'fas fa-mobile-screen-button';
case 'long-arrow-right':
@@ -53,7 +58,7 @@ class IconHelper
case 'download':
return 'fas fa-download';
case 'checkmark':
return 'fas fa-check icon-white';
return 'fas fa-check';
case 'x':
return 'fas fa-times';
case 'logout':
@@ -85,8 +90,11 @@ class IconHelper
case 'licenses':
case 'license':
return 'far fa-save';
case 'requests':
case 'requestable':
return 'fas fa-laptop';
case 'request':
case 'requested':
return 'fa-solid fa-bell-concierge';
case 'reports':
return 'fas fa-chart-bar';
case 'heart':
@@ -129,9 +137,12 @@ class IconHelper
return 'fa-regular fa-clipboard';
case 'paperclip':
return 'fas fa-paperclip';
case 'contact-card':
return 'fa-regular fa-id-card';
case 'files':
return 'fa-regular fa-file';
case 'more-info':
case 'support':
return 'far fa-life-ring';
case 'calendar':
return 'fas fa-calendar';
@@ -142,7 +153,7 @@ class IconHelper
case 'more-files':
return 'fa-solid fa-laptop-file';
case 'maintenances':
return 'fas fa-wrench';
return 'fa-solid fa-screwdriver-wrench';
case 'seats':
return 'far fa-list-alt';
case 'globe-us':
@@ -201,6 +212,56 @@ class IconHelper
return 'fa-solid fa-lightbulb';
case 'highlight':
return 'fa-solid fa-highlighter';
case 'manager':
return 'fa-solid fa-building-user';
case 'company':
return 'fa-regular fa-building';
case 'parent':
return 'fa-solid fa-building-flag';
case 'number':
return 'fa-solid fa-hashtag';
case 'depreciation':
return 'fa-solid fa-arrows-down-to-line';
case 'depreciation-calendar':
case 'expiration':
case 'terminates':
return 'fa-regular fa-calendar-xmark';
case 'manufacturer':
return 'fa-solid fa-industry';
case 'fieldset' :
return 'fa-regular fa-rectangle-list';
case 'deleted-date':
return 'fa-solid fa-calendar-xmark';
case 'eol':
return 'fa-regular fa-calendar-days';
case 'category':
return 'fa-solid fa-icons';
case 'cost':
return 'fa-solid fa-money-bills';
case 'available':
return 'fa-solid fa-box';
case 'checkedout':
return 'fa-solid fa-box-open';
case 'purchase_order':
return 'fa-solid fa-file-invoice-dollar';
case 'order':
return 'fa-solid fa-file-invoice';
case 'checkout-all':
return 'fa-solid fa-arrows-down-to-people';
case 'square-right':
return 'fa-regular fa-square-caret-right';
case 'square-left':
return 'fa-regular fa-square-caret-left';
case 'square':
return 'fa-solid fa-square';
case 'models':
case 'model':
return 'fa-solid fa-boxes-stacked';
case 'min-qty':
return 'fa-solid fa-chart-pie';
}
}
}
@@ -90,10 +90,10 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
}
if($request->get('redirect_option') === 'back'){
if($request->input('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
}
// Was the accessory created?
@@ -182,7 +182,11 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
session()->put(['redirect_option' => $request->get('redirect_option')]);
if($request->input('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->input('redirect_option')]);
}
if ($accessory->save()) {
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
@@ -76,7 +76,7 @@ class AccessoryCheckinController extends Controller
if ($accessory_checkout->delete()) {
event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at));
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
->with('success', trans('admin/accessories/message.checkin.success'));
@@ -67,7 +67,7 @@ class AccessoryCheckoutController extends Controller
*/
public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse
{
$this->authorize('checkout', $accessory);
$target = $this->determineCheckoutTarget();
@@ -89,12 +89,19 @@ class AccessoryCheckoutController extends Controller
$accessory_checkout->save();
}
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
event(new CheckoutableCheckedOut(
$accessory,
$target,
auth()->user(),
$request->input('note'),
[],
$accessory->checkout_qty,
));
$request->request->add(['checkout_to_type' => request('checkout_to_type')]);
$request->request->add(['assigned_to' => $target->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
session()->put(['redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => $request->input('checkout_to_type')]);
// Redirect to the new accessory page
@@ -13,9 +13,9 @@ use App\Models\Company;
use App\Models\Contracts\Acceptable;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\AcceptanceAssetAcceptedNotification;
use App\Notifications\AcceptanceAssetAcceptedToUserNotification;
use App\Notifications\AcceptanceAssetDeclinedNotification;
use App\Notifications\AcceptanceItemAcceptedNotification;
use App\Notifications\AcceptanceItemAcceptedToUserNotification;
use App\Notifications\AcceptanceItemDeclinedNotification;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
@@ -74,16 +74,16 @@ class AcceptanceController extends Controller
*/
public function store(Request $request, $id) : RedirectResponse
{
$acceptance = CheckoutAcceptance::find($id);
if (!$acceptance = CheckoutAcceptance::find($id)) {
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$assigned_user = User::find($acceptance->assigned_to_id);
$settings = Setting::getSettings();
$sig_filename='';
if (is_null($acceptance)) {
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
}
if (! $acceptance->isPending()) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
}
@@ -138,14 +138,14 @@ class AcceptanceController extends Controller
// Convert PDF logo to base64 for TCPDF
// This is needed for TCPDF to properly embed the image if it's a png and the cache isn't writable
$encoded_logo = null;
if ($settings->acceptance_pdf_logo) {
if (($settings->acceptance_pdf_logo) && (Storage::disk('public')->exists($settings->acceptance_pdf_logo))) {
$encoded_logo = base64_encode(file_get_contents(public_path() . '/uploads/' . $settings->acceptance_pdf_logo));
}
// Get the data array ready for the notifications and PDF generation
$data = [
'item_tag' => $item->asset_tag,
'item_name' => $item->name, // this handles licenses seats, which don't have a 'name' field
'item_name' => $item->display_name, // this handles licenses seats, which don't have a 'name' field
'item_model' => $item->model?->name,
'item_serial' => $item->serial,
'item_status' => $item->assetstatus?->name,
@@ -183,13 +183,13 @@ class AcceptanceController extends Controller
// Add the attachment for the signing user into the $data array
$data['file'] = $pdf_filename;
try {
$assigned_user->notify((new AcceptanceAssetAcceptedToUserNotification($data))->locale($assigned_user->locale));
$assigned_user->notify((new AcceptanceItemAcceptedToUserNotification($data))->locale($assigned_user->locale));
} catch (\Exception $e) {
Log::warning($e);
}
}
try {
$acceptance->notify((new AcceptanceAssetAcceptedNotification($data))->locale(Setting::getSettings()->locale));
$acceptance->notify((new AcceptanceItemAcceptedNotification($data))->locale(Setting::getSettings()->locale));
} catch (\Exception $e) {
Log::warning($e);
}
@@ -204,7 +204,7 @@ class AcceptanceController extends Controller
$acceptance->decline($sig_filename, $request->input('note'));
}
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
$acceptance->notify(new AcceptanceItemDeclinedNotification($data));
Log::debug('New event acceptance.');
event(new CheckoutDeclined($acceptance));
$return_msg = trans('admin/users/message.declined');
@@ -216,7 +216,7 @@ class AcceptanceController extends Controller
try {
$recipient = User::find($acceptance->alert_on_response_id);
if ($recipient) {
if ($recipient?->email) {
Log::debug('Attempting to send email acceptance.');
Mail::to($recipient)->send(new CheckoutAcceptanceResponseMail(
$acceptance,
@@ -53,6 +53,7 @@ class AccessoriesController extends Controller
'company_id',
'notes',
'checkouts_count',
'order_number',
'qty',
// These are *relationships* so we wouldn't normally include them in this array,
// since they would normally create a `column not found` error,
@@ -91,6 +92,10 @@ class AccessoriesController extends Controller
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
}
if ($request->filled('order_number')) {
$accessories->where('accessories.order_number', '=', $request->input('order_number'));
}
if ($request->filled('category_id')) {
$accessories->where('category_id', '=', $request->input('category_id'));
}
@@ -325,7 +330,14 @@ class AccessoriesController extends Controller
}
// Set this value to be able to pass the qty through to the event
event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note')));
event(new CheckoutableCheckedOut(
$accessory,
$target,
auth()->user(),
$request->input('note'),
[],
$accessory->checkout_qty,
));
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkout.success')));
@@ -389,7 +401,7 @@ class AccessoriesController extends Controller
]);
if ($request->filled('search')) {
$accessories = $accessories->where('accessories.name', 'LIKE', '%'.$request->get('search').'%');
$accessories = $accessories->where('accessories.name', 'LIKE', '%'.$request->input('search').'%');
}
$accessories = $accessories->orderBy('name', 'ASC')->paginate(50);
@@ -249,7 +249,7 @@ class AssetModelsController extends Controller
* it, but I'll be damned if I can think of one. - snipe
*/
if ($request->filled('custom_fieldset_id')) {
$assetmodel->fieldset_id = $request->get('custom_fieldset_id');
$assetmodel->fieldset_id = $request->input('custom_fieldset_id');
}
+47 -31
View File
@@ -3,37 +3,39 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use App\Http\Requests\StoreAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Http\Transformers\ComponentsTransformer;
use App\Models\AccessoryCheckout;
use App\Models\CheckoutAcceptance;
use App\Models\LicenseSeat;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Requests\FilterRequest;
use App\Http\Requests\StoreAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\ComponentsTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\AccessoryCheckout;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use App\View\Label;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use App\View\Label;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
@@ -56,7 +58,7 @@ class AssetsController extends Controller
* @param int $assetId
* @since [v4.0]
*/
public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array
public function index(FilterRequest $request, $action = null, $upcoming_status = null) : JsonResponse | array
{
@@ -152,6 +154,15 @@ class AssetsController extends Controller
}
$assets = Asset::select('assets.*')
// ->addSelect([
// 'first_checkout_at' => Actionlog::query()
// ->select('created_at')
// ->whereColumn('item_id', 'assets.id')
// ->where('item_type', Asset::class)
// ->where('action_type', 'checkout')
// ->orderBy('created_at')
// ->limit(1),
// ])
->with(
'model',
'location',
@@ -376,7 +387,7 @@ class AssetsController extends Controller
}
if ($request->filled('order_number')) {
$assets->where('assets.order_number', '=', strval($request->get('order_number')));
$assets->where('assets.order_number', '=', strval($request->input('order_number')));
}
// This is kinda gross, but we need to do this because the Bootstrap Tables
@@ -653,7 +664,7 @@ class AssetsController extends Controller
public function store(StoreAssetRequest $request): JsonResponse
{
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
$asset->model()->associate(AssetModel::find((int) $request->input('model_id')));
$asset->fill($request->validated());
$asset->created_by = auth()->id();
@@ -682,8 +693,8 @@ class AssetsController extends Controller
// If input value is null, use custom field's default value
if ($field_val == null) {
Log::debug('Field value for ' . $field->db_column . ' is null');
$field_val = $field->defaultValue($request->get('model_id'));
Log::debug('Use the default fieldset value of ' . $field->defaultValue($request->get('model_id')));
$field_val = $field->defaultValue($request->input('model_id'));
Log::debug('Use the default fieldset value of ' . $field->defaultValue($request->input('model_id')));
}
// if the field is set to encrypted, make sure we encrypt the value
@@ -694,7 +705,7 @@ class AssetsController extends Controller
// If input value is null, use custom field's default value
if (($field_val == null) && ($request->has('model_id') != '')) {
$field_val = Crypt::encrypt($field->defaultValue($request->get('model_id')));
$field_val = Crypt::encrypt($field->defaultValue($request->input('model_id')));
} else {
$field_val = Crypt::encrypt($request->input($field->db_column));
}
@@ -712,15 +723,15 @@ class AssetsController extends Controller
}
if ($asset->save()) {
if ($request->get('assigned_user')) {
if ($request->input('assigned_user')) {
$target = User::find(request('assigned_user'));
} elseif ($request->get('assigned_asset')) {
} elseif ($request->input('assigned_asset')) {
$target = Asset::find(request('assigned_asset'));
} elseif ($request->get('assigned_location')) {
} elseif ($request->input('assigned_location')) {
$target = Location::find(request('assigned_location'));
}
if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name')));
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->input('name')));
}
if ($asset->image) {
@@ -797,19 +808,22 @@ class AssetsController extends Controller
}
}
if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
if (($request->filled('assigned_user')) && ($target = User::find($request->input('assigned_user')))) {
$location = $target->location_id;
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->input('assigned_asset')))) {
$location = $target->location_id;
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
->update(['location_id' => $target->location_id]);
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) {
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->input('assigned_location')))) {
$location = $target->id;
}
if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location);
// Using `->has` preserves the asset name if the name parameter was not included in request.
$asset_name = request()->has('name') ? request('name') : $asset->name;
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', $asset_name, $location);
}
if ($asset->image) {
@@ -953,7 +967,7 @@ class AssetsController extends Controller
}
if ($request->filled('status_id')) {
$asset->status_id = $request->get('status_id');
$asset->status_id = $request->input('status_id');
}
if (! isset($target)) {
@@ -1033,7 +1047,7 @@ class AssetsController extends Controller
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at') . ' ' . date('H:i:s') : date('Y-m-d H:i:s');
$originalValues = $asset->getRawOriginal();
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
if (($request->filled('checkin_at')) && ($request->input('checkin_at') != date('Y-m-d'))) {
$originalValues['action_date'] = $checkin_at;
}
@@ -1134,7 +1148,9 @@ class AssetsController extends Controller
$payload = [
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'note' => $request->input('note'),
'note' => e($request->input('note')),
'status_label' => e($asset->assetstatus->display_name),
'status_type' => $asset->assetstatus->getStatuslabelType(),
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
];
@@ -1142,7 +1158,7 @@ class AssetsController extends Controller
/**
* Update custom fields in the database.
* Validation for these fields is handled through the AssetRequest form request
* $model = AssetModel::find($request->get('model_id'));
* $model = AssetModel::find($request->input('model_id'));
*/
if (($asset->model) && ($asset->model->fieldset)) {
$payload['custom_fields'] = [];
@@ -43,6 +43,7 @@ class CategoriesController extends Controller
'created_at',
'updated_at',
'image',
'tag_color',
'notes',
];
@@ -57,6 +58,7 @@ class CategoriesController extends Controller
'require_acceptance',
'checkin_email',
'image',
'tag_color',
'notes',
])
->with('adminuser')
@@ -263,7 +265,7 @@ class CategoriesController extends Controller
]);
if ($request->filled('search')) {
$categories = $categories->where('name', 'LIKE', '%'.$request->get('search').'%');
$categories = $categories->where('name', 'LIKE', '%'.$request->input('search').'%');
}
$categories = $categories->where('category_type', $category_type)->orderBy('name', 'ASC')->paginate(50);
@@ -38,6 +38,7 @@ class CompaniesController extends Controller
'accessories_count',
'consumables_count',
'components_count',
'tag_color',
'notes',
];
@@ -64,6 +65,11 @@ class CompaniesController extends Controller
$companies->where('created_by', '=', $request->input('created_by'));
}
if ($request->filled('tag_color')) {
$companies->where('tag_color', '=', $request->input('tag_color'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value');
@@ -191,11 +197,12 @@ class CompaniesController extends Controller
'companies.name',
'companies.email',
'companies.image',
'companies.tag_color',
]);
if ($request->filled('search')) {
$companies = $companies->where('companies.name', 'LIKE', '%'.$request->get('search').'%');
$companies = $companies->where('companies.name', 'LIKE', '%'.$request->input('search').'%');
}
$companies = $companies->orderBy('name', 'ASC')->paginate(50);
@@ -58,8 +58,8 @@ class ComponentsController extends Controller
];
$components = Component::select('components.*')
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets')
->withSum('uncontrainedAssets', 'components_assets.assigned_qty');
->with('company', 'location', 'category', 'supplier', 'adminuser', 'manufacturer')
->withSum('unconstrainedAssets as sum_unconstrained_assets', 'components_assets.assigned_qty');
$filter = [];
@@ -87,6 +87,10 @@ class ComponentsController extends Controller
$components->where('components.company_id', '=', $request->input('company_id'));
}
if ($request->filled('order_number')) {
$components->where('components.order_number', '=', $request->input('order_number'));
}
if ($request->filled('category_id')) {
$components->where('category_id', '=', $request->input('category_id'));
}
@@ -112,7 +116,8 @@ class ComponentsController extends Controller
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $components->count()) ? $components->count() : app('api_offset_value');
$components_count = $components->count();
$offset = ($request->input('offset') > $components_count) ? $components_count : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -143,7 +148,7 @@ class ComponentsController extends Controller
break;
}
$total = $components->count();
$total = $components_count;
$components = $components->skip($offset)->take($limit)->get();
return (new ComponentsTransformer)->transformComponents($components, $total);
@@ -302,11 +307,11 @@ class ComponentsController extends Controller
}
// Make sure there is at least one available to checkout
if ($component->numRemaining() < $request->get('assigned_qty')) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->get('assigned_qty')])));
if ($component->numRemaining() < $request->input('assigned_qty')) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->input('assigned_qty')])));
}
if ($component->numRemaining() >= $request->get('assigned_qty')) {
if ($component->numRemaining() >= $request->input('assigned_qty')) {
$asset = Asset::find($request->input('assigned_to'));
$component->assigned_to = $request->input('assigned_to');
@@ -314,18 +319,18 @@ class ComponentsController extends Controller
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'created_at' => Carbon::now(),
'assigned_qty' => $request->get('assigned_qty', 1),
'assigned_qty' => $request->input('assigned_qty', 1),
'created_by' => auth()->id(),
'asset_id' => $request->get('assigned_to'),
'note' => $request->get('note'),
'asset_id' => $request->input('assigned_to'),
'note' => $request->input('note'),
]);
$component->logCheckout($request->input('note'), $asset);
$component->logCheckout($request->input('note'), $asset, null, [], $request->get('assigned_qty', 1));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->get('assigned_qty')])));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->input('assigned_qty')])));
}
/**
@@ -86,6 +86,10 @@ class ConsumablesController extends Controller
$consumables->where('consumables.company_id', '=', $request->input('company_id'));
}
if ($request->filled('order_number')) {
$consumables->where('consumables.order_number', '=', $request->input('order_number'));
}
if ($request->filled('category_id')) {
$consumables->where('category_id', '=', $request->input('category_id'));
}
@@ -326,8 +330,14 @@ class ConsumablesController extends Controller
);
}
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
event(new CheckoutableCheckedOut(
$consumable,
$user,
auth()->user(),
$request->input('note'),
[],
$consumable->checkout_qty,
));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
@@ -346,7 +356,7 @@ class ConsumablesController extends Controller
]);
if ($request->filled('search')) {
$consumables = $consumables->where('consumables.name', 'LIKE', '%'.$request->get('search').'%');
$consumables = $consumables->where('consumables.name', 'LIKE', '%'.$request->input('search').'%');
}
$consumables = $consumables->orderBy('name', 'ASC')->paginate(50);
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreDepartmentRequest;
use App\Http\Transformers\DepartmentsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Department;
@@ -23,21 +24,23 @@ class DepartmentsController extends Controller
public function index(Request $request) : JsonResponse | array
{
$this->authorize('view', Department::class);
$allowed_columns = ['id', 'name', 'image', 'users_count', 'notes'];
$allowed_columns = ['id', 'name', 'image', 'users_count', 'notes', 'tag_color'];
$departments = Department::select(
'departments.id',
'departments.name',
'departments.phone',
'departments.fax',
'departments.location_id',
'departments.company_id',
'departments.manager_id',
'departments.created_at',
'departments.updated_at',
'departments.image',
'departments.notes',
)->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
[
'departments.id',
'departments.name',
'departments.phone',
'departments.fax',
'departments.location_id',
'departments.company_id',
'departments.manager_id',
'departments.created_at',
'departments.updated_at',
'departments.image',
'departments.tag_color',
'departments.notes'
])->with('location')->with('manager')->with('company')->withCount('users as users_count');
if ($request->filled('search')) {
$departments = $departments->TextSearch($request->input('search'));
@@ -59,6 +62,10 @@ class DepartmentsController extends Controller
$departments->where('location_id', '=', $request->input('location_id'));
}
if ($request->filled('tag_color')) {
$departments->where('tag_color', '=', $request->input('departments.tag_color'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $departments->count()) ? $departments->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -94,18 +101,17 @@ class DepartmentsController extends Controller
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*/
public function store(ImageUploadRequest $request) : JsonResponse
public function store(StoreDepartmentRequest $request): JsonResponse
{
$this->authorize('create', Department::class);
$department = new Department;
$department->fill($request->all());
$department->fill($request->validated());
$department = $request->handleImages($department);
$department->created_by = auth()->id();
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
if ($department->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.create.success')));
return response()->json(Helper::formatStandardApiResponse('success', (new DepartmentsTransformer)->transformDepartment($department), trans('admin/departments/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
@@ -121,7 +127,7 @@ class DepartmentsController extends Controller
public function show($id) : array
{
$this->authorize('view', Department::class);
$department = Department::findOrFail($id);
$department = Department::withCount('users as users_count')->findOrFail($id);
return (new DepartmentsTransformer)->transformDepartment($department);
}
@@ -141,7 +147,7 @@ class DepartmentsController extends Controller
$department = $request->handleImages($department);
if ($department->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $department, trans('admin/departments/message.update.success')));
return response()->json(Helper::formatStandardApiResponse('success', (new DepartmentsTransformer)->transformDepartment($department), trans('admin/departments/message.update.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
@@ -185,10 +191,11 @@ class DepartmentsController extends Controller
'id',
'name',
'image',
'tag_color',
]);
if ($request->filled('search')) {
$departments = $departments->where('name', 'LIKE', '%'.$request->get('search').'%');
$departments = $departments->where('name', 'LIKE', '%'.$request->input('search').'%');
}
$departments = $departments->orderBy('name', 'ASC')->paginate(50);
@@ -24,7 +24,7 @@ class GroupsController extends Controller
$this->authorize('view', Group::class);
$groups = Group::select('id', 'name', 'permissions', 'notes', 'created_at', 'updated_at', 'created_by')->with('adminuser')->withCount('users as users_count');
$groups = Group::select(['id', 'name', 'permissions', 'notes', 'created_at', 'updated_at', 'created_by'])->with('adminuser')->withCount('users as users_count');
if ($request->filled('search')) {
$groups = $groups->TextSearch($request->input('search'));
@@ -50,6 +50,7 @@ class GroupsController extends Controller
'id',
'name',
'created_at',
'updated_at',
'users_count',
];
+32 -5
View File
@@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use League\Csv\Reader;
use Onnov\DetectEncoding\EncodingDetector;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
@@ -149,7 +150,9 @@ class ImportController extends Controller
}
$date = date('Y-m-d-his');
$fixed_filename = str_slug($file->getClientOriginalName());
$fixed_filename = Str::of($file->getClientOriginalName())->basename('.csv').'.csv';
try {
$file->move($path, $date.'-'.$fixed_filename);
} catch (FileException $exception) {
@@ -193,7 +196,7 @@ class ImportController extends Controller
$this->authorize('import');
// Run a backup immediately before processing
if ($request->get('run-backup')) {
if ($request->input('run-backup')) {
Log::debug('Backup manually requested via importer');
Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H-i-s')]);
} else {
@@ -209,38 +212,49 @@ class ImportController extends Controller
$errors = $request->import($import);
$redirectTo = 'hardware.index';
switch ($request->get('import-type')) {
switch ($request->input('import-type')) {
case 'asset':
$model_perms = 'App\Models\Asset';
$redirectTo = 'hardware.index';
break;
case 'assetModel':
$model_perms = 'App\Models\AssetModel';
$redirectTo = 'models.index';
break;
case 'accessory':
$model_perms = 'App\Models\Accessory';
$redirectTo = 'accessories.index';
break;
case 'consumable':
$model_perms = 'App\Models\Consumable';
$redirectTo = 'consumables.index';
break;
case 'component':
$model_perms = 'App\Models\Component';
$redirectTo = 'components.index';
break;
case 'license':
$model_perms = 'App\Models\License';
$redirectTo = 'licenses.index';
break;
case 'user':
$model_perms = 'App\Models\User';
$redirectTo = 'users.index';
break;
case 'location':
$model_perms = 'App\Models\Location';
$redirectTo = 'locations.index';
break;
case 'supplier':
$model_perms = 'App\Models\Supplier';
$redirectTo = 'suppliers.index';
break;
case 'manufacturer':
$model_perms = 'App\Models\Manufacturer';
$redirectTo = 'manufacturers.index';
break;
case 'category':
$model_perms = 'App\Models\Category';
$redirectTo = 'categories.index';
break;
}
@@ -251,7 +265,11 @@ class ImportController extends Controller
//Flash message before the redirect
Session::flash('success', trans('admin/hardware/message.import.success'));
return response()->json(Helper::formatStandardApiResponse('success', null, ['redirect_url' => route($redirectTo)]));
if (auth()->user()->can('view', $model_perms)) {
return response()->json(Helper::formatStandardApiResponse('success', null, ['redirect_url' => route($redirectTo)]));
}
return response()->json(Helper::formatStandardApiResponse('success', null, ['redirect_url' => route('imports.index')]));
}
/**
@@ -261,9 +279,16 @@ class ImportController extends Controller
*/
public function destroy($import_id) : JsonResponse
{
$this->authorize('create', Asset::class);
$this->authorize('import');
if ($import = Import::find($import_id)) {
if ((auth()->user()->id != $import->created_by) && (!auth()->user()->isSuperUser())) {
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
try {
// Try to delete the file
Storage::delete('imports/'.$import->file_path);
@@ -280,4 +305,6 @@ class ImportController extends Controller
}
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
}
@@ -24,7 +24,7 @@ class LabelsController extends Controller
$labels = Label::find();
if ($request->filled('search')) {
$search = $request->get('search');
$search = $request->input('search');
$labels = $labels->filter(function ($label, $index) use ($search) {
return stripos($label->getName(), $search) !== false;
});
@@ -32,11 +32,11 @@ class LabelsController extends Controller
$total = $labels->count();
$offset = $request->get('offset', 0);
$offset = $request->input('offset', 0);
$offset = ($offset > $total) ? $total : $offset;
$maxLimit = config('app.max_results');
$limit = $request->get('limit', $maxLimit);
$limit = $request->input('limit', $maxLimit);
$limit = ($limit > $maxLimit) ? $maxLimit : $limit;
$labels = $labels->skip($offset)->take($limit);
@@ -26,11 +26,11 @@ class LicenseSeatsController extends Controller
if ($license = License::find($licenseId)) {
$this->authorize('view', $license);
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department')
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department', 'user.company', 'asset.company')
->where('license_seats.license_id', $licenseId);
if ($request->input('status') == 'available') {
$seats->whereNull('license_seats.assigned_to');
$seats->whereNull('license_seats.assigned_to')->whereNull('license_seats.asset_id');
}
if ($request->input('status') == 'assigned') {
@@ -40,8 +40,10 @@ class LicenseSeatsController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
if ($request->input('sort') == 'department') {
if ($request->input('sort') == 'assigned_user.department') {
$seats->OrderDepartments($order);
} elseif ($request->input('sort') == 'assigned_user.company') {
$seats->OrderCompany($order);
} else {
$seats->orderBy('updated_at', $order);
}
@@ -77,17 +79,14 @@ class LicenseSeatsController extends Controller
{
$this->authorize('view', License::class);
// sanity checks:
// 1. does the license seat exist?
if (! $licenseSeat = LicenseSeat::find($seatId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
}
// 2. does the seat belong to the specified license?
if (! $license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
if ($licenseSeat = LicenseSeat::where('license_id', $licenseId)->find($seatId)) {
return (new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat);
}
return (new LicenseSeatsTransformer)->transformLicenseSeat($licenseSeat);
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat ID or license not found or the seat does not belong to this license'));
}
/**
@@ -99,66 +98,103 @@ class LicenseSeatsController extends Controller
*/
public function update(Request $request, $licenseId, $seatId) : JsonResponse | array
{
$validated = $this->validate($request, [
'assigned_to' => [
'sometimes',
'int',
'nullable',
'prohibits:asset_id',
// must be a valid user or null to unassign
function ($attribute, $value, $fail) {
if (!is_null($value) && !User::where('id', $value)->whereNull('deleted_at')->exists()) {
$fail('The selected assigned_to is invalid.');
}
},
],
'asset_id' => [
'sometimes',
'int',
'nullable',
'prohibits:assigned_to',
// must be a valid asset or null to unassign
function ($attribute, $value, $fail) {
if (!is_null($value) && !Asset::where('id', $value)->whereNull('deleted_at')->exists()) {
$fail('The selected asset_id is invalid.');
}
},
],
'notes' => 'sometimes|string|nullable',
]);
$this->authorize('checkout', License::class);
$licenseSeat = LicenseSeat::with(['license', 'asset', 'user'])->find($seatId);
if (! $licenseSeat = LicenseSeat::find($seatId)) {
if (!$licenseSeat) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
}
$license = $licenseSeat->license()->first();
$license = $licenseSeat->license;
if (!$license || $license->id != intval($licenseId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
}
$oldUser = $licenseSeat->user()->first();
$oldAsset = $licenseSeat->asset()->first();
$oldUser = $licenseSeat->user;
$oldAsset = $licenseSeat->asset;
// attempt to update the license seat
$licenseSeat->fill($request->all());
$licenseSeat->created_by = auth()->id();
$licenseSeat->fill($validated);
// check if this update is a checkin operation
// 1. are relevant fields touched at all?
$touched = $licenseSeat->isDirty('assigned_to') || $licenseSeat->isDirty('asset_id');
// 2. are they cleared? if yes then this is a checkin operation
$is_checkin = ($touched && $licenseSeat->assigned_to === null && $licenseSeat->asset_id === null);
$assignmentTouched = $licenseSeat->isDirty('assigned_to') || $licenseSeat->isDirty('asset_id');
$anythingTouched = $licenseSeat->isDirty();
if (! $touched) {
// nothing to update
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
if (! $anythingTouched) {
return response()->json(
Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success'))
);
}
if( $touched && $licenseSeat->unreassignable_seat) {
if( $assignmentTouched && $licenseSeat->unreassignable_seat) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.checkout.unavailable')));
}
// 2. are they cleared? if yes then this is a checkin operation
$is_checkin = ($assignmentTouched && $licenseSeat->assigned_to === null && $licenseSeat->asset_id === null);
$target = null;
// the logging functions expect only one "target". if both asset and user are present in the request,
// we simply let assets take precedence over users...
if ($licenseSeat->isDirty('assigned_to')) {
$target = $is_checkin ? $oldUser : User::find($licenseSeat->assigned_to);
}
if ($licenseSeat->isDirty('asset_id')) {
$target = $is_checkin ? $oldAsset : Asset::find($licenseSeat->asset_id);
}
if (is_null($target)){
return response()->json(Helper::formatStandardApiResponse('error', null, 'Target not found'));
if ($assignmentTouched && is_null($target)){
// if both asset_id and assigned_to are null then we are "checking-in"
// a related model that does not exist (possible purged or bad data).
if (!is_null($request->input('asset_id')) || !is_null($request->input('assigned_to'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Target not found'));
}
}
if ($licenseSeat->save()) {
if ($is_checkin) {
if(!$licenseSeat->license->reassignable){
$licenseSeat->unreassignable_seat = true;
$licenseSeat->save();
if($assignmentTouched) {
if ($is_checkin) {
if (!$licenseSeat->license->reassignable) {
$licenseSeat->unreassignable_seat = true;
$licenseSeat->save();
}
// todo: skip if target is null?
$licenseSeat->logCheckin($target, $licenseSeat->notes);
} else {
// 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('notes'), $target);
}
$licenseSeat->logCheckin($target, $licenseSeat->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('notes'), $target);
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}
@@ -32,7 +32,7 @@ class LicensesController extends Controller
$licenses->ExpiredLicenses();
} elseif ($request->input('status')=='expiring') {
$licenses->ExpiringLicenses($settings->alert_interval);
} else {
} elseif ($request->input('status')=='active') {
$licenses->ActiveLicenses();
}
@@ -265,7 +265,7 @@ class LicensesController extends Controller
]);
if ($request->filled('search')) {
$licenses = $licenses->where('licenses.name', 'LIKE', '%'.$request->get('search').'%');
$licenses = $licenses->where('licenses.name', 'LIKE', '%'.$request->input('search').'%');
}
$licenses = $licenses->orderBy('name', 'ASC')->paginate(50);
@@ -59,6 +59,7 @@ class LocationsController extends Controller
'state',
'updated_at',
'zip',
'tag_color',
'notes',
];
@@ -81,6 +82,8 @@ class LocationsController extends Controller
'locations.ldap_ou',
'locations.currency',
'locations.company_id',
'locations.tag_color',
'locations.tag_color',
'locations.notes',
'locations.created_by',
'locations.deleted_at',
@@ -145,6 +148,10 @@ class LocationsController extends Controller
$locations->onlyTrashed();
}
if ($request->filled('tag_color')) {
$locations->where('tag_color', '=', $request->input('locations.tag_color'));
}
// 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');
@@ -193,7 +200,7 @@ class LocationsController extends Controller
// Only scope location if the setting is enabled
if (Setting::getSettings()->scope_locations_fmcs) {
$location->company_id = Company::getIdForCurrentUser($request->get('company_id'));
$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) {
response()->json(Helper::formatStandardApiResponse('error', null, 'different company than parent'));
@@ -235,6 +242,7 @@ class LocationsController extends Controller
'locations.currency',
'locations.company_id',
'locations.notes',
'locations.tag_color',
])
->withCount('assignedAssets as assigned_assets_count')
->withCount('assets as assets_count')
@@ -270,13 +278,13 @@ class LocationsController extends Controller
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'));
$location->company_id = Company::getIdForCurrentUser($request->input('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');
$location->company_id = $request->input('company_id');
}
}
@@ -402,6 +410,7 @@ class LocationsController extends Controller
'locations.name',
'locations.parent_id',
'locations.image',
'locations.tag_color',
]);
// Only scope locations if the setting is enabled
@@ -82,6 +82,8 @@ class MaintenancesController extends Controller
'location',
'is_warranty',
'status_label',
'model',
'model_number',
];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -100,6 +102,12 @@ class MaintenancesController extends Controller
case 'asset_name':
$maintenances = $maintenances->OrderByAssetName($order);
break;
case 'model':
$maintenances = $maintenances->OrderByAssetModelName($order);
break;
case 'model_number':
$maintenances = $maintenances->OrderByAssetModelNumber($order);
break;
case 'serial':
$maintenances = $maintenances->OrderByAssetSerial($order);
break;
@@ -47,6 +47,7 @@ class ManufacturersController extends Controller
'consumables_count',
'components_count',
'licenses_count',
'tag_color',
'notes',
];
@@ -63,6 +64,7 @@ class ManufacturersController extends Controller
'updated_at',
'image',
'deleted_at',
'tag_color',
'notes',
])
->with('adminuser')
@@ -76,10 +78,16 @@ class ManufacturersController extends Controller
$manufacturers->onlyTrashed();
}
if ($request->input('status') == 'deleted') {
$manufacturers->onlyTrashed();
}
if ($request->filled('search')) {
$manufacturers = $manufacturers->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$manufacturers->where('name', '=', $request->input('name'));
}
@@ -104,6 +112,10 @@ class ManufacturersController extends Controller
$manufacturers->where('support_email', '=', $request->input('support_email'));
}
if ($request->filled('tag_color')) {
$manufacturers->where('tag_color', '=', $request->input('manufacturers.tag_color'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -258,10 +270,11 @@ class ManufacturersController extends Controller
'id',
'name',
'image',
'tag_color',
]);
if ($request->filled('search')) {
$manufacturers = $manufacturers->where('name', 'LIKE', '%'.$request->get('search').'%');
$manufacturers = $manufacturers->where('name', 'LIKE', '%'.$request->input('search').'%');
}
$manufacturers = $manufacturers->orderBy('name', 'ASC')->paginate(50);
@@ -145,7 +145,7 @@ class PredefinedKitsController extends Controller
]);
if ($request->filled('search')) {
$kits = $kits->where('name', 'LIKE', '%'.$request->get('search').'%');
$kits = $kits->where('name', 'LIKE', '%'.$request->input('search').'%');
}
$kits = $kits->orderBy('name', 'ASC')->paginate(50);
@@ -184,7 +184,7 @@ class PredefinedKitsController extends Controller
$quantity = 1;
}
$license_id = $request->get('license');
$license_id = $request->input('license');
$relation = $kit->licenses();
if ($relation->find($license_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, ['license' => trans('admin/kits/general.license_error')]));
@@ -254,7 +254,7 @@ class PredefinedKitsController extends Controller
$kit = PredefinedKit::findOrFail($kit_id);
$model_id = $request->get('model');
$model_id = $request->input('model');
$quantity = $request->input('quantity', 1);
if ($quantity < 1) {
$quantity = 1;
@@ -332,7 +332,7 @@ class PredefinedKitsController extends Controller
$quantity = 1;
}
$consumable_id = $request->get('consumable');
$consumable_id = $request->input('consumable');
$relation = $kit->consumables();
if ($relation->find($consumable_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, ['consumable' => trans('admin/kits/general.consumable_error')]));
@@ -406,7 +406,7 @@ class PredefinedKitsController extends Controller
$quantity = 1;
}
$accessory_id = $request->get('accessory');
$accessory_id = $request->input('accessory');
$relation = $kit->accessories();
if ($relation->find($accessory_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, ['accessory' => trans('admin/kits/general.accessory_error')]));
+19 -7
View File
@@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ProfileTransformer;
use App\Models\CheckoutRequest;
use App\Models\Setting;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Http\Request;
@@ -14,6 +15,7 @@ use Laravel\Passport\TokenRepository;
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
use Illuminate\Support\Facades\Gate;
use App\Models\CustomField;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
@@ -179,14 +181,24 @@ class ProfileController extends Controller
*@since [v8.1.16]
* @author [Godfrey Martinez] [<gmartinez@grokability.com>]
*/
public function eulas(ProfileTransformer $transformer)
public function eulas(ProfileTransformer $transformer, Request $request)
{
// Only return this user's EULAs
$eulas = auth()->user()->eulas;
return response()->json(
$transformer->transformFiles($eulas, $eulas->count())
);
if (($request->filled('user_id')) && ($request->input( 'user_id') != 0)) {
$eula_user = User::find($request->input('user_id'));
if (($eula_user) && (Setting::getSettings()->manager_view_enabled) && (auth()->user()->isManagerOf($eula_user))) {
$eulas = $eula_user->eulas;
} else {
return response()->json(Helper:: formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found')));
}
} else {
$eulas = auth()->user()->eulas;
}
return response()->json($transformer->transformFiles($eulas, $eulas->count()));
}
}
@@ -226,7 +226,7 @@ class SettingsController extends Controller
$login_attempts = DB::table('login_attempts');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'created_at';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
$total = $login_attempts->count();
$login_attempts->orderBy($sort, $order);
@@ -324,7 +324,7 @@ class StatuslabelsController extends Controller
$statuslabels = Statuslabel::orderBy('default_label', 'desc')->orderBy('name', 'asc')->orderBy('deployable', 'desc');
if ($request->filled('search')) {
$statuslabels = $statuslabels->where('name', 'LIKE', '%'.$request->get('search').'%');
$statuslabels = $statuslabels->where('name', 'LIKE', '%'.$request->input('search').'%');
}
if ($request->filled('deployable')) {
@@ -50,12 +50,13 @@ class SuppliersController extends Controller
'accessories_count',
'components_count',
'consumables_count',
'tag_color',
'url',
'notes',
];
$suppliers = Supplier::select(
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip'])
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip', 'tag_color'])
->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
->withCount('accessories as accessories_count')
@@ -251,10 +252,11 @@ class SuppliersController extends Controller
'id',
'name',
'image',
'tag_color',
]);
if ($request->filled('search')) {
$suppliers = $suppliers->where('suppliers.name', 'LIKE', '%'.$request->get('search').'%');
$suppliers = $suppliers->where('suppliers.name', 'LIKE', '%'.$request->input('search').'%');
}
$suppliers = $suppliers->orderBy('name', 'ASC')->paginate(50);
@@ -110,15 +110,18 @@ class UploadedFilesController extends Controller
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file);
$files[] = $file_name;
$object->logUpload($file_name, $request->get('notes'));
$object->logUpload($file_name, $request->input('notes'));
}
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
->where('item_type', '=', self::$map_object_type[$object_type])
->where('item_id', '=', $id)->whereIn('filename', $files)
->get();
if (isset($files)) {
$file_results = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
->where('item_type', '=', self::$map_object_type[$object_type])
->where('item_id', '=', $id)->whereIn('filename', $files)
->get();
return response()->json(Helper::formatStandardApiResponse('success', (new UploadedFilesTransformer())->transformFiles($file_results, count($file_results)), trans_choice('general.file_upload_status.upload.success', count($files))));
}
return response()->json(Helper::formatStandardApiResponse('success', (new UploadedFilesTransformer())->transformFiles($files, count($files)), trans_choice('general.file_upload_status.upload.success', count($files))));
}
// No files were submitted
@@ -185,7 +188,7 @@ class UploadedFilesController extends Controller
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
$this->authorize('update', self::$map_object_type[$object_type]);
$this->authorize('update', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
+139 -83
View File
@@ -31,6 +31,7 @@ use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
use App\Http\Requests\DeleteUserRequest;
use Illuminate\Http\JsonResponse;
use App\Http\Requests\FilterRequest;
class UsersController extends Controller
{
@@ -42,7 +43,7 @@ class UsersController extends Controller
*
* @return array
*/
public function index(Request $request) : array
public function index(FilterRequest $request) : array
{
$this->authorize('view', User::class);
@@ -162,6 +163,11 @@ class UsersController extends Controller
if ($request->filled('filter')) {
$filter = json_decode($request->input('filter'), true);
if (is_null($filter)) {
$filter = [];
}
$filter = array_filter($filter, function ($key) use ($allowed_columns) {
return in_array($key, $allowed_columns);
}, ARRAY_FILTER_USE_KEY);
@@ -247,7 +253,7 @@ class UsersController extends Controller
}
if ($request->filled('group_id')) {
$users = $users->ByGroup($request->get('group_id'));
$users = $users->ByGroup($request->input('group_id'));
}
if ($request->filled('department_id')) {
@@ -394,11 +400,11 @@ class UsersController extends Controller
if ($request->filled('search')) {
$users = $users->where(function ($query) use ($request) {
$query->SimpleNameSearch($request->get('search'))
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('display_name', 'LIKE', '%'.$request->get('search').'%')
->orWhere('email', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
$query->SimpleNameSearch($request->input('search'))
->orWhere('username', 'LIKE', '%'.$request->input('search').'%')
->orWhere('display_name', 'LIKE', '%'.$request->input('search').'%')
->orWhere('email', 'LIKE', '%'.$request->input('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->input('search').'%');
});
}
@@ -444,16 +450,24 @@ class UsersController extends Controller
if ($request->has('permissions')) {
$permissions_array = $request->input('permissions');
// Strip out the superuser permission if the API user isn't a superadmin
if (! auth()->user()->isSuperUser()) {
unset($permissions_array['superuser']);
if ((is_array($permissions_array)) && (array_key_exists('superuser', $permissions_array))) {
unset($permissions_array['superuser']);
}
}
if (!auth()->user()->isAdmin()) {
if ((is_array($permissions_array)) && (array_key_exists('admin', $permissions_array))) {
unset($permissions_array['admin']);
}
}
$user->permissions = $permissions_array;
}
//
if ($request->filled('password')) {
$user->password = bcrypt($request->get('password'));
$user->password = bcrypt($request->input('password'));
} else {
$user->password = $user->noPassword();
}
@@ -472,12 +486,22 @@ class UsersController extends Controller
}
if ($request->filled('groups')) {
if (($request->has('groups')) && (auth()->user()->isSuperUser())) {
$validator = Validator::make($request->only('groups'), [
'groups.*' => 'integer|exists:permission_groups,id',
]);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
}
// Sync the groups since the user is a superuser and the groups pass validation
$user->groups()->sync($request->input('groups'));
} else {
$user->groups()->sync([]);
}
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.create')));
}
@@ -514,95 +538,121 @@ class UsersController extends Controller
*/
public function update(SaveUserRequest $request, User $user): JsonResponse
{
$this->authorize('update', User::class);
$this->authorize('update', $user);
$this->authorize('update', $user);
/**
* This is a janky hack to prevent people from changing admin demo user data on the public demo.
* The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
* Thanks, jerks. You are why we can't have nice things. - snipe
*
*/
/**
* This is a janky hack to prevent people from changing admin demo user data on the public demo.
* The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
* Thanks, jerks. You are why we can't have nice things. - snipe
*
*/
if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
}
// Pull out sensitive fields that require extra permission
$user->fill($request->except(['password', 'username', 'email', 'activated', 'permissions', 'activation_code', 'remember_token', 'two_factor_secret', 'two_factor_enrolled', 'two_factor_optin']));
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
if ($request->filled('password')) {
$user->password = bcrypt($request->input('password'));
}
$user->fill($request->all());
if ($request->filled('company_id')) {
$user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
if ($request->filled('username')) {
$user->username = $request->input('username');
}
if ($user->id == $request->input('manager_id')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
if ($request->filled('email')) {
$user->email = $request->input('email');
}
// check for permissions related fields and pull them out if the current user cannot edit them
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
if ($request->filled('password')) {
$user->password = bcrypt($request->input('password'));
}
if ($request->filled('username')) {
$user->username = $request->input('username');
}
if ($request->filled('display_name')) {
$user->display_name = $request->input('display_name');
}
if ($request->filled('email')) {
$user->email = $request->input('email');
}
if ($request->filled('activated')) {
$user->activated = $request->input('activated');
}
if ($request->filled('activated')) {
$user->activated = $request->input('activated');
}
// We need to use has() instead of filled()
// here because we need to overwrite permissions
// if someone needs to null them out
if ($request->has('permissions')) {
$permissions_array = $request->input('permissions');
$orig_permissions_array = $user->decodePermissions();
// Strip out the individual superuser permission if the API user isn't a superadmin
if (!auth()->user()->isSuperUser()) {
unset($permissions_array['superuser']);
if (is_array($orig_permissions_array)) {
if (array_key_exists('superuser', $orig_permissions_array)) {
$permissions_array['superuser'] = $orig_permissions_array['superuser'];
}
}
}
// Strip out the individual admin permission if the API user isn't an admin
if ((!auth()->user()->isAdmin()) && (!auth()->user()->isSuperUser())) {
if (is_array($orig_permissions_array)) {
if (array_key_exists('admin', $orig_permissions_array)) {
$permissions_array['admin'] = $orig_permissions_array['admin'];
}
}
}
// This is going to update the whole thing, not just what was passed
$user->permissions = $permissions_array;
}
if($request->has('location_id')) {
// Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
}
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
}
if ($user->save()) {
// Check if the request has groups passed and has a value, AND that the user us a superuser
if (($request->has('groups')) && (auth()->user()->isSuperUser())) {
$validator = Validator::make($request->only('groups'), [
'groups.*' => 'integer|exists:permission_groups,id',
]);
if ($request->filled('display_name')) {
$user->display_name = $request->input('display_name');
}
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
}
if ($request->filled('company_id')) {
$user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
}
// Sync the groups since the user is a superuser and the groups pass validation
$user->groups()->sync($request->input('groups'));
if ($user->id == $request->input('manager_id')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
}
if ($request->has('location_id')) {
// Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
}
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
if ($user->save()) {
// Check if the request has groups passed and has a value, AND that the user us a superuser
if (($request->has('groups')) && (auth()->user()->isSuperUser())) {
$validator = Validator::make($request->only('groups'), [
'groups.*' => 'integer|exists:permission_groups,id',
]);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()));
}
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
// Sync the groups since the user is a superuser and the groups pass validation
$user->groups()->sync($request->input('groups'));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors()));
}
/**
@@ -620,21 +670,27 @@ class UsersController extends Controller
$this->authorize('delete', $user);
if ($user->delete()) {
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
// Remove the user's avatar if they have one
if (Storage::disk('public')->exists('avatars/' . $user->avatar)) {
try {
Storage::disk('public')->delete('avatars/' . $user->avatar);
} catch (\Exception $e) {
Log::debug($e);
}
if ($user->delete()) {
// Remove the user's avatar if they have one
// @todo This should be done on purge, not here
// if (Storage::disk('public')->exists('avatars/' . $user->avatar)) {
// try {
// Storage::disk('public')->delete('avatars/' . $user->avatar);
// } catch (\Exception $e) {
// Log::debug($e);
// }
// }
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
}
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.delete')));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.error.delete')));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.cannot_delete')));
}
@@ -783,7 +839,7 @@ class UsersController extends Controller
if ($request->filled('id')) {
try {
$user = User::find($request->get('id'));
$user = User::find($request->input('id'));
$this->authorize('update', $user);
$user->two_factor_secret = null;
$user->two_factor_enrolled = 0;
@@ -98,10 +98,10 @@ class AssetCheckinController extends Controller
$asset->expected_checkin = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
$asset->name = $request->get('name');
$asset->name = $request->input('name');
if ($request->filled('status_id')) {
$asset->status_id = e($request->get('status_id'));
$asset->status_id = e($request->input('status_id'));
}
// Add any custom fields that should be included in the checkout
@@ -112,11 +112,11 @@ class AssetCheckinController extends Controller
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
Log::debug('NEW Location ID: '.$request->get('location_id'));
$asset->location_id = $request->get('location_id');
Log::debug('NEW Location ID: '.$request->input('location_id'));
$asset->location_id = $request->input('location_id');
if ($request->get('update_default_location') == 0){
$asset->rtd_location_id = $request->get('location_id');
if ($request->input('update_default_location') == 0){
$asset->rtd_location_id = $request->input('location_id');
}
}
@@ -124,9 +124,9 @@ class AssetCheckinController extends Controller
// Handle last checkin date
$checkin_at = date('Y-m-d H:i:s');
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
if (($request->filled('checkin_at')) && ($request->input('checkin_at') != date('Y-m-d'))) {
$originalValues['action_date'] = $checkin_at;
$checkin_at = $request->get('checkin_at');
$checkin_at = $request->input('checkin_at');
}
$asset->last_checkin = $checkin_at;
@@ -145,7 +145,7 @@ class AssetCheckinController extends Controller
$acceptance->delete();
});
session()->put('redirect_option', $request->get('redirect_option'));
session()->put('redirect_option', $request->input('redirect_option'));
// Add any custom fields that should be included in the checkout
$asset->customFieldsForCheckinCheckout('display_checkin');
@@ -88,17 +88,17 @@ class AssetCheckoutController extends Controller
$asset = $this->updateAssetLocation($asset, $target);
$checkout_at = date('Y-m-d H:i:s');
if (($request->filled('checkout_at')) && ($request->get('checkout_at') != date('Y-m-d'))) {
$checkout_at = $request->get('checkout_at');
if (($request->filled('checkout_at')) && ($request->input('checkout_at') != date('Y-m-d'))) {
$checkout_at = $request->input('checkout_at');
}
$expected_checkin = '';
if ($request->filled('expected_checkin')) {
$expected_checkin = $request->get('expected_checkin');
$expected_checkin = $request->input('expected_checkin');
}
if ($request->filled('status_id')) {
$asset->status_id = $request->get('status_id');
$asset->status_id = $request->input('status_id');
}
@@ -123,9 +123,9 @@ class AssetCheckoutController extends Controller
}
}
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
session()->put(['redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => $request->input('checkout_to_type')]);
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) {
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->input('note'), $request->input('name'))) {
return Helper::getRedirectOption($request, $asset->id, 'Assets')
->with('success', trans('admin/hardware/message.checkout.success'));
}
+131 -116
View File
@@ -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\CreateMultipleAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
@@ -98,7 +99,7 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
*/
public function store(ImageUploadRequest $request) : RedirectResponse
public function store(CreateMultipleAssetRequest $request): RedirectResponse
{
$this->authorize(Asset::class);
@@ -135,129 +136,143 @@ class AssetsController extends Controller
$successes = [];
$failures = [];
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
$asset = new Asset();
try {
DB::beginTransaction();
for ($a = 1, $aMax = count($asset_tags); $a <= $aMax; $a++) {
$asset = new Asset();
$asset->model()->associate($model);
$asset->name = $request->input('name');
$asset->model()->associate($model);
$asset->name = $request->input('name');
// Check for a corresponding serial
if (($serials) && (array_key_exists($a, $serials))) {
$asset->serial = $serials[$a];
}
if (($asset_tags) && (array_key_exists($a, $asset_tags))) {
$asset->asset_tag = $asset_tags[$a];
}
$asset->company_id = $companyId;
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->created_by = auth()->id();
$asset->status_id = request('status_id');
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost');
$asset->purchase_date = request('purchase_date', null);
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null);
$asset->byod = request('byod', 0);
if (! empty($settings->audit_interval)) {
$asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString();
}
// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) {
$asset->location_id = $request->input('rtd_location_id', null);
}
if ($request->has('use_cloned_image')) {
$cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-'.date('U').'-'.$cloned_model_img->image;
$new_image = 'assets/'.$new_image_name;
Storage::disk('public')->copy('assets/'.$cloned_model_img->image, $new_image);
$asset->image = $new_image_name;
// Check for a corresponding serial
if (($serials) && (array_key_exists($a, $serials))) {
$asset->serial = $serials[$a];
}
} else {
$asset = $request->handleImages($asset);
}
if (($asset_tags) && (array_key_exists($a, $asset_tags))) {
$asset->asset_tag = $asset_tags[$a];
}
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
$asset->company_id = $companyId;
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->created_by = auth()->id();
$asset->status_id = request('status_id');
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = request('purchase_cost');
$asset->purchase_date = request('purchase_date', null);
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null);
$asset->byod = request('byod', 0);
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (!empty($settings->audit_interval)) {
$asset->next_audit_date = Carbon::now()->addMonths((int)$settings->audit_interval)->toDateString();
}
// Set location_id to rtd_location_id ONLY if the asset isn't being checked out
if (!request('assigned_user') && !request('assigned_asset') && !request('assigned_location')) {
$asset->location_id = $request->input('rtd_location_id', null);
}
if ($request->has('use_cloned_image')) {
$cloned_model_img = Asset::select('image')->find($request->input('clone_image_from_id'));
if ($cloned_model_img) {
$new_image_name = 'clone-' . date('U') . '-' . $cloned_model_img->image;
$new_image = 'assets/' . $new_image_name;
Storage::disk('public')->copy('assets/' . $cloned_model_img->image, $new_image);
$asset->image = $new_image_name;
}
} else {
$asset = $request->handleImages($asset);
}
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
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));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
$asset->{$field->db_column} = $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 the asset before saving
// Note - it can be tempting to instead want to call saveOrFail(), to automatically throw when an object
// is invalid (and can't save). But this won't work, because Custom Fields _overrides_ the save() method
// to inject the Custom Field Rules into the $rules property right before invoking the _real_ save.
// so, instead, we have to catch failures on the 'else' clause and throw there.
if ($asset->isValid() && $asset->save()) {
$target = null;
$location = null;
if ($userId = request('assigned_user')) {
$target = User::find($userId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user'));
}
$location = $target->location_id;
} elseif ($assetId = request('assigned_asset')) {
$target = Asset::find($assetId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset'));
}
$location = $target->location_id;
} elseif ($locationId = request('assigned_location')) {
$target = Location::find($locationId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location'));
}
$location = $target->id;
}
if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->input('name'), $location);
}
$successes[] = "<a href='" . route('hardware.show', $asset) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
} else {
$asset->throwValidationException(); // we have to do this for the reason listed above - can't use saveOrFail()
$failures[] = join(",", $asset->getErrors()->all()); //TODO - this can probably go away soon
}
}
// Validate the asset before saving
if ($asset->isValid() && $asset->save()) {
$target = null;
$location = null;
if ($userId = request('assigned_user')) {
$target = User::find($userId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user'));
}
$location = $target->location_id;
} elseif ($assetId = request('assigned_asset')) {
$target = Asset::find($assetId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset'));
}
$location = $target->location_id;
} elseif ($locationId = request('assigned_location')) {
$target = Location::find($locationId);
if (!$target) {
return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location'));
}
$location = $target->id;
}
if (isset($target)) {
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
}
$successes[] = "<a href='" . route('hardware.show', $asset) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
} else {
$failures[] = join(",", $asset->getErrors()->all());
}
} catch (\Throwable $e) {
\Log::debug("Caught exception in multi-create - rolling back: " . $e->getMessage());
DB::rollBack();
throw $e;
}
if($request->get('redirect_option') === 'back'){
DB::commit();
if($request->input('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
}
session()->put(['checkout_to_type' => $request->get('checkout_to_type'),
session()->put(['checkout_to_type' => $request->input('checkout_to_type'),
'other_redirect' => 'model' ]);
@@ -439,7 +454,7 @@ class AssetsController extends Controller
// Update custom fields in the database.
// 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'));
$model = AssetModel::find($request->input('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->element == 'checkbox' && !$request->has($field->db_column)) {
@@ -465,9 +480,9 @@ class AssetsController extends Controller
}
}
session()->put([
'redirect_option' => $request->get('redirect_option'),
'checkout_to_type' => $request->get('checkout_to_type'),
'other_redirect' => $request->get('redirect_option') === 'other_redirect' ? 'model' : null,
'redirect_option' => $request->input('redirect_option'),
'checkout_to_type' => $request->input('checkout_to_type'),
'other_redirect' => $request->input('redirect_option') === 'other_redirect' ? 'model' : null,
]);
@@ -537,9 +552,9 @@ class AssetsController extends Controller
*/
public function getAssetBySerial(Request $request) : RedirectResponse
{
$topsearch = ($request->get('topsearch')=="true");
$topsearch = ($request->input('topsearch')=="true");
if (!$asset = Asset::where('serial', '=', $request->get('serial'))->first()) {
if (!$asset = Asset::where('serial', '=', $request->input('serial'))->first()) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('view', $asset);
@@ -555,8 +570,8 @@ class AssetsController extends Controller
*/
public function getAssetByTag(Request $request, $tag=null) : RedirectResponse
{
$tag = $tag ? $tag : $request->get('assetTag');
$topsearch = ($request->get('topsearch') == 'true');
$tag = $tag ? $tag : $request->input('assetTag');
$topsearch = ($request->input('topsearch') == 'true');
// Search for an exact and unique asset tag match
$assets = Asset::where('asset_tag', '=', $tag);
@@ -667,8 +682,8 @@ class AssetsController extends Controller
return (new Label())
->with('assets', collect([ $asset ]))
->with('settings', Setting::getSettings())
->with('template', request()->get('template'))
->with('offset', request()->get('offset'))
->with('template', request()->input('template'))
->with('offset', request()->input('offset'))
->with('bulkedit', false)
->with('count', 0);
}
@@ -967,7 +982,7 @@ class AssetsController extends Controller
$this->authorize('audit', Asset::class);
session()->put('redirect_option', $request->get('redirect_option'));
session()->put('redirect_option', $request->input('redirect_option'));
session()->put('other_redirect', 'audit');
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Assets;
use App\Events\CheckoutablesCheckedOutInBulk;
use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
@@ -12,6 +13,7 @@ use App\Models\Setting;
use App\View\Label;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
@@ -588,7 +590,7 @@ class BulkAssetsController extends Controller
if ($request->session()->has('bulk_back_url')) {
$bulk_back_url = $request->session()->pull('bulk_back_url');
}
$assetIds = $request->get('ids');
$assetIds = $request->input('ids');
if(empty($assetIds)) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated'));
@@ -644,6 +646,8 @@ class BulkAssetsController extends Controller
*/
public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException
{
Context::add('action', 'bulk_asset_checkout');
$this->authorize('checkout', Asset::class);
try {
@@ -652,11 +656,11 @@ class BulkAssetsController extends Controller
$target = $this->determineCheckoutTarget();
session()->put(['checkout_to_type' => $target]);
if (! is_array($request->get('selected_assets'))) {
if (! is_array($request->input('selected_assets'))) {
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans('admin/hardware/message.checkout.no_assets_selected'));
}
$asset_ids = array_filter($request->get('selected_assets'));
$asset_ids = array_filter($request->input('selected_assets'));
$assets = Asset::findOrFail($asset_ids);
@@ -692,14 +696,14 @@ class BulkAssetsController extends Controller
}
}
$checkout_at = date('Y-m-d H:i:s');
if (($request->filled('checkout_at')) && ($request->get('checkout_at') != date('Y-m-d'))) {
$checkout_at = $request->get('checkout_at');
if (($request->filled('checkout_at')) && ($request->input('checkout_at') != date('Y-m-d'))) {
$checkout_at = $request->input('checkout_at');
}
$expected_checkin = '';
if ($request->filled('expected_checkin')) {
$expected_checkin = $request->get('expected_checkin');
$expected_checkin = $request->input('expected_checkin');
}
$errors = [];
@@ -709,10 +713,10 @@ class BulkAssetsController extends Controller
// See if there is a status label passed
if ($request->filled('status_id')) {
$asset->status_id = $request->get('status_id');
$asset->status_id = $request->input('status_id');
}
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->input('note')), $asset->name, null);
//TODO - I think this logic is duplicated in the checkOut method?
if ($target->location_id != '') {
@@ -730,6 +734,15 @@ class BulkAssetsController extends Controller
});
if (! $errors) {
CheckoutablesCheckedOutInBulk::dispatch(
$assets,
$target,
$admin,
$checkout_at,
$expected_checkin,
e($request->get('note')),
);
// Redirect to the new asset page
return redirect()->to('hardware')->with('success', trans_choice('admin/hardware/message.multi-checkout.success', $asset_ids));
}
@@ -743,7 +756,7 @@ class BulkAssetsController extends Controller
public function restore(Request $request) : RedirectResponse
{
$this->authorize('update', Asset::class);
$assetIds = $request->get('ids');
$assetIds = $request->input('ids');
if (empty($assetIds)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated'));
@@ -78,6 +78,7 @@ class CategoriesController extends Controller
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->alert_on_response = $request->input('alert_on_response', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$category->tag_color = $request->input('tag_color');
$category->notes = $request->input('notes');
$category->created_by = auth()->id();
@@ -132,6 +133,7 @@ class CategoriesController extends Controller
$category->require_acceptance = $request->input('require_acceptance', '0');
$category->alert_on_response = $request->input('alert_on_response', '0');
$category->checkin_email = $request->input('checkin_email', '0');
$category->tag_color = $request->input('tag_color');
$category->notes = $request->input('notes');
$category = $request->handleImages($category);
@@ -60,6 +60,7 @@ final class CompaniesController extends Controller
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company->email = $request->input('email');
$company->tag_color = $request->input('tag_color');
$company->notes = $request->input('notes');
$company->created_by = auth()->id();
@@ -102,6 +103,7 @@ final class CompaniesController extends Controller
$company->phone = $request->input('phone');
$company->fax = $request->input('fax');
$company->email = $request->input('email');
$company->tag_color = $request->input('tag_color');
$company->notes = $request->input('notes');
$company = $request->handleImages($company);
@@ -98,7 +98,7 @@ class ComponentCheckinController extends Controller
event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now()));
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
return Helper::getRedirectOption($request, $component->id, 'Components')
->with('success', trans('admin/components/message.checkin.success'));
@@ -80,8 +80,8 @@ class ComponentCheckoutController extends Controller
$max_to_checkout = $component->numRemaining();
// Make sure there are at least the requested number of components available to checkout
if ($max_to_checkout < $request->get('assigned_qty')) {
return redirect()->back()->withInput()->with('error', trans('admin/components/message.checkout.unavailable', ['remaining' => $max_to_checkout, 'requested' => $request->get('assigned_qty')]));
if ($max_to_checkout < $request->input('assigned_qty')) {
return redirect()->back()->withInput()->with('error', trans('admin/components/message.checkout.unavailable', ['remaining' => $max_to_checkout, 'requested' => $request->input('assigned_qty')]));
}
$validator = Validator::make($request->all(), [
@@ -115,12 +115,19 @@ class ComponentCheckoutController extends Controller
'note' => $request->input('note'),
]);
event(new CheckoutableCheckedOut($component, $asset, auth()->user(), $request->input('note')));
event(new CheckoutableCheckedOut(
$component,
$asset,
auth()->user(),
$request->input('note'),
[],
$component->checkout_qty,
));
$request->request->add(['checkout_to_type' => 'asset']);
$request->request->add(['assigned_asset' => $asset->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
session()->put(['redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => $request->input('checkout_to_type')]);
return Helper::getRedirectOption($request, $component->id, 'Components')
->with('success', trans('admin/components/message.checkout.success'));
@@ -7,8 +7,8 @@ use App\Http\Requests\ImageUploadRequest;
use App\Models\Company;
use App\Models\Component;
use App\Helpers\Helper;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
@@ -88,10 +88,10 @@ class ComponentsController extends Controller
$component = $request->handleImages($component);
if($request->get('redirect_option') === 'back'){
if($request->input('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
}
@@ -168,7 +168,7 @@ class ComponentsController extends Controller
$component = $request->handleImages($component);
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
if ($component->save()) {
return Helper::getRedirectOption($request, $component->id, 'Components')
@@ -226,6 +226,20 @@ class ComponentsController extends Controller
public function show(Component $component)
{
$this->authorize('view', $component);
return view('components/view', compact('component'));
return view('components/view', compact('component'))->with('snipe_component', $component);
}
public function getClone(Component $component) : View | RedirectResponse
{
$this->authorize('create', Component::class);
$cloned_component = clone $component;
$cloned_component->id = null;
$cloned_component->deleted_at = null;
// Show the page
return view('components/edit')
->with('item', $cloned_component)
->with('component', $cloned_component);
}
}
@@ -102,12 +102,20 @@ class ConsumableCheckoutController extends Controller
}
$consumable->checkout_qty = $quantity;
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
event(new CheckoutableCheckedOut(
$consumable,
$user,
auth()->user(),
$request->input('note'),
[],
$consumable->checkout_qty,
));
$request->request->add(['checkout_to_type' => 'user']);
$request->request->add(['assigned_user' => $user->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
session()->put(['redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => $request->input('checkout_to_type')]);
// Redirect to the new consumable page
@@ -98,10 +98,10 @@ class ConsumablesController extends Controller
$consumable = $request->handleImages($consumable);
}
if($request->get('redirect_option') === 'back'){
if($request->input('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
}
@@ -175,7 +175,7 @@ class ConsumablesController extends Controller
$consumable = $request->handleImages($consumable);
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
if ($consumable->save()) {
return Helper::getRedirectOption($request, $consumable->id, 'Consumables')
+7 -2
View File
@@ -30,16 +30,18 @@ use App\Models\Consumable;
use App\Models\License;
use App\Models\Location;
use App\Models\Maintenance;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\Traits\DisablesDebugbar;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
abstract class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests, DisablesDebugbar, DispatchesJobs, ValidatesRequests;
static $map_object_type = [
'accessories' => Accessory::class,
@@ -52,6 +54,7 @@ abstract class Controller extends BaseController
'licenses' => License::class,
'locations' => Location::class,
'models' => AssetModel::class,
'suppliers' => Supplier::class,
'users' => User::class,
];
@@ -66,6 +69,7 @@ abstract class Controller extends BaseController
'licenses' => 'private_uploads/licenses/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/models/',
'suppliers' => 'private_uploads/suppliers/',
'users' => 'private_uploads/users/',
];
@@ -80,6 +84,7 @@ abstract class Controller extends BaseController
'licenses' => 'license',
'locations' => 'location',
'models' => 'model',
'suppliers' => 'supplier',
'users' => 'user',
];
+19 -19
View File
@@ -112,9 +112,9 @@ class CustomFieldsController extends Controller
if ($request->filled('custom_format')) {
$field->format = $request->get('custom_format');
$field->format = $request->input('custom_format');
} else {
$field->format = $request->get('format');
$field->format = $request->input('format');
}
if ($field->save()) {
@@ -225,34 +225,34 @@ class CustomFieldsController extends Controller
public function update(CustomFieldRequest $request, CustomField $field) : RedirectResponse
{
$this->authorize('update', CustomField::class);
$show_in_email = $request->get("show_in_email", 0);
$display_in_user_view = $request->get("display_in_user_view", 0);
$show_in_email = $request->input("show_in_email", 0);
$display_in_user_view = $request->input("display_in_user_view", 0);
// Override the display settings if the field is encrypted
if ($request->get("field_encrypted") == '1') {
if ($request->input("field_encrypted") == '1') {
$show_in_email = '0';
$display_in_user_view = '0';
}
$field->name = trim($request->get("name"));
$field->element = $request->get("element");
$field->field_values = $request->get("field_values");
$field->name = trim($request->input("name"));
$field->element = $request->input("element");
$field->field_values = $request->input("field_values");
$field->created_by = auth()->id();
$field->help_text = $request->get("help_text");
$field->help_text = $request->input("help_text");
$field->show_in_email = $show_in_email;
$field->is_unique = $request->get("is_unique", 0);
$field->is_unique = $request->input("is_unique", 0);
$field->display_in_user_view = $display_in_user_view;
$field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0);
$field->show_in_listview = $request->get("show_in_listview", 0);
$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);
$field->auto_add_to_fieldsets = $request->input("auto_add_to_fieldsets", 0);
$field->show_in_listview = $request->input("show_in_listview", 0);
$field->show_in_requestable_list = $request->input("show_in_requestable_list", 0);
$field->display_checkin = $request->input("display_checkin", 0);
$field->display_checkout = $request->input("display_checkout", 0);
$field->display_audit = $request->input("display_audit", 0);
if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = $request->get('custom_format');
if ($request->input('format') == 'CUSTOM REGEX') {
$field->format = $request->input('custom_format');
} else {
$field->format = $request->get('format');
$field->format = $request->input('format');
}
if ($field->element == 'checkbox' || $field->element == 'radio'){
@@ -74,7 +74,7 @@ class CustomFieldsetsController extends Controller
{
$this->authorize('create', CustomField::class);
return view('custom_fields.fieldsets.edit')->with('item', new CustomFieldset());
return view('custom_fields.fieldsets.view')->with('custom_fieldset', new CustomFieldset());
}
/**
@@ -91,7 +91,7 @@ class CustomFieldsetsController extends Controller
$this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset([
'name' => $request->get('name'),
'name' => $request->input('name'),
'created_by' => auth()->id(),
]);
@@ -127,7 +127,7 @@ class CustomFieldsetsController extends Controller
public function edit(CustomFieldset $fieldset) : View | RedirectResponse
{
$this->authorize('create', CustomField::class);
return view('custom_fields.fieldsets.edit')->with('item', $fieldset);
return view('custom_fields.fieldsets.view')->with('custom_fieldset', $fieldset);
}
/**
@@ -55,6 +55,7 @@ class DepartmentsController extends Controller
$department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null);
$department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null);
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
$department->tag_color = $request->input('tag_color');
$department->notes = $request->input('notes');
$department = $request->handleImages($department);
@@ -157,6 +158,7 @@ class DepartmentsController extends Controller
$department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null);
$department->phone = $request->input('phone');
$department->fax = $request->input('fax');
$department->tag_color = $request->input('tag_color');
$department->notes = $request->input('notes');
$department = $request->handleImages($department);
+70 -8
View File
@@ -7,6 +7,7 @@ use App\Models\Group;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use \Illuminate\Contracts\View\View;
use \App\Models\User;
/**
* This controller handles all actions related to User Groups for
@@ -43,9 +44,24 @@ class GroupsController extends Controller
$permissions = config('permissions');
$groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions);
$selectedPermissions = $request->old('permissions', $groupPermissions);
$users_query = User::query()
->select(['users.id', 'users.first_name', 'users.last_name', 'users.username'])
->where('show_in_list', 1)
->whereNull('deleted_at');
$users_count = $users_query->count();
$users = collect();
if ($users_count <= config('app.max_unpaginated_records')) {
$users = $users_query->orderBy('first_name', 'asc')->orderBy('last_name', 'asc')->get();
}
// Show the page
return view('groups/edit', compact('permissions', 'selectedPermissions', 'groupPermissions'))->with('group', $group);
return view('groups/edit', compact('permissions', 'selectedPermissions', 'groupPermissions'))
->with('group', $group)
->with('associated_users', collect())
->with('unselected_users', $users)
->with('all_users_count', $users_count);
}
/**
@@ -60,12 +76,23 @@ class GroupsController extends Controller
// create a new group instance
$group = new Group();
$group->name = $request->input('name');
if ($request->filled('permission')) {
$group->permissions = json_encode($request->array('permission'));
} else {
$group->permissions = null;
}
$group->permissions = json_encode($request->input('permission'));
$group->created_by = auth()->id();
$group->notes = $request->input('notes');
if ($group->save()) {
$group->users()->sync($request->input('associated_users'));
if ($request->filled('users_to_sync')) {
$associated_users = explode(',',$request->input('users_to_sync'));
$group->users()->sync($associated_users);
}
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create'));
}
@@ -88,11 +115,35 @@ class GroupsController extends Controller
if ((!is_array($groupPermissions)) || (!$groupPermissions)) {
$groupPermissions = [];
}
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
$associated_users = $group->users()->get();
//dd($associated_users->toArray());
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'))->with('associated_users', $associated_users);
$selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions);
$users_query = User::query()
->select(['users.id', 'users.first_name', 'users.last_name', 'users.username'])
->where('show_in_list', 1)
->whereNull('deleted_at');
$users_count = $users_query->count();
$associated_users = collect();
$unselected_users = collect();
if ($users_count <= config('app.max_unpaginated_records')) {
$associated_users = $group->users()->where('show_in_list', 1)->orderBy('first_name', 'asc')->orderBy('last_name', 'asc')->get();
// Get the unselected users
$unselected_users = User::query()
->select(['users.id', 'users.first_name', 'users.last_name', 'users.username'])
->where('show_in_list', 1)
->whereNotIn('id', $associated_users->pluck('id')->toArray())
->orderBy('first_name', 'asc')
->orderBy('last_name', 'asc')
->get();
}
return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions'))
->with('associated_users', $associated_users)
->with('unselected_users', $unselected_users)
->with('all_users_count', $users_count);
}
/**
@@ -106,13 +157,24 @@ class GroupsController extends Controller
public function update(Request $request, Group $group) : RedirectResponse
{
$group->name = $request->input('name');
$group->permissions = json_encode($request->input('permission'));
if ($request->filled('permission')) {
$group->permissions = json_encode($request->array('permission'));
} else {
$group->permissions = null;
}
$group->notes = $request->input('notes');
if (! config('app.lock_passwords')) {
if ($group->save()) {
$group->users()->sync($request->input('associated_users'));
if ($request->has('users_to_sync')) {
$associated_users = explode(',',$request->input('users_to_sync'));
$group->users()->sync($associated_users);
}
return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.update'));
}
+25 -7
View File
@@ -28,23 +28,41 @@ class HealthController extends BaseController
*/
public function get()
{
try {
if (DB::select('select 2 + 2')) {
return response()->json([
'status' => 'ok',
]);
$db_status = 'ok';
} else {
$db_status = 'Could not connect to database';
}
} catch (\Exception $e) {
\Log::error('Could not connect to database');
return response()->json([
'status' => 'database connection failed',
], 500);
$db_status = 'Could not connect to database';
}
if (is_writable(storage_path('logs'))) {
$filesystem_status = 'ok';
} else {
$filesystem_status = 'Could not write to storage/logs';
}
if (($filesystem_status!='ok') || ($db_status!='ok')) {
return response()->json([
'status' =>
[
'database' => $db_status,
'filesystem' => $filesystem_status,
]
], 500);
}
return response()->json([
'status' => 'ok',
]);
}
}
@@ -47,7 +47,7 @@ class CheckoutKitController extends Controller
*/
public function store(Request $request, $kit_id)
{
$user_id = e($request->get('user_id'));
$user_id = e($request->input('user_id'));
if (is_null($user = User::find($user_id))) {
return redirect()->back()->with('error', trans('admin/users/message.user_not_found'));
}
+1 -1
View File
@@ -81,7 +81,7 @@ class LabelsController extends Controller
$settings = Setting::getSettings();
if (request()->has('settings')) {
$overrides = request()->get('settings');
$overrides = request()->input('settings');
foreach ($overrides as $key => $value) {
$settings->$key = $value;
}
@@ -97,8 +97,8 @@ class LicenseCheckinController extends Controller
$licenseSeat->unreassignable_seat = true;
}
session()->put(['redirect_option' => $request->get('redirect_option')]);
if ($request->get('redirect_option') === 'target'){
session()->put(['redirect_option' => $request->input('redirect_option')]);
if ($request->input('redirect_option') === 'target'){
session()->put(['checkout_to_type' => 'user']);
}
@@ -96,13 +96,13 @@ class LicenseCheckoutController extends Controller
session()->put(['checkout_to_type' => 'asset']);
$checkoutTarget = $this->checkoutToAsset($licenseSeat);
$request->request->add(['assigned_asset' => $checkoutTarget->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']);
session()->put(['redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => 'asset']);
} elseif ($request->filled('assigned_to')) {
session()->put(['checkout_to_type' => 'user']);
$checkoutTarget = $this->checkoutToUser($licenseSeat);
$request->request->add(['assigned_user' => $checkoutTarget->id]);
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']);
session()->put(['redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => 'user']);
}
@@ -102,10 +102,10 @@ class LicensesController extends Controller
$license->created_by = auth()->id();
$license->min_amt = $request->input('min_amt');
if($request->get('redirect_option') === 'back'){
if($request->input('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
}
if ($license->save()) {
@@ -183,7 +183,7 @@ class LicensesController extends Controller
$license->category_id = $request->input('category_id');
$license->min_amt = $request->input('min_amt');
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
if ($license->save()) {
return Helper::getRedirectOption($request, $license->id, 'Licenses')
@@ -315,7 +315,8 @@ class LicensesController extends Controller
public function getExportLicensesCsv()
{
$this->authorize('view', License::class);
\Debugbar::disable();
$this->disableDebugbar();
$response = new StreamedResponse(function () {
// Open output stream
@@ -82,6 +82,7 @@ class LocationsController extends Controller
$location->created_by = auth()->id();
$location->phone = request('phone');
$location->fax = request('fax');
$location->tag_color = $request->input('tag_color');
$location->notes = $request->input('notes');
$location->company_id = Company::getIdForCurrentUser($request->input('company_id'));
@@ -156,6 +157,7 @@ class LocationsController extends Controller
$location->fax = request('fax');
$location->ldap_ou = $request->input('ldap_ou');
$location->manager_id = $request->input('manager_id');
$location->tag_color = $request->input('tag_color');
$location->notes = $request->input('notes');
// Only scope the location if the setting is enabled
@@ -101,6 +101,7 @@ class ManufacturersController extends Controller
$manufacturer->support_email = $request->input('support_email');
$manufacturer->notes = $request->input('notes');
$manufacturer = $request->handleImages($manufacturer);
$manufacturer->tag_color = $request->input('tag_color');
if ($manufacturer->save()) {
return redirect()->route('manufacturers.index')->with('success', trans('admin/manufacturers/message.create.success'));
@@ -142,6 +143,7 @@ class ManufacturersController extends Controller
$manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url');
$manufacturer->support_phone = $request->input('support_phone');
$manufacturer->support_email = $request->input('support_email');
$manufacturer->tag_color = $request->input('tag_color');
$manufacturer->notes = $request->input('notes');
// Set the model's image property to null if the image is being deleted
+1 -1
View File
@@ -38,7 +38,7 @@ class ModalController extends Controller
if (in_array($type, $allowed_types)) {
$view = view("modals.${type}");
$view = view("modals.{$type}");
if ($type == "statuslabel") {
$view->with('statuslabel_types', Helper::statusTypeList());
+21 -12
View File
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\ProfileTransformer;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CurrentInventory;
@@ -34,7 +35,7 @@ class ProfileController extends Controller
*/
public function getIndex() : View
{
$this->authorize('self.profile');
$user = auth()->user();
return view('account/profile', compact('user'));
}
@@ -47,20 +48,25 @@ class ProfileController extends Controller
*/
public function postIndex(ImageUploadRequest $request) : RedirectResponse
{
$this->authorize('self.profile');
$user = auth()->user();
$user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->website = $request->input('website');
$user->gravatar = $request->input('gravatar');
$user->skin = $request->input('skin');
$user->phone = $request->input('phone');
if ((Gate::allows('self.profile')) && (! config('app.lock_passwords'))) {
$user->first_name = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->website = $request->input('website');
$user->gravatar = $request->input('gravatar');
$user->phone = $request->input('phone');
}
$user->enable_sounds = $request->input('enable_sounds', false);
$user->enable_confetti = $request->input('enable_confetti', false);
$user->link_light_color = $request->input('link_light_color', '#296282');
$user->link_dark_color = $request->input('link_dark_color', '#296282');
$user->nav_link_color = $request->input('nav_link_color', '#FFFFFF');
$user->locale = $request->input('locale');
if (! config('app.lock_passwords')) {
$user->locale = $request->input('locale');
}
if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled == '1') && (! config('app.lock_passwords')))) {
$user->two_factor_optin = $request->input('two_factor_optin', '0');
@@ -249,7 +255,10 @@ class ProfileController extends Controller
$logentry = Actionlog::where('filename', $filename)->first();
// Make sure the user has permission to view this file
if (auth()->id() != $logentry->target_id) {
// Also allow if the user (manager) able to view both users and assets
$allowed_to_view_users_assets = Gate::allows('view', User::class) && Gate::allows('view', Asset::class);
if (auth()->id() != $logentry->target_id && !$allowed_to_view_users_assets) {
return redirect()->route('account')->with('error', trans('general.generic_model_not_found', ['model' => 'file']));
}
@@ -19,7 +19,8 @@ class ReportTemplatesController extends Controller
$report = $request->user()->reportTemplates()->create([
'name' => $validated['name'],
'options' => $request->except(['_token', 'name']),
'options' => $request->except(['_token', 'name', 'is_shared']),
'is_shared' => $request->has('is_shared'),
]);
session()->flash('success', trans('admin/reports/message.create.success'));
@@ -45,6 +46,12 @@ class ReportTemplatesController extends Controller
{
$this->authorize('reports.view');
if ($reportTemplate->created_by != auth()->id()) {
return redirect()
->route('report-templates.show', $reportTemplate)
->withError(trans('general.report_not_editable'));
}
return view('reports/custom', [
'customfields' => CustomField::get(),
'template' => $reportTemplate,
@@ -55,13 +62,29 @@ class ReportTemplatesController extends Controller
{
$this->authorize('reports.view');
// Ignore "options" rules since data does not come in under that key...
$validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options'));
if ($reportTemplate->created_by != auth()->id()) {
return redirect()
->route('report-templates.show', $reportTemplate)
->withError(trans('general.report_not_editable'));
}
$reportTemplate->update([
'name' => $validated['name'],
'options' => $request->except(['_token', 'name']),
]);
$properties = [
'name' => $request->input('name'),
'options' => Arr::except($request->all(), ['_token', 'id', 'name', 'is_shared']),
'is_shared' => $reportTemplate->is_shared,
];
if ($reportTemplate->created_by == $request->user()->id) {
$properties['is_shared'] = $request->boolean('is_shared');
}
$reportTemplate->fill($properties);
if ($reportTemplate->isInvalid()) {
return redirect()->back()->withInput()->withErrors($reportTemplate->getErrors());
}
$reportTemplate->save();
session()->flash('success', trans('admin/reports/message.update.success'));
@@ -72,6 +95,12 @@ class ReportTemplatesController extends Controller
{
$this->authorize('reports.view');
if ($reportTemplate->creator()->isNot(auth()->user())) {
return redirect()
->route('report-templates.show', $reportTemplate)
->withError(trans('general.generic_model_not_found', ['model' => 'report template']));
}
$reportTemplate->delete();
return redirect()->route('reports/custom')
+172 -54
View File
@@ -3,12 +3,21 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Mail\CheckoutAccessoryMail;
use App\Mail\CheckoutAssetMail;
use App\Mail\CheckoutComponentMail;
use App\Mail\CheckoutConsumableMail;
use App\Mail\CheckoutLicenseMail;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use App\Models\Checkoutable;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use App\Models\Maintenance;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
@@ -18,9 +27,11 @@ use App\Models\License;
use App\Models\ReportTemplate;
use App\Models\Setting;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\Mail;
use \Illuminate\Contracts\View\View;
use League\Csv\Reader;
@@ -29,6 +40,7 @@ use League\Csv\EscapeFormula;
use App\Http\Requests\CustomAssetReportRequest;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\RedirectResponse;
use function Livewire\before;
/**
* This controller handles all actions related to Reports for
@@ -231,7 +243,8 @@ class ReportsController extends Controller
ini_set('max_execution_time', 12000);
$this->authorize('reports.view');
\Debugbar::disable();
$this->disableDebugbar();
$response = new StreamedResponse(function () {
Log::debug('Starting streamed response');
@@ -426,8 +439,8 @@ class ReportsController extends Controller
ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes
$this->authorize('reports.view');
$this->disableDebugbar();
\Debugbar::disable();
$customfields = CustomField::get();
$response = new StreamedResponse(function () use ($customfields, $request) {
Log::debug('Starting streamed response');
@@ -441,6 +454,10 @@ class ReportsController extends Controller
$header = [];
if($request->filled('is_shared')) {
$header[] = trans('admin/reports/general.share_template');
}
if ($request->filled('id')) {
$header[] = trans('general.id');
}
@@ -538,6 +555,14 @@ class ReportsController extends Controller
$header[] = 'Username';
}
if ($request->filled('user_company')) {
$header[] = trans('admin/reports/general.custom_export.user_company');
}
if ($request->filled('email')) {
$header[] = 'Email';
}
if ($request->filled('employee_num')) {
$header[] = 'Employee No.';
}
@@ -578,6 +603,10 @@ class ReportsController extends Controller
$header[] = trans('admin/reports/general.custom_export.user_zip');
}
if ($request->filled('target_notes')) {
$header[] = trans('admin/reports/general.custom_export.target_notes');
}
if ($request->filled('status')) {
$header[] = trans('general.status');
}
@@ -635,7 +664,14 @@ class ReportsController extends Controller
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
Log::debug('Added headers: '.$executionTime);
$assets = Asset::select('assets.*')->with(
if($request->filled('is_shared')) {
//to fill with logic for the report template and NOT the assets retrieved by the query
//do we scope here or??
}
$assets = Asset::select('assets.*')->with(
'location', 'assetstatus', 'company', 'defaultLoc', 'assignedTo',
'model.category', 'model.manufacturer', 'supplier');
@@ -742,9 +778,15 @@ class ReportsController extends Controller
$assets->whereBetween('assets.updated_at', [$request->input('last_updated_start'), $request->input('last_updated_end')]);
}
if(($request->filled('last_updated_before'))){
$last_updated_window = Carbon::parse(today()->subDays($request->input('last_updated_before')));
$assets->where('assets.updated_at', '<' , $last_updated_window);
}
if ($request->filled('exclude_archived')) {
$assets->notArchived();
}
if ($request->input('deleted_assets') == 'include_deleted') {
$assets->withTrashed();
}
@@ -752,7 +794,6 @@ class ReportsController extends Controller
$assets->onlyTrashed();
}
Log::debug($assets->toSql());
$assets->orderBy('assets.id', 'ASC')->chunk(500, function ($assets) use ($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
@@ -807,7 +848,7 @@ class ReportsController extends Controller
}
if ($request->filled('eol')) {
$row[] = ($asset->purchase_date != '') ? $asset->asset_eol_date : '';
$row[] = ($asset->asset_eol_date != '') ? $asset->asset_eol_date : '';
}
if ($request->filled('warranty')) {
@@ -871,6 +912,23 @@ class ReportsController extends Controller
}
}
if ($request->filled('user_company')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto->company) ? $asset->assignedto->company->display_name : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('email')) {
// Only works if we're checked out to a user, not anything else.
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->email : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('employee_num')) {
// Only works if we're checked out to a user, not anything else.
if ($asset->checkedOutToUser()) {
@@ -952,6 +1010,15 @@ class ReportsController extends Controller
}
}
if ($request->filled('target_notes')) {
if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedto) ? $asset->assignedto->notes : '';
} else {
$row[] = ''; // Empty string if unassigned
}
}
if ($request->filled('status')) {
$row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : '';
}
@@ -1116,34 +1183,32 @@ class ReportsController extends Controller
$this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
$query = CheckoutAcceptance::pending()
->where('checkoutable_type', 'App\Models\Asset')
$query = CheckoutAcceptance::Pending()
->with([
'checkoutable' => function (MorphTo $query) {
$query->morphWith([
AssetModel::class => ['model'],
Company::class => ['company'],
Asset::class => ['assignedTo'],
])->with('model.category');
$query->withTrashed()->morphWith([
Asset::class => ['model.category', 'assignedTo', 'company'],
Accessory::class => ['category','checkouts', 'company'],
LicenseSeat::class => ['user', 'license'],
Component::class => ['assignedTo', 'company'],
Consumable::class => ['company'],
]);
},
'assignedTo' => function($query){
$query->withTrashed();
}
]);
])->orderByDesc('checkout_acceptances.created_at');
if ($showDeleted) {
$query->withTrashed();
}
$assetsForReport = $query->get()
->map(function ($acceptance) {
return [
'assetItem' => $acceptance->checkoutable,
'acceptance' => $acceptance,
];
});
$itemsForReport = $query->get()
->filter(fn ($unaccepted) => $unaccepted->checkoutable)
->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));
return view('reports/unaccepted_assets', compact('assetsForReport','showDeleted' ));
return view('reports/unaccepted_assets', compact('itemsForReport','showDeleted' ));
}
/**
@@ -1155,41 +1220,79 @@ class ReportsController extends Controller
public function sentAssetAcceptanceReminder(Request $request) : RedirectResponse
{
$this->authorize('reports.view');
if (!$acceptance = CheckoutAcceptance::pending()->find($request->input('acceptance_id'))) {
$id = $request->input('acceptance_id');
$query = CheckoutAcceptance::query()
->with([
'checkoutable' => function (MorphTo $query) {
$query->withTrashed()->morphWith([
Asset::class => ['model.category', 'assignedTo', 'company', 'checkouts'],
Accessory::class => ['category', 'company', 'checkouts'],
LicenseSeat::class => ['user', 'license', 'checkouts'],
Component::class => ['assignedTo', 'company', 'checkouts'],
Consumable::class => ['company', 'checkouts'],
]);
},
'assignedTo' => fn ($q) => $q->withTrashed(),
])
->pending();
$acceptance = $query->find($id);
if (!$acceptance) {
Log::debug('No pending acceptances');
// Redirect to the unaccepted assets report page with error
// Redirect to the unaccepted items report page with error
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$item = $acceptance->checkoutable;
$assignee = $acceptance->assignedTo ?? $item->assignedTo ?? null;
$email = $assignee?->email;
$locale = $assignee?->locale;
$assetItem = $acceptance->checkoutable;
Log::debug(print_r($assetItem, true));
Log::debug(print_r($acceptance, true));
if (is_null($acceptance->created_at)){
Log::debug('No acceptance created_at');
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
} else {
$logItem_res = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get();
if($item instanceof LicenseSeat){
$logItem_res = $item->license->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get();
}
else{
$logItem_res = $item->checkouts()->with('adminuser')->where('created_at', '=', $acceptance->created_at)->get();
}
if ($logItem_res->isEmpty()){
Log::debug('Acceptance date mismatch');
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$logItem = $logItem_res[0];
}
$email = $assetItem->assignedTo?->email;
$locale = $assetItem->assignedTo?->locale;
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));
$mailable = $this->getCheckoutMailType($acceptance, $logItem);
Mail::to($email)->send($mailable->locale($locale));
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
}
private function getCheckoutMailType(CheckoutAcceptance $acceptance, $logItem) : Mailable
{
$lookup = [
Accessory::class => CheckoutAccessoryMail::class,
Asset::class => CheckoutAssetMail::class,
LicenseSeat::class => CheckoutLicenseMail::class,
Consumable::class => CheckoutConsumableMail::class,
Component::class => CheckoutComponentMail::class,
];
$mailable= $lookup[get_class($acceptance->checkoutable)];
return new $mailable(
$acceptance->checkoutable,
$acceptance->checkedOutTo ?? $acceptance->assignedTo,
$logItem->adminuser,
$acceptance,
$acceptance->note,
firstTimeSending: false,
);
}
/**
* sentAssetAcceptanceReminder
*
@@ -1221,31 +1324,43 @@ class ReportsController extends Controller
public function postAssetAcceptanceReport($deleted = false) : Response
{
$this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
$showDeleted = request('deleted') === 'deleted';;
/**
* Get all assets with pending checkout acceptances
*/
if($showDeleted) {
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->withTrashed()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
} else {
$acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
}
$assetsForReport = $acceptances
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
});
$acceptances = CheckoutAcceptance::pending()
->with([
'checkoutable' => function (MorphTo $acceptance) {
$acceptance->withTrashed()->morphWith([
Asset::class => ['model.category', 'assignedTo', 'company'],
Accessory::class => ['category','checkouts', 'company'],
LicenseSeat::class => ['user', 'license'],
Component::class => ['assignedTo', 'company'],
Consumable::class => ['company'],
]);
},
'assignedTo',
])->orderByDesc('checkout_acceptances.created_at');
if ($showDeleted) {
$acceptances->withTrashed();
}
$itemsForReport = $acceptances->get()
->filter(fn ($unaccepted) => $unaccepted->checkoutable)
->map(fn ($unaccepted) => Checkoutable::fromAcceptance($unaccepted));
$rows = [];
$header = [
trans('general.date'),
trans('general.type'),
trans('admin/companies/table.title'),
trans('general.category'),
trans('admin/hardware/form.model'),
trans('admin/hardware/form.name'),
trans('general.name'),
trans('admin/hardware/table.asset_tag'),
trans('admin/hardware/table.checkoutto'),
];
@@ -1253,16 +1368,19 @@ class ReportsController extends Controller
$header = array_map('trim', $header);
$rows[] = implode(',', $header);
foreach ($assetsForReport as $item) {
foreach ($itemsForReport as $item) {
if ($item['assetItem'] != null){
if ($item != null){
$row = [ ];
$row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
$row[] = str_replace(',', '', e($item['assetItem']->name));
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->display_name : trans('admin/reports/general.deleted_user')));
$row[] = str_replace(',', '', $item->acceptance->created_at);
$row[] = str_replace(',', '', $item->type);
$row[] = str_replace(',', '', $item->plain_text_company);
$row[] = str_replace(',', '', $item->plain_text_category);
$row[] = str_replace(',', '', $item->plain_text_model);
$row[] = str_replace(',', '', $item->plain_text_name);
$row[] = str_replace(',', '', $item->asset_tag);
$row[] = str_replace(',', '', ($item->acceptance->assignedto) ? $item->acceptance->assignedto->display_name : trans('admin/reports/general.deleted_user'));
$rows[] = implode(',', $row);
}
}
+30 -251
View File
@@ -6,38 +6,31 @@ use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsSamlRequest;
use App\Http\Requests\SetupUserRequest;
use App\Http\Requests\StoreLabelSettings;
use App\Http\Requests\StoreLdapSettings;
use App\Http\Requests\StoreLocalizationSettings;
use App\Http\Requests\StoreNotificationSettings;
use App\Http\Requests\StoreLabelSettings;
use App\Http\Requests\StoreSecuritySettings;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\Group;
use App\Models\Labels\Label as LabelModel;
use App\Models\Setting;
use App\Models\Asset;
use App\Models\User;
use App\Notifications\FirstAdminNotification;
use App\Notifications\MailTest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use \Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Settings for
@@ -47,224 +40,6 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse;
*/
class SettingsController extends Controller
{
/**
* Checks to see whether or not the database has a migrations table
* and a user, otherwise display the setup view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0]
*
* @return \Illuminate\Contracts\View\View | \Illuminate\Http\Response
*/
public function getSetupIndex() : View
{
$start_settings['php_version_min'] = false;
if (version_compare(PHP_VERSION, config('app.min_php'), '<')) {
return response('<center><h1>This software requires PHP version '.config('app.min_php').' or greater. This server is running '.PHP_VERSION.'. </h1><h2>Please upgrade PHP on this server and try again. </h2></center>', 500);
}
try {
$conn = DB::select('select 2 + 2');
$start_settings['db_conn'] = true;
$start_settings['db_name'] = DB::connection()->getDatabaseName();
$start_settings['db_error'] = null;
} catch (\PDOException $e) {
$start_settings['db_conn'] = false;
$start_settings['db_name'] = config('database.connections.mysql.database');
$start_settings['db_error'] = $e->getMessage();
}
$start_settings['url_config'] = trim(config('app.url'), '/'). '/setup';
$start_settings['real_url'] = request()->url();
$start_settings['url_valid'] = $start_settings['url_config'] === $start_settings['real_url'];
$start_settings['php_version_min'] = true;
// Curl the .env file to make sure it's not accessible via a browser
$start_settings['env_exposed'] = $this->dotEnvFileIsExposed();
if (App::Environment('production') && (true == config('app.debug'))) {
$start_settings['debug_exposed'] = true;
} else {
$start_settings['debug_exposed'] = false;
}
$environment = app()->environment();
if ('production' != $environment) {
$start_settings['env'] = $environment;
$start_settings['prod'] = false;
} else {
$start_settings['env'] = $environment;
$start_settings['prod'] = true;
}
$start_settings['owner'] = '';
if (function_exists('posix_getpwuid')) { // Probably Linux
$owner = posix_getpwuid(fileowner($_SERVER['SCRIPT_FILENAME']));
// This *should* be an array, but we've seen this return a bool in some chrooted environments
if (is_array($owner)) {
$start_settings['owner'] = $owner['name'];
}
}
if (($start_settings['owner'] === 'root') || ($start_settings['owner'] === '0')) {
$start_settings['owner_is_admin'] = true;
} else {
$start_settings['owner_is_admin'] = false;
}
$start_settings['writable'] = $this->storagePathIsWritable();
$start_settings['gd'] = extension_loaded('gd');
return view('setup/index')
->with('step', 1)
->with('start_settings', $start_settings)
->with('section', 'Pre-Flight Check');
}
/**
* Determine if the .env file accessible via a browser.
*
* @return bool This method will return true when exceptions (such as curl exception) is thrown.
* Check the log files to see more details about the exception.
*/
protected function dotEnvFileIsExposed() : bool
{
try {
return Http::withoutVerifying()->timeout(10)
->accept('*/*')
->get(URL::to('.env'))
->successful();
} catch (\Exception $e) {
Log::debug($e->getMessage());
return true;
}
}
/**
* Determine if the app storage path is writable.
*/
protected function storagePathIsWritable(): bool
{
return File::isWritable(storage_path()) &&
File::isWritable(storage_path('framework')) &&
File::isWritable(storage_path('framework/cache')) &&
File::isWritable(storage_path('framework/sessions')) &&
File::isWritable(storage_path('framework/views')) &&
File::isWritable(storage_path('logs'));
}
/**
* Save the first admin user from Setup.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
*
*/
public function postSaveFirstAdmin(SetupUserRequest $request) : RedirectResponse
{
$user = new User();
$user->first_name = $data['first_name'] = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->email = $data['email'] = $request->input('email');
$user->activated = 1;
$permissions = ['superuser' => 1];
$user->permissions = json_encode($permissions);
$user->username = $data['username'] = $request->input('username');
$user->password = bcrypt($request->input('password'));
$data['password'] = $request->input('password');
$settings = new Setting();
$settings->full_multiple_companies_support = $request->input('full_multiple_companies_support', 0);
$settings->site_name = $request->input('site_name');
$settings->alert_email = $request->input('email');
$settings->alerts_enabled = 1;
$settings->pwd_secure_min = 10;
$settings->brand = 1;
$settings->locale = $request->input('locale', 'en-US');
$settings->default_currency = $request->input('default_currency', 'USD');
$settings->created_by = 1;
$settings->email_domain = $request->input('email_domain');
$settings->email_format = $request->input('email_format');
$settings->next_auto_tag_base = 1;
$settings->auto_increment_assets = $request->input('auto_increment_assets', 0);
$settings->auto_increment_prefix = $request->input('auto_increment_prefix');
$settings->zerofill_count = $request->input('zerofill_count') ?: 0;
if ((! $user->isValid()) || (! $settings->isValid())) {
return redirect()->back()->withInput()->withErrors($user->getErrors())->withErrors($settings->getErrors());
} else {
$user->save();
Auth::login($user, true);
$settings->save();
if ($request->input('email_creds') == '1') {
$data = [];
$data['email'] = $user->email;
$data['username'] = $user->username;
$data['first_name'] = $user->first_name;
$data['last_name'] = $user->last_name;
$data['password'] = $request->input('password');
$user->notify(new FirstAdminNotification($data));
}
return redirect()->route('setup.done');
}
}
/**
* Return the admin user creation form in Setup.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0]
*/
public function getSetupUser() : View
{
return view('setup/user')
->with('step', 3)
->with('section', 'Create a User');
}
/**
* Return the view that tells the user that the Setup is done.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0]
*/
public function getSetupDone() : View
{
return view('setup/done')
->with('step', 4)
->with('section', 'Done!');
}
/**
* Migrate the database tables, and return the output
* to a view for Setup.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0]
*/
public function getSetupMigrate() : View
{
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', ['--no-interaction' => true]);
}
return view('setup/migrate')
->with('output', 'Databases installed!')
->with('step', 2)
->with('section', 'Create Database Tables');
}
/**
* Return a view that shows some of the key settings.
@@ -339,7 +114,7 @@ class SettingsController extends Controller
$setting->email_domain = $request->input('email_domain');
$setting->email_format = $request->input('email_format');
$setting->username_format = $request->input('username_format');
$setting->require_accept_signature = $request->input('require_accept_signature');
$setting->require_accept_signature = $request->input('require_accept_signature', '0');
$setting->show_assigned_assets = $request->input('show_assigned_assets', '0');
if (! config('app.lock_passwords')) {
$setting->login_note = $request->input('login_note');
@@ -399,12 +174,10 @@ class SettingsController extends Controller
}
$setting->brand = $request->input('brand', '1');
$setting->header_color = $request->input('header_color');
$setting->support_footer = $request->input('support_footer');
$setting->version_footer = $request->input('version_footer');
$setting->footer_text = $request->input('footer_text');
$setting->skin = $request->input('skin');
$setting->allow_user_skin = $request->input('allow_user_skin', '0');
$setting->show_url_in_emails = $request->input('show_url_in_emails', '0');
$setting->logo_print_assets = $request->input('logo_print_assets', '0');
$setting->load_remote = $request->input('load_remote', 0);
@@ -418,6 +191,11 @@ class SettingsController extends Controller
$request->validate(['site_name' => 'required']);
}
$setting->header_color = $request->input('header_color');
$setting->link_light_color = $request->input('link_light_color', '#296282');
$setting->link_dark_color = $request->input('link_dark_color', '#296282');
$setting->nav_link_color = $request->input('nav_link_color', '#FFFFFF');
$setting->site_name = $request->input('site_name', 'Snipe-IT');
$setting->custom_css = $request->input('custom_css');
@@ -589,6 +367,7 @@ class SettingsController extends Controller
$setting->time_display_format = $request->input('time_display_format');
$setting->digit_separator = $request->input('digit_separator');
$setting->name_display_format = $request->input('name_display_format');
$setting->week_start = $request->input('week_start', 0);
if ($setting->save()) {
return redirect()->route('settings.index')
@@ -624,24 +403,23 @@ class SettingsController extends Controller
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
// Check if the audit interval has changed - if it has, we want to update ALL of the assets audit dates
if ($request->input('audit_interval') != $setting->audit_interval) {
// This could be a negative number if the user is trying to set the audit interval to a lower number than it was before
$audit_diff_months = ((int)$request->input('audit_interval') - (int)($setting->audit_interval));
// Check if the audit interval has changed - if it has, check if we should update all of the assets audit dates
if ((($request->input('audit_interval') != $setting->audit_interval)) && ($request->input('update_existing_dates') == 1)) {
// Batch update the dates. We have to use this method to avoid time limit exceeded errors on very large datasets,
// but it DOES mean this change doesn't get logged in the action logs, since it skips the observer.
// @see https://stackoverflow.com/questions/54879160/laravel-observer-not-working-on-bulk-insert
$affected = Asset::whereNotNull('next_audit_date')
->whereNull('deleted_at')
->update(
['next_audit_date' => DB::raw('DATE_ADD(next_audit_date, INTERVAL '.$audit_diff_months.' MONTH)')]
);
Log::debug($affected .' assets affected by audit interval update');
// This could be a negative number if the user is trying to set the audit interval to a lower number than it was before
$audit_diff_months = ((int)$request->input('audit_interval') - (int)($setting->audit_interval));
// Batch update the dates. We have to use this method to avoid time limit exceeded errors on very large datasets,
// but it DOES mean this change doesn't get logged in the action logs, since it skips the observer.
// @see https://stackoverflow.com/questions/54879160/laravel-observer-not-working-on-bulk-insert
$affected = Asset::whereNotNull('next_audit_date')
->whereNull('deleted_at')
->update(
['next_audit_date' => DB::raw('DATE_ADD(next_audit_date, INTERVAL ' . $audit_diff_months . ' MONTH)')]
);
Log::debug($affected . ' assets affected by audit interval update');
}
$alert_email = rtrim($request->input('alert_email'), ',');
@@ -772,6 +550,7 @@ class SettingsController extends Controller
$setting->label2_asset_logo = $request->input('label2_asset_logo');
$setting->label2_1d_type = $request->input('label2_1d_type');
$setting->label2_2d_type = $request->input('label2_2d_type');
$setting->label2_2d_prefix = $request->input('label2_2d_prefix');
$setting->label2_2d_target = $request->input('label2_2d_target');
$setting->label2_fields = $request->input('label2_fields');
$setting->label2_empty_row_count = $request->input('label2_empty_row_count');
+270
View File
@@ -0,0 +1,270 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\SetupUserRequest;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\FirstAdminNotification;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
use \Illuminate\Contracts\View\View;
/**
* This controller handles all actions related to Settings for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*/
class SetupController extends Controller
{
/**
* Checks to see whether or not the database has a migrations table
* and a user, otherwise display the setup view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0]
*
* @return \Illuminate\Contracts\View\View | \Illuminate\Http\Response
*/
public function getSetupIndex() : View
{
$start_settings['php_version_min'] = false;
if (version_compare(PHP_VERSION, config('app.min_php'), '<')) {
return response('<center><h1>This software requires PHP version '.config('app.min_php').' or greater. This server is running '.PHP_VERSION.'. </h1><h2>Please upgrade PHP on this server and try again. </h2></center>', 500);
}
try {
$conn = DB::select('select 2 + 2');
$start_settings['db_conn'] = true;
$start_settings['db_name'] = DB::connection()->getDatabaseName();
$start_settings['db_error'] = null;
} catch (\PDOException $e) {
$start_settings['db_conn'] = false;
$start_settings['db_name'] = config('database.connections.mysql.database');
$start_settings['db_error'] = $e->getMessage();
}
$start_settings['url_config'] = trim(config('app.url'), '/'). '/setup';
$start_settings['real_url'] = request()->url();
$start_settings['url_valid'] = $start_settings['url_config'] === $start_settings['real_url'];
$start_settings['php_version_min'] = true;
// Curl the .env file to make sure it's not accessible via a browser
$start_settings['env_exposed'] = $this->dotEnvFileIsExposed();
if (App::Environment('production') && (true == config('app.debug'))) {
$start_settings['debug_exposed'] = true;
} else {
$start_settings['debug_exposed'] = false;
}
$environment = app()->environment();
if ('production' != $environment) {
$start_settings['env'] = $environment;
$start_settings['prod'] = false;
} else {
$start_settings['env'] = $environment;
$start_settings['prod'] = true;
}
$start_settings['owner'] = '';
if (function_exists('posix_getpwuid')) { // Probably Linux
$owner = posix_getpwuid(fileowner($_SERVER['SCRIPT_FILENAME']));
// This *should* be an array, but we've seen this return a bool in some chrooted environments
if (is_array($owner)) {
$start_settings['owner'] = $owner['name'];
}
}
if (($start_settings['owner'] === 'root') || ($start_settings['owner'] === '0')) {
$start_settings['owner_is_admin'] = true;
} else {
$start_settings['owner_is_admin'] = false;
}
$start_settings['writable'] = $this->storagePathIsWritable();
$start_settings['gd'] = extension_loaded('gd');
return view('setup/index')
->with('step', 1)
->with('start_settings', $start_settings)
->with('section', trans('general.setup_config_check'))
->with('icon', 'fa-regular fa-rectangle-list');
}
/**
* Determine if the .env file accessible via a browser.
*
* @return bool This method will return true when exceptions (such as curl exception) is thrown.
* Check the log files to see more details about the exception.
*/
protected function dotEnvFileIsExposed() : bool
{
try {
return Http::withoutVerifying()->timeout(10)
->accept('*/*')
->get(URL::to('.env'))
->successful();
} catch (\Exception $e) {
Log::debug($e->getMessage());
return true;
}
}
/**
* Determine if the app storage path is writable.
*/
protected function storagePathIsWritable(): bool
{
return File::isWritable(storage_path()) &&
File::isWritable(storage_path('framework')) &&
File::isWritable(storage_path('framework/cache')) &&
File::isWritable(storage_path('framework/sessions')) &&
File::isWritable(storage_path('framework/views')) &&
File::isWritable(storage_path('logs'));
}
/**
* Save the first admin user from Setup.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
*
*/
public function postSaveFirstAdmin(SetupUserRequest $request) : RedirectResponse
{
$user = new User();
$user->first_name = $data['first_name'] = $request->input('first_name');
$user->last_name = $request->input('last_name');
$user->email = $data['email'] = $request->input('email');
$user->activated = 1;
$permissions = ['superuser' => 1];
$user->permissions = json_encode($permissions);
$user->username = $data['username'] = $request->input('username');
$user->password = bcrypt($request->input('password'));
$data['password'] = $request->input('password');
$settings = new Setting();
$settings->full_multiple_companies_support = $request->input('full_multiple_companies_support', 0);
$settings->site_name = $request->input('site_name');
$settings->alert_email = $request->input('email');
$settings->alerts_enabled = 1;
$settings->pwd_secure_min = 10;
$settings->brand = 1;
$settings->link_light_color = $request->input('link_light_color', '#296282');
$settings->link_dark_color = $request->input('link_dark_color', '#296282');
$settings->nav_link_color = $request->input('nav_link_color', '#FFFFFF');
$settings->locale = $request->input('locale', 'en-US');
$settings->default_currency = $request->input('default_currency', 'USD');
$settings->created_by = 1;
$settings->email_domain = $request->input('email_domain');
$settings->email_format = $request->input('email_format');
$settings->next_auto_tag_base = 1;
$settings->auto_increment_assets = $request->input('auto_increment_assets', 0);
$settings->auto_increment_prefix = $request->input('auto_increment_prefix');
$settings->zerofill_count = $request->input('zerofill_count') ?: 0;
if ((! $user->isValid()) || (! $settings->isValid())) {
return redirect()->back()->withInput()->withErrors($user->getErrors())->withErrors($settings->getErrors());
} else {
$user->save();
Auth::login($user, true);
$settings->save();
if ($request->input('email_creds') == '1') {
$data = [];
$data['email'] = $user->email;
$data['username'] = $user->username;
$data['first_name'] = $user->first_name;
$data['last_name'] = $user->last_name;
$data['password'] = $request->input('password');
$user->notify(new FirstAdminNotification($data));
}
return redirect()
->route('setup.done')
->with('section', trans('general.setup_create_admin'))
->with('icon', 'fa-solid fa-champagne-glasses')
->with('success', trans('admin/settings/general.create_admin_success'));
}
}
/**
* Return the admin user creation form in Setup.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0]
*/
public function getSetupUser() : View
{
return view('setup/user')
->with('step', 3)
->with('section', trans('general.setup_create_admin'))
->with('icon', 'fa-solid fa-user-plus');
}
/**
* Return the view that tells the user that the Setup is done.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0]
*/
public function getSetupDone() : View
{
return view('setup/done')
->with('success', trans('general.create_admin_success'))
->with('step', 4)
->with('icon', 'fa-solid fa-champagne-glasses fa-shake')
->with('section', trans('general.setup_done'));
}
/**
* Migrate the database tables, and return the output
* to a view for Setup.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v3.0]
*/
public function setupMigrate()
{
Artisan::call('migrate', ['--force' => true]);
$output = Artisan::output();
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', ['--no-interaction' => true]);
}
return view('setup/migrate')
->with('success', trans('general.create_admin_success'))
->with('output', trim($output))
->with('step', 2)
->with('section', trans('general.setup_create_database'))
->with('icon', 'fa-solid fa-database');
}
}
@@ -67,6 +67,7 @@ class SuppliersController extends Controller
$supplier->phone = request('phone');
$supplier->fax = request('fax');
$supplier->email = request('email');
$supplier->tag_color = $request->input('tag_color');
$supplier->notes = request('notes');
$supplier->url = $supplier->addhttp(request('url'));
$supplier->created_by = auth()->id();
@@ -111,6 +112,7 @@ class SuppliersController extends Controller
$supplier->fax = request('fax');
$supplier->email = request('email');
$supplier->url = $supplier->addhttp(request('url'));
$supplier->tag_color = $request->input('tag_color');
$supplier->notes = request('notes');
$supplier = $request->handleImages($supplier);
@@ -2,9 +2,11 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Import;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
@@ -54,7 +56,7 @@ class UploadedFilesController extends Controller
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile(self::$map_storage_path[$object_type], self::$map_file_prefix[$object_type].'-'.$object->id, $file);
$files[] = $file_name;
$object->logUpload($file_name, $request->get('notes'));
$object->logUpload($file_name, $request->input('notes'));
}
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
@@ -131,7 +133,7 @@ class UploadedFilesController extends Controller
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
$this->authorize('update', self::$map_object_type[$object_type]);
$this->authorize('update', $object);
if (!$object) {
return redirect()->back()->withFragment('files')->with('error',trans('general.file_upload_status.invalid_object'));
@@ -155,7 +157,31 @@ class UploadedFilesController extends Controller
}
// The file doesn't seem to really exist, so report an error
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.delete.error', 1));
return redirect()->back()->withFragment('files')->with('error', trans_choice('general.file_upload_status.delete.error', 1));
}
public function downloadImport(Import $import) {
$this->authorize('import');
if ($import = Import::find($import->id)) {
if ((auth()->user()->id != $import->created_by) && (!auth()->user()->isSuperUser())) {
return redirect()->back()->with('error', trans('general.file_upload_status.file_not_found'));
}
if (config('filesystems.default') == 's3_private') {
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/imports/' . $import->file_path, now()->addMinutes(5)));
}
if (Storage::exists('private_uploads/imports/' . $import->file_path)) {
return response()->download(config('app.private_uploads') . '/imports/' . $import->file_path);
}
}
return redirect()->back()->with('error', trans('general.file_upload_status.file_not_found'));
}
@@ -169,6 +169,7 @@ class BulkUsersController extends Controller
->conditionallyAddItem('remote')
->conditionallyAddItem('ldap_import')
->conditionallyAddItem('activated')
->conditionallyAddItem('display_name')
->conditionallyAddItem('start_date')
->conditionallyAddItem('end_date')
->conditionallyAddItem('city')
@@ -214,6 +215,10 @@ class BulkUsersController extends Controller
$this->update_array['locale'] = null;
}
if ($request->input('null_display_name')=='1') {
$this->update_array['display_name'] = null;
}
if (! $manager_conflict) {
$this->conditionallyAddItem('manager_id');
}
@@ -229,8 +234,11 @@ class BulkUsersController extends Controller
// Only sync groups if groups were selected
if ($request->filled('groups')) {
foreach ($users as $user) {
$user->groups()->sync($request->input('groups'));
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
$user->groups()->sync($request->input('groups'));
}
}
}
@@ -312,7 +320,9 @@ class BulkUsersController extends Controller
foreach ($users as $user) {
$user->accessories()->sync([]);
if ($request->input('delete_user')=='1') {
$user->delete();
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
$user->delete();
}
}
}
+30 -11
View File
@@ -56,7 +56,7 @@ class UsersController extends Controller
public function create(Request $request)
{
$this->authorize('create', User::class);
$groups = Group::pluck('name', 'id');
$groups = Group::orderBy('name', 'asc')->pluck('name', 'id');
$userGroups = collect();
@@ -122,18 +122,31 @@ class UsersController extends Controller
// Strip out the superuser permission if the user isn't a superadmin
$permissions_array = $request->input('permission');
if (! auth()->user()->isSuperUser()) {
unset($permissions_array['superuser']);
// Strip out the individual superuser permission if the API user isn't a superadmin
if (!auth()->user()->isSuperUser()) {
if ((is_array($permissions_array)) && (array_key_exists('superuser', $permissions_array))) {
unset($permissions_array['superuser']);
}
}
// Strip out the individual admin permission if the API user isn't an admin
if (!auth()->user()->isAdmin()) {
if ((is_array($permissions_array)) && (array_key_exists('admin', $permissions_array))) {
unset($permissions_array['admin']);
}
}
$user->permissions = json_encode($permissions_array);
// we have to invoke the form request here to handle image uploads
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
if ($request->get('redirect_option') === 'back'){
if ($request->input('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
}
@@ -151,7 +164,9 @@ class UsersController extends Controller
}
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
$user->groups()->sync($request->input('groups'));
}
} else {
$user->groups()->sync([]);
}
@@ -199,7 +214,7 @@ class UsersController extends Controller
}
$permissions = config('permissions');
$groups = Group::pluck('name', 'id');
$groups = Group::orderBy('name', 'asc')->pluck('name', 'id');
$userGroups = $user->groups()->pluck('name', 'id');
$user->permissions = $user->decodePermissions();
@@ -325,7 +340,7 @@ class UsersController extends Controller
// Handle uploaded avatar
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
session()->put(['redirect_option' => $request->get('redirect_option')]);
session()->put(['redirect_option' => $request->input('redirect_option')]);
if ($user->save()) {
// Redirect to the user page
@@ -351,10 +366,13 @@ class UsersController extends Controller
if ($user = User::find($id)) {
$this->authorize('delete', $user);
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
if ($user->delete()) {
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.delete'));
if ($user->delete()) {
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.delete'));
}
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.cannot_delete'));
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found'));
@@ -505,7 +523,8 @@ class UsersController extends Controller
public function getExportUserCsv()
{
$this->authorize('view', User::class);
\Debugbar::disable();
$this->disableDebugbar();
$response = new StreamedResponse(function () {
// Open output stream
+16 -3
View File
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
use App\Actions\CheckoutRequests\CancelCheckoutRequestAction;
use App\Actions\CheckoutRequests\CreateCheckoutRequestAction;
use App\Enums\ActionType;
use App\Exceptions\AssetNotRequestable;
use App\Models\Actionlog;
use App\Models\Asset;
@@ -155,7 +156,19 @@ class ViewAssetsController extends Controller
public function getRequestableIndex() : View
{
$assets = Asset::with('model', 'defaultLoc', 'location', 'assignedTo', 'requests')->Hardware()->RequestableAssets();
$models = AssetModel::with('category', 'requests', 'assets')->RequestableModels()->get();
$models = AssetModel::with([
'category',
'requests',
'assets' => function ($q) {
$q->where('requestable', 1)
->whereHas('assetstatus', fn ($s) =>
$s->where('archived', 0)
->where(fn ($s) =>
$s->where('deployable', 1)->orWhere('pending', 1)
)
);
},
])->RequestableModels()->get();
return view('account/requestable-assets', compact('assets', 'models'));
}
@@ -193,7 +206,7 @@ class ViewAssetsController extends Controller
if ($fullItemType == Asset::class) {
$data['item_url'] = route('hardware.show', $item->id);
} else {
$data['item_url'] = route("view/${itemType}", $item->id);
$data['item_url'] = route("view/{$itemType}", $item->id);
}
$settings = Setting::getSettings();
@@ -201,7 +214,7 @@ class ViewAssetsController extends Controller
if (($item_request = $item->isRequestedBy($user)) || $cancel_by_admin) {
$item->cancelRequest($requestingUser);
$data['item_quantity'] = ($item_request) ? $item_request->qty : 1;
$logaction->logaction('request_canceled');
$logaction->logaction(ActionType::RequestCanceled);
if (($settings->alert_email != '') && ($settings->alerts_enabled == '1') && (! config('app.lock_passwords'))) {
$settings->notify(new RequestAssetCancelation($data));
+1
View File
@@ -44,6 +44,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\CheckForTwoFactor::class,
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
\App\Http\Middleware\AssetCountForSidebar::class,
\App\Http\Middleware\CheckColorSettings::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
@@ -0,0 +1,75 @@
<?php
namespace App\Http\Middleware;
use App\Models\Setting;
use Closure;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Support\Facades\Auth;
class CheckColorSettings
{
/**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* @param Guard $auth
* @return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// Set defaults in case this is accessed via the /setup screen
$nav_color = '#ffffff';
$link_dark_color = '#89c9ed';
$link_light_color = '#3c8dbc';
if ($settings = Setting::getSettings()) {
$nav_color = $settings->nav_link_color;
$link_dark_color = $settings->link_dark_color;
$link_light_color = $settings->link_light_color;
}
// Override system settings
if ($request->user()) {
if ($request->user()->nav_color) {
$nav_color = $request->user()->nav_color;
}
if ($request->user()->link_dark_color) {
$link_dark_color = $request->user()->link_dark_color;
}
if ($request->user()->nav_color) {
$link_light_color = $request->user()->link_light_color;
}
}
view()->share('nav_link_color', $nav_color);
view()->share('link_dark_color', $link_dark_color);
view()->share('link_light_color', $link_light_color);
return $next($request);
}
}
@@ -0,0 +1,68 @@
<?php
namespace App\Http\Requests;
use App\Http\Requests\Traits\MayContainCustomFields;
use App\Models\Asset;
use Illuminate\Foundation\Http\FormRequest;
use App\Helpers\Helper;
use App\Models\Setting;
use App\Models\AssetModel;
use App\Rules\UniqueUndeleted;
use Illuminate\Support\Str;
class CreateMultipleAssetRequest extends ImageUploadRequest //should I extend from StoreAssetRequest? FIXME OR TODO OR THINKME
{
use MayContainCustomFields;
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true; //TODO - should I do the auth check here?
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
//grab the rules for serials and asset_tags, and tweak them into an array context for multi-create usage
$modelRules = (new Asset)->getRules();
unset($modelRules['serial']);
$asset_tag_rules = $modelRules['asset_tag'];
unset($modelRules['asset_tag']);
// now we replace the 'not_array' rule with 'distinct'
array_splice($asset_tag_rules, array_search('not_array', $asset_tag_rules), 1, 'distinct');
// and replace the 'unique_undeleted' rule with the Rule object
foreach ($asset_tag_rules as $i => $asset_tag_rule) {
if (Str::startsWith($asset_tag_rule, 'unique_undeleted')) {
$asset_tag_rules[$i] = new UniqueUndeleted('assets', 'asset_tag');
}
}
$serials_unique = Setting::getSettings()['unique_serial'];
$serials_required = AssetModel::find($this?->model_id)?->require_serial;
$serial_rules = ['string'];
if ($serials_unique) {
// $serial_rules[] = 'unique_undeleted:assets,serial';
$serial_rules[] = new UniqueUndeleted('assets', 'serial');
$serial_rules[] = 'distinct';
}
if ($serials_required) {
$serial_rules[] = 'required';
} else {
$serial_rules[] = 'nullable';
}
return array_merge($modelRules, [
'serials.*' => $serial_rules,
'asset_tags.*' => $asset_tag_rules,
]);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
use App\Rules\ValidJson;
use Illuminate\Foundation\Http\FormRequest;
class FilterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'filter' => ['nullable', new ValidJson()],
];
}
}

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