Compare commits

...

182 Commits

Author SHA1 Message Date
snipe 9221641bba Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	config/version.php
2024-02-15 14:50:37 +00:00
snipe 6bca1e3b22 Bumped version
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 14:48:07 +00:00
snipe 00cea3eb3c Merge remote-tracking branch 'origin/develop' 2024-02-15 14:45:30 +00:00
snipe 19b52a2f24 Merge pull request #14279 from snipe/fixes/clearer_ui_for_fieldsets
Improved UI for fieldsets
2024-02-15 14:19:34 +00:00
snipe 6237f6192c Added fieldset button
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 14:14:18 +00:00
snipe cc58d4c3ad Nicer form UI
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 14:08:06 +00:00
snipe 060c59bf9d Merge remote-tracking branch 'origin/develop' 2024-02-15 12:30:22 +00:00
snipe 6fd3c494be Merge pull request #14277 from snipe/fixes/change_log_level_to_info
Lower log level to warning on webhook failure
2024-02-15 12:28:57 +00:00
snipe 8adfa8dd83 Change exceptions to warnings
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 12:28:12 +00:00
snipe 6c9001df09 Changed log level in checkoutable listener to info
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 12:20:14 +00:00
snipe d6159d8cb5 Merge remote-tracking branch 'origin/develop' 2024-02-15 09:59:23 +00:00
snipe 81b9753b79 Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/dist/all-defer.js
#	public/mix-manifest.json
2024-02-15 09:58:09 +00:00
snipe d8b1eec91b Merge pull request #14197 from inietov/fixes/component_licenses_checkouts_not_displayed_in_asset_history
Fixes #14010 Adds Components and Licenses logs to Assets history view
2024-02-15 09:57:46 +00:00
snipe b871813cfd Merge pull request #14172 from spencerrlongg/bug/sc-23514
Resolve Duplicate Activity Logs for Imports
2024-02-15 09:54:43 +00:00
snipe 6f7dce53cc Merge pull request #14276 from snipe/bug/sc-24828
Fixed ambiguous query on supplier_id in maintenances
2024-02-15 09:52:23 +00:00
snipe 13d2af2155 Fixed ambiguous query on supplier_id in maintenances
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 09:51:14 +00:00
snipe bf3794822c Updated assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 09:47:45 +00:00
snipe 1fa8fba5fe Merge pull request #14268 from Godmartinz/checked-out-notif-fix
Fixed the notification listener
2024-02-15 08:29:15 +00:00
snipe 3d7697da6f Merge pull request #14275 from snipe/snyk/14270
Upgrade webpack from 5.89.0 to 5.90.0
2024-02-15 08:28:26 +00:00
snipe 42cf17d3bf Upgrade webpack from 5.89.0 to 5.90.0
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 08:27:52 +00:00
snipe 783e37f06d Merge pull request #14274 from snipe/snyk/14269
Upgrade alpinejs from 3.13.3 to 3.13.5
2024-02-15 08:21:58 +00:00
snipe 9a4cda0bf6 Upgrade alpinejs from 3.13.3 to 3.13.5
Signed-off-by: snipe <snipe@snipe.net>
2024-02-15 08:21:19 +00:00
snipe b6279af1d8 Merge pull request #14272 from marcusmoore/testing/update-state-helper-name
Updated testing helper name
2024-02-15 08:19:40 +00:00
Marcus Moore bc0a7542ac Change enableWebhook to enableSlackWebhook 2024-02-14 18:17:34 -08:00
Godfrey M b136e9e29d fix the listener to fire when checking in 2024-02-14 09:16:20 -08:00
snipe 50c910461a Merge remote-tracking branch 'origin/develop' 2024-02-14 11:36:28 +00:00
snipe 88a84e9350 Merge pull request #14264 from snipe/feature/sc-24816
Added serial and status label to asset maintenances page and API
2024-02-14 10:37:01 +00:00
snipe 43bb8ae0a8 Added string
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 10:14:44 +00:00
snipe 638071dadd Check the asset is not deleted when creating the permissions array
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 10:14:38 +00:00
snipe 8a4c90ade8 Check that the asset is not deleted
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 10:14:25 +00:00
snipe 1dfa1da0ee Disable the edit button if permission is not allowed
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 10:14:10 +00:00
snipe 71ebade641 Added serial and status to searchable relations
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:59:10 +00:00
snipe b86b05c4fc Added serial and status fields to presenter
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:58:55 +00:00
snipe 35a70988cb Added sorting by status label
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:58:39 +00:00
snipe 56ba26eb45 Disallow editing of deleted assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:58:07 +00:00
snipe adb8be9345 Added bootstrap formatting to show deleted assets more clearly
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:57:57 +00:00
snipe abc9ee22c6 Added serial and status to transformer
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:48:02 +00:00
snipe e5ee9760c0 I don’t think we use these anyore because of the modifiers on date
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:47:33 +00:00
snipe 7868a8c174 Added serial for API controller
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:47:13 +00:00
snipe 03df4cec45 Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2024-02-14 09:46:43 +00:00
snipe e2c3386c33 Merge remote-tracking branch 'origin/develop' 2024-02-13 20:37:17 +00:00
snipe a866d6b16e Merge pull request #14261 from snipe/fixed/missing_location_string
Fixed incorrect string for location not existing
2024-02-13 20:29:32 +00:00
snipe a76a69d085 Fixed incorrect string for location not existing
Signed-off-by: snipe <snipe@snipe.net>
2024-02-13 20:23:32 +00:00
snipe 280e778749 Merge remote-tracking branch 'origin/develop' 2024-02-13 13:46:52 +00:00
snipe 60ba898167 Merge pull request #14255 from marcusmoore/chore/sc-24805
Added LDAP group tag to LDAP tests
2024-02-13 13:42:51 +00:00
snipe 2c9d5b9ea3 Merge pull request #14247 from ubc-cpsc/bugfix/CVE-2023-37260
Fixes CVE-2023-37260 upgrading league/oauth2-server
2024-02-13 13:40:53 +00:00
snipe eb6e2636b5 Merge pull request #14246 from ubc-cpsc/bugfix/CVE-2022-24894
Fixes CVE-2022-24894 by upgrading symfony/http-kernel
2024-02-13 13:40:33 +00:00
snipe 4c1964d509 Merge pull request #14245 from ubc-cpsc/bugfix/CVE-2024-24821
Fixes CVE-2024-24821 by upgrading composer/composer
2024-02-13 13:39:16 +00:00
snipe 7547277352 Merge pull request #14236 from snipe/jerm/upgrade-script-enhancements
Change how we check forward-looking upgrade requirements
2024-02-13 13:38:35 +00:00
snipe 3e00bc49fd Merge pull request #14250 from mauro-miatello/develop
Cleaned up navbar-custom-menu
2024-02-13 13:33:03 +00:00
snipe 99e0b65de7 Merge pull request #14256 from marcusmoore/bug/sc-24790
Fixed accessory check in emails being sent when setting disabled
2024-02-13 13:32:42 +00:00
Jeremy Price f4c1460c2b remove help text options until i put together the help text 2024-02-12 19:18:26 -08:00
Jeremy Price bb2e1de0a8 Change how we check forward-looking upgrade requirements
In https://github.com/snipe/snipe-it/pull/14128 we added the capability
for the upgrade.php script to check version requirements _before_
downloading the new source, to help keep from breaking installations.

Turns out, `file_get_contents()` isn't a reliable way to grab a url, because
some systems have `allow_url_fopen` turned off in their PHP
configurations.

In this iteration, we swap that out for a curl function, while also
adding more error handling, the ability to entirely skip the
PHP version checks if for some reason you Just Can't query the upgrade
json correctly, as well as adding a lot of helpful text around the whole
issue.

Additionally, I've added some error checking around DB backups and
initial artisan down-ing, since shell_exec would happily march right
past any errors.
2024-02-12 19:18:26 -08:00
Marcus Moore 7154d23759 Pass the correct variable to the route helper 2024-02-12 16:45:18 -08:00
Marcus Moore df23fd0dee Remove usused import 2024-02-12 16:35:54 -08:00
Marcus Moore adfb8895df Improve factory state name 2024-02-12 16:31:32 -08:00
Marcus Moore c8e12ddb5c Remove bug in factory state 2024-02-12 16:30:09 -08:00
Marcus Moore 5b181ecea7 Remove old comment 2024-02-12 16:29:34 -08:00
Marcus Moore 728aaaab20 Ensure accessory check in emails are not sent when the setting is disabled 2024-02-12 16:22:59 -08:00
Marcus Moore 095a7d9b34 Scaffold tests around accessory check in 2024-02-12 12:54:48 -08:00
Marcus Moore cf53f2778f Add LDAP test cases to group 2024-02-12 12:28:27 -08:00
snipe 65e20282b6 Merge pull request #14251 from snipe/dependabot/github_actions/develop/codacy/codacy-analysis-cli-action-4.4.0
Bump codacy/codacy-analysis-cli-action from 4.3.0 to 4.4.0
2024-02-12 08:35:30 +00:00
dependabot[bot] 405c5b5ad0 Bump codacy/codacy-analysis-cli-action from 4.3.0 to 4.4.0
Bumps [codacy/codacy-analysis-cli-action](https://github.com/codacy/codacy-analysis-cli-action) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/codacy/codacy-analysis-cli-action/releases)
- [Commits](https://github.com/codacy/codacy-analysis-cli-action/compare/v4.3.0...v4.4.0)

---
updated-dependencies:
- dependency-name: codacy/codacy-analysis-cli-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 08:33:17 +00:00
MrM 6f0fe16b87 Update default.blade.php
removed some repeated attributes
2024-02-11 18:45:37 +01:00
snipe 111daffc17 Merge pull request #14188 from spencerrlongg/bug/14146
Fixes Default Location Being Set During Asset Creation and Checkout
2024-02-10 11:47:46 +00:00
Joël Pittet b8a478f558 Fixes by CVE-2023-37260 upgrading league/oauth2-server 2024-02-09 17:24:07 -08:00
Joël Pittet 9f7084d077 Revert "Fixes by CVE-2022-24894 upgrading league/oauth2-server"
This reverts commit 0840cd3df3.
2024-02-09 17:22:36 -08:00
Joël Pittet 0840cd3df3 Fixes by CVE-2022-24894 upgrading league/oauth2-server 2024-02-09 17:21:24 -08:00
Joël Pittet cefdaf9a9b Fixes CVE-2022-24894 2024-02-09 17:17:44 -08:00
Joël Pittet 13335b19e9 Fixes CVE-2024-24821 2024-02-09 17:04:34 -08:00
snipe 6e471a27e7 Merge remote-tracking branch 'origin/develop' 2024-02-09 21:10:27 +00:00
snipe 513ea67e7d Merge pull request #14244 from snipe/fixes/null_barcode_if_hard_deleted
Return null if asset was hard-deleted/purged
2024-02-09 21:09:17 +00:00
snipe 3868e711f4 Return null if asset was hard-deleted/purged
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 21:08:07 +00:00
snipe f33f712de7 Merge remote-tracking branch 'origin/develop' 2024-02-09 21:00:16 +00:00
snipe c12e1f6d6c Merge pull request #14243 from snipe/fixes/reports_controller_when_item_is_deleted
Fixed ReportsController to not try to return a serial if the item doesn’t exist
2024-02-09 20:54:37 +00:00
snipe 479abd5231 Do not try to return a serial if the item doesn’t exist
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 20:53:33 +00:00
snipe a60a24a4a8 Merge remote-tracking branch 'origin/develop' 2024-02-09 20:42:48 +00:00
snipe 55b3050ca8 Re-applied previous withTrashed PR
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 20:37:18 +00:00
snipe 2c996a8508 Merge pull request #14241 from snipe/revert-14240-feature/sc-24786
Revert "Fixed barcodes crashing if asset was deleted"
2024-02-09 20:36:02 +00:00
snipe 84f8eee869 Revert "Fixed barcodes crashing if asset was deleted" 2024-02-09 20:35:45 +00:00
snipe 590c19dbd7 Merge pull request #14240 from snipe/feature/sc-24786
Feature/sc 24786
2024-02-09 20:28:31 +00:00
snipe fa47707974 Use withTrashed() to get the barcode on deleted assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 20:26:49 +00:00
snipe ca62481083 Added button and route
Signed-off-by: snipe <snipe@snipe.net>
2024-02-09 20:23:13 +00:00
snipe 1c3306046c Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/js/build/app.js
#	public/js/dist/all.js
#	public/mix-manifest.json
2024-02-08 23:14:54 +00:00
snipe f4fc845375 Updated dev assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-08 23:06:47 +00:00
snipe a7af987322 Merge pull request #14229 from Godmartinz/select2-target-fix
Fixed select inputs un-select2-ifying on mobile
2024-02-08 23:04:55 +00:00
Godfrey M c4eaae923a removed vue comments since they do not apply anymore 2024-02-08 15:02:47 -08:00
snipe 849ba02516 Merge pull request #14187 from Godmartinz/general-hook_fix
Fixed the general webhook not notifying anymore
2024-02-08 23:01:36 +00:00
Godfrey Martinez 9dcd14a712 Merge branch 'develop' into general-hook_fix 2024-02-08 14:59:25 -08:00
snipe 3412b4dc5a Merge remote-tracking branch 'origin/develop' 2024-02-08 14:58:24 +00:00
snipe a3b96aff1f Merge pull request #14233 from uberbrady/prevent_svg_injection_with_fake_extensions_rebased
Fixes file upload XSS vulnerability [sc-24156]
2024-02-08 14:56:59 +00:00
Brady Wetherington 9bb191f29f Fixes file upload XSS vulnerability [sc-24156] 2024-02-08 14:30:40 +00:00
snipe 4a43ccfa92 Merge pull request #14228 from akemidx/bug/sc-23516
Fixed: 404 Error on Importer When Uploading a .csv Under Certain Circumstance
2024-02-08 13:43:07 +00:00
Godfrey M b73e8642d3 removed unnecessary changes 2024-02-06 13:06:21 -08:00
Godfrey M cfe2277a64 forgot to remove comment line 2024-02-06 13:05:04 -08:00
akemidx e776c2cffa formatting the button tag 2024-02-06 15:51:04 -05:00
Godfrey M ca59bc3c9c removes if statement that prevents select2-ifying inputs 2024-02-06 12:46:17 -08:00
akemidx 575362f4dc adding in line to null out the active file on click of button 2024-02-06 15:38:07 -05:00
snipe f0139c5e41 Merge remote-tracking branch 'origin/develop' 2024-02-06 18:25:19 +00:00
snipe 5f8ac66036 Merge pull request #14223 from Godmartinz/user-bulk-edit
Fixed Select2 functionality in User bulk check-in Delete User
2024-02-06 18:22:46 +00:00
snipe a49e66c689 Merge remote-tracking branch 'origin/develop' 2024-02-06 16:42:16 +00:00
snipe 1630e4bc2f Merge pull request #14227 from snipe/fixes/bulk_update_by_audit_interval
Switch to bulk updating to handle audit interval updates
2024-02-06 16:06:08 +00:00
snipe bf674a0f4d Removed backticks
Signed-off-by: snipe <snipe@snipe.net>
2024-02-06 15:58:36 +00:00
snipe b170755c3d Switch to bulk updating to handle audit interval updates
Signed-off-by: snipe <snipe@snipe.net>
2024-02-06 15:52:46 +00:00
snipe b25612bbac Merge remote-tracking branch 'origin/develop' 2024-02-06 09:09:39 +00:00
snipe a43183ff96 Increased chunk
Signed-off-by: snipe <snipe@snipe.net>
2024-02-06 09:09:27 +00:00
Godfrey M 5aa34695a1 z 2024-02-05 16:41:29 -08:00
snipe 3b36372a66 Merge pull request #14222 from marcusmoore/tests/company-get-id-for-current-user
Added tests around getIdForCurrentUser method
2024-02-05 22:04:08 +00:00
snipe e763693829 Merge remote-tracking branch 'origin/develop' 2024-02-05 20:51:37 +00:00
snipe e08d60ed18 Merge pull request #14221 from snipe/bug/sc-24749
Chunk data to reduce memory on large datasets when updating `next_audit_date`
2024-02-05 20:50:41 +00:00
Marcus Moore 9e6e2de71e Add docblock 2024-02-05 12:49:29 -08:00
snipe b49935701b Chunk data to reduce memory on large datasets when updating next_audit_date
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 20:48:24 +00:00
Marcus Moore 76cc5995d9 Write tests around getIdForCurrentUser method [sc-24748] 2024-02-05 12:38:33 -08:00
snipe c0fbf106ed Merge remote-tracking branch 'origin/develop' 2024-02-05 19:13:17 +00:00
snipe 7b4020c5e9 Updated string paths for URL
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 19:09:16 +00:00
snipe 383e3829e5 Merge remote-tracking branch 'origin/develop' 2024-02-05 19:04:40 +00:00
snipe 91356af838 Merge pull request #14220 from snipe/bug/sc-24740
Removed loading of assets for label count
2024-02-05 19:02:10 +00:00
snipe 4c967a43a7 Removed loading of assets for label count
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 18:56:51 +00:00
snipe deca842eb0 Merge remote-tracking branch 'origin/develop' 2024-02-05 18:35:56 +00:00
snipe 44366746dd Merge pull request #14219 from snipe/fixes/resurface_load_remote
Fixed #14185 - Resurfaced `load_remote` in admin
2024-02-05 18:33:11 +00:00
snipe 3806cec10e Updated language
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 18:28:05 +00:00
snipe a800fa07f9 Removed duplicate string
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 18:27:58 +00:00
snipe 32c360f032 Re-added UI for load_remote
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 18:23:52 +00:00
snipe 25f1445afc Merge branch 'master' of https://github.com/snipe/snipe-it 2024-02-05 17:26:54 +00:00
snipe 0042359e5f Prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 17:23:30 +00:00
snipe cf2e2bfbc1 Updated prod assets
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 17:23:21 +00:00
snipe 60f9a8be9a Merge remote-tracking branch 'origin/develop'
Signed-off-by: snipe <snipe@snipe.net>

# Conflicts:
#	public/css/dist/all.css
#	public/css/dist/bootstrap-table.css
#	public/js/dist/bootstrap-table.js
#	public/mix-manifest.json
2024-02-05 17:22:58 +00:00
snipe 8316db9702 Merge pull request #13805 from gitgrimbo/more_line_based_markdown
Use parseEscapedMarkedownInline for more views
2024-02-05 17:19:41 +00:00
snipe 1a480435de Merge pull request #14190 from marcusmoore/bug/sc-24685
Fixed consumable checkout via API not sending notification
2024-02-05 17:16:57 +00:00
snipe ebc373744a Merge pull request #14218 from snipe/security/alpine_3_18_6
Security upgrade alpine from 3.18.5 to 3.18.6 #14178
2024-02-05 17:16:18 +00:00
snipe 650aa25659 Merge pull request #14191 from Godmartinz/googlechat_webhook
Added support for Google Chat notifications
2024-02-05 17:10:54 +00:00
snipe 212dd06948 Security upgrade alpine from 3.18.5 to 3.18.6 #14178
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 17:07:31 +00:00
snipe b6eea2e170 Merge pull request #14217 from snipe/security/snyk_bs_table_14199
Upgrade bootstrap-table from 1.22.1 to 1.22.2 #14199
2024-02-05 16:59:51 +00:00
snipe 688017bd8a Upgrade bootstrap-table from 1.22.1 to 1.22.2
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 16:59:01 +00:00
snipe 58a9c27342 Merge remote-tracking branch 'origin/develop' 2024-02-05 16:31:32 +00:00
snipe f52b00256d Merge pull request #14213 from snipe/fixes/orientate_on_exif
Fixed FD-40296 - mobile uploads sometimes uploading with incorrect orientation
2024-02-05 16:22:03 +00:00
snipe 2d48d96b3c Merge remote-tracking branch 'origin/develop' 2024-02-05 16:10:54 +00:00
snipe abd13a1140 Merge pull request #14216 from snipe/bug/sc-24727
Fixed company asset counts for dashboard widget
2024-02-05 16:07:40 +00:00
snipe 5882d71f9b Fix company asset counts for dashboard widget
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 15:55:53 +00:00
snipe dc92bbf61e Merge pull request #14215 from snipe/bug/sc-24726
Removed initial check for assets, licenses, etc
2024-02-05 15:54:45 +00:00
snipe e6fdeb0e8a Removed initial check for assets, licenses, etc
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 15:30:18 +00:00
snipe 3b948c7b7e Add orientate to image upload
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 14:29:43 +00:00
snipe d965fe759a Fixed #14211 - duplicate array key ldap_emp_num
Signed-off-by: snipe <snipe@snipe.net>
2024-02-05 12:28:24 +00:00
Ivan Nieto Vivanco 5e4a3379a9 Adds Components and Licenses logs to Assets history view 2024-02-01 01:35:25 -06:00
Godfrey M 9dc428b720 ran composer update on the package since the version was explicitized 2024-01-31 17:30:56 -08:00
Godfrey M 8a5b469ff8 adds # to webhook channel trigger 2024-01-31 12:54:30 -08:00
Godfrey M ed06f32a7a adds # to webhook channel trigger 2024-01-31 12:53:56 -08:00
Godfrey M a876703f8f spacing 2024-01-31 10:48:48 -08:00
Godfrey M 01bb8d8c9a spacing 2024-01-31 10:48:23 -08:00
Godfrey M f452204d62 applies a conditional to have the the integration test button function properly 2024-01-31 10:47:25 -08:00
Godfrey M 0eabb147b2 adds google notif to consumable check out 2024-01-30 13:19:28 -08:00
Marcus Moore 2e0e39ccc8 Ensure notification is sent when consumable is checked out via api 2024-01-30 13:19:20 -08:00
spencerrlongg 63e733f0d6 changes from a note to a source 2024-01-30 15:14:59 -06:00
Godfrey M 2406d2cfdb adds google notifs to license seats check in and out 2024-01-30 13:14:24 -08:00
Godfrey M e074ca0bf9 adds google notifs to accessories check in and out 2024-01-30 12:57:45 -08:00
Marcus Moore 13c37e708f Implement tests around consumable checkout 2024-01-30 12:43:20 -08:00
Marcus Moore 3f76d65b95 Improve test method name 2024-01-30 12:43:07 -08:00
Godfrey M 87bce0c097 adds google notifs for asset check in and out 2024-01-30 12:38:17 -08:00
Marcus Moore 6d41c8cf67 Merge branch 'develop' into bug/sc-24685 2024-01-30 11:44:36 -08:00
Godfrey M 22385a8e35 fix for general webhook not notifiying anymore 2024-01-30 10:47:18 -08:00
snipe 77fa213ccd Merge pull request #14181 from marcusmoore/bug/sc-24671
Fixed accessory checkout via API not sending notification and not adhering to qty limit
2024-01-30 14:24:42 +00:00
spencerrlongg d0a82adc3f changed condition 2024-01-29 20:44:26 -06:00
Marcus Moore de2aa903c5 Scaffold tests 2024-01-29 17:56:55 -08:00
Marcus Moore 42ec2548c9 Fire event when accessory checked out via API
Brings behavior in line with GUI controller
2024-01-29 17:03:19 -08:00
Marcus Moore a2cba67f4e Improve assertion 2024-01-29 16:59:57 -08:00
Marcus Moore 7d45cfff2c Ensure accessory available when checking out via api 2024-01-29 16:49:09 -08:00
Marcus Moore f1ab8253f0 Implement tests included two currently failing tests 2024-01-29 16:48:40 -08:00
Marcus Moore e5d3df7d24 Scaffold accessory checkout tests for api 2024-01-29 15:59:23 -08:00
Marcus Moore 987676df08 Implement additional tests 2024-01-29 15:56:18 -08:00
Marcus Moore f16f62f76c Scaffold and implement some tests around accessory checkout 2024-01-29 14:21:30 -08:00
Godfrey M dfa33f651a webhook test works 2024-01-29 12:58:09 -08:00
Godfrey M 30c2927987 merged develope 2024-01-29 11:21:00 -08:00
Godfrey M e8159d97fa changes 2024-01-29 11:12:25 -08:00
spencerrlongg 2deba17d91 that's all of 'em 2024-01-25 20:04:02 -06:00
spencerrlongg 3574ef5bb9 a few more imports, component sample 2024-01-25 19:54:53 -06:00
spencerrlongg abf13f1619 revert spacing 2024-01-25 19:37:59 -06:00
spencerrlongg 4a7df470f0 this works 2024-01-25 19:34:41 -06:00
spencerrlongg c9101f4d97 initial work, not working yet 2024-01-25 18:18:24 -06:00
Godfrey M 6e9a46e582 working on Chat integration test 2024-01-24 15:50:36 -08:00
Godfrey M 1d3124f89f adding a test variable for test methods 2024-01-24 14:38:45 -08:00
Godfrey M ada1a593a4 add google placeholder 2024-01-24 11:29:32 -08:00
gitgrimbo d556f0d275 Use parseEscapedMarkedownInline for more views 2023-10-27 15:37:56 +01:00
103 changed files with 1710 additions and 632 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
uses: codacy/codacy-analysis-cli-action@v4.3.0
uses: codacy/codacy-analysis-cli-action@v4.4.0
with:
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
# You can also omit the token and run the tools that support default configurations
+1 -1
View File
@@ -1,4 +1,4 @@
FROM alpine:3.18.5
FROM alpine:3.18.6
# Apache + PHP
RUN apk add --no-cache \
apache2 \
+15 -2
View File
@@ -45,8 +45,21 @@ DB_PASSWORD={}
Now you are ready to run the entire test suite from your terminal:
`php artisan test`
```shell
php artisan test
````
To run individual test files, you can pass the path to the test that you want to run:
`php artisan test tests/Unit/AccessoryTest.php`
```shell
php artisan test tests/Unit/AccessoryTest.php
```
Some tests, like ones concerning LDAP, are marked with the `@group` annotation. Those groups can be run, or excluded, using the `--group` or `--exclude-group` flags:
```shell
php artisan test --group=ldap
php artisan test --exclude-group=ldap
```
This can be helpful if a set of tests are failing because you don't have an extension, like LDAP, installed.
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Accessories;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Accessory;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Accessory\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class AccessoriesFilesController extends Controller
{
/**
* Validates and stores files associated with a accessory.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(AssetFileRequest $request, $accessoryId = null)
public function store(UploadFileRequest $request, $accessoryId = null)
{
if (config('app.lock_passwords')) {
@@ -45,30 +44,7 @@ class AccessoriesFilesController extends Controller
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'accessory-'.$accessory->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/accessories/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/accessories/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/accessories/', 'accessory-'.$accessory->id, $file);
//Log the upload to the log
$accessory->logUpload($file_name, e($request->input('notes')));
}
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\AccessoriesTransformer;
@@ -278,7 +279,7 @@ class AccessoriesController extends Controller
public function checkout(Request $request, $accessoryId)
{
// Check if the accessory exists
if (is_null($accessory = Accessory::find($accessoryId))) {
if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist')));
}
@@ -302,7 +303,7 @@ class AccessoriesController extends Controller
'note' => $request->get('note'),
]);
$accessory->logCheckout($request->input('note'), $user);
event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success')));
}
@@ -36,7 +36,8 @@ class AssetMaintenancesController extends Controller
{
$this->authorize('view', Asset::class);
$maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'admin');
$maintenances = AssetMaintenance::select('asset_maintenances.*')
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin');
if ($request->filled('search')) {
$maintenances = $maintenances->TextSearch($request->input('search'));
@@ -47,7 +48,7 @@ class AssetMaintenancesController extends Controller
}
if ($request->filled('supplier_id')) {
$maintenances->where('supplier_id', '=', $request->input('supplier_id'));
$maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id'));
}
if ($request->filled('asset_maintenance_type')) {
@@ -70,10 +71,13 @@ class AssetMaintenancesController extends Controller
'notes',
'asset_tag',
'asset_name',
'serial',
'user_id',
'supplier',
'is_warranty',
'status_label',
];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
@@ -90,6 +94,12 @@ class AssetMaintenancesController extends Controller
case 'asset_name':
$maintenances = $maintenances->OrderByAssetName($order);
break;
case 'serial':
$maintenances = $maintenances->OrderByAssetSerial($order);
break;
case 'status_label':
$maintenances = $maintenances->OrderStatusName($order);
break;
default:
$maintenances = $maintenances->orderBy($sort, $order);
break;
@@ -40,7 +40,9 @@ class CompaniesController extends Controller
'components_count',
];
$companies = Company::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
$companies = Company::withCount(['assets as assets_count' => function ($query) {
$query->AssetsForShow();
}])->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
if ($request->filled('search')) {
$companies->TextSearch($request->input('search'));
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ConsumablesTransformer;
@@ -11,6 +12,7 @@ use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Auth;
class ConsumablesController extends Controller
{
@@ -290,17 +292,9 @@ class ConsumablesController extends Controller
]
);
// Log checkout event
$logaction = $consumable->logCheckout($request->input('note'), $user);
$data['log_id'] = $logaction->id;
$data['eula'] = $consumable->getEula();
$data['first_name'] = $user->first_name;
$data['item_name'] = $consumable->name;
$data['checkout_date'] = $logaction->created_at;
$data['note'] = $logaction->note;
$data['require_acceptance'] = $consumable->requireAcceptance();
event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
}
@@ -33,7 +33,12 @@ class ReportsController extends Controller
if (($request->filled('item_type')) && ($request->filled('item_id'))) {
$actionlogs = $actionlogs->where('item_id', '=', $request->input('item_id'))
->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
->where('item_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')))
->orWhere(function($query) use ($request)
{
$query->where('target_id', '=', $request->input('item_id'))
->where('target_type', '=', 'App\\Models\\'.ucwords($request->input('item_type')));
});
}
if ($request->filled('action_type')) {
@@ -148,30 +148,20 @@ class AssetMaintenancesController extends Controller
*/
public function edit($assetMaintenanceId = null)
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the improvement management page
return redirect()->route('maintenances.index')
->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif (! $assetMaintenance->asset) {
return redirect()->route('maintenances.index')
->with('error', 'The asset associated with this maintenance does not exist.');
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
if ($assetMaintenance->completion_date == '0000-00-00') {
$assetMaintenance->completion_date = null;
}
if ($assetMaintenance->start_date == '0000-00-00') {
$assetMaintenance->start_date = null;
}
if ($assetMaintenance->cost == '0.00') {
$assetMaintenance->cost = null;
}
// Prepare Improvement Type List
$assetMaintenanceType = [
@@ -203,8 +193,10 @@ class AssetMaintenancesController extends Controller
// Check if the asset maintenance exists
if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')
->with('error', trans('admin/asset_maintenances/message.not_found'));
return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found'));
} elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) {
// Redirect to the asset maintenance management page
return redirect()->route('maintenances.index')->with('error', 'asset does not exist');
} elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) {
return static::getInsufficientPermissionsRedirect();
}
@@ -3,26 +3,25 @@
namespace App\Http\Controllers;
use App\Helpers\StorageHelper;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\AssetModel;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use enshrined\svgSanitize\Sanitizer;
class AssetModelsFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $modelId
* @return Redirect
* @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(AssetFileRequest $request, $modelId = null)
public function store(UploadFileRequest $request, $modelId = null)
{
if (! $model = AssetModel::find($modelId)) {
return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -37,27 +36,7 @@ class AssetModelsFilesController extends Controller
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'model-'.$model->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension=='svg') {
\Log::debug('This is an SVG');
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/assetmodels/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/assetmodels/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$model->id,$file);
$model->logUpload($file_name, e($request->get('notes')));
}
@@ -4,26 +4,25 @@ namespace App\Http\Controllers\Assets;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use enshrined\svgSanitize\Sanitizer;
class AssetFilesController extends Controller
{
/**
* Upload a file to the server.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $assetId
* @return Redirect
* @since [v1.0]
* @throws \Illuminate\Auth\Access\AuthorizationException
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function store(AssetFileRequest $request, $assetId = null)
public function store(UploadFileRequest $request, $assetId = null)
{
if (! $asset = Asset::find($assetId)) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
@@ -37,28 +36,7 @@ class AssetFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension=='svg') {
\Log::debug('This is an SVG');
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/assets/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/assets/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, e($request->get('notes')));
}
@@ -146,7 +146,8 @@ class AssetsController extends Controller
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
}
if ($asset->assigned_to == '') {
// 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);
}
@@ -521,31 +522,33 @@ class AssetsController extends Controller
public function getBarCode($assetId = null)
{
$settings = Setting::getSettings();
$asset = Asset::find($assetId);
$barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
if ($asset = Asset::withTrashed()->find($assetId)) {
$barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png';
if (isset($asset->id, $asset->asset_tag)) {
if (file_exists($barcode_file)) {
$header = ['Content-type' => 'image/png'];
if (isset($asset->id, $asset->asset_tag)) {
if (file_exists($barcode_file)) {
$header = ['Content-type' => 'image/png'];
return response()->file($barcode_file, $header);
} else {
// Calculate barcode width in pixel based on label width (inch)
$barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001;
return response()->file($barcode_file, $header);
} else {
// Calculate barcode width in pixel based on label width (inch)
$barcode_width = ($settings->labels_width - $settings->labels_display_sgutter) * 200.000000000001;
$barcode = new \Com\Tecnick\Barcode\Barcode();
try {
$barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
file_put_contents($barcode_file, $barcode_obj->getPngData());
$barcode = new \Com\Tecnick\Barcode\Barcode();
try {
$barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50);
file_put_contents($barcode_file, $barcode_obj->getPngData());
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
} catch (\Exception $e) {
Log::debug('The barcode format is invalid.');
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
} catch (\Exception $e) {
Log::debug('The barcode format is invalid.');
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
}
}
}
}
return null;
}
/**
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Components;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Component;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class ComponentsFilesController extends Controller
{
/**
* Validates and stores files associated with a component.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(AssetFileRequest $request, $componentId = null)
public function store(UploadFileRequest $request, $componentId = null)
{
if (config('app.lock_passwords')) {
@@ -43,30 +42,7 @@ class ComponentsFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'component-'.$component->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/components/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/components/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/components/','component-'.$component->id, $file);
//Log the upload to the log
$component->logUpload($file_name, e($request->input('notes')));
@@ -76,7 +76,6 @@ class ConsumableCheckoutController extends Controller
return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable'));
}
$admin_user = Auth::user();
$assigned_to = e($request->input('assigned_to'));
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Consumables;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\Consumable;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Consumable\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class ConsumablesFilesController extends Controller
{
/**
* Validates and stores files associated with a consumable.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(AssetFileRequest $request, $consumableId = null)
public function store(UploadFileRequest $request, $consumableId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled'));
@@ -42,30 +41,7 @@ class ConsumablesFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'consumable-'.$consumable->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/consumables/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/consumables/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/consumables/','consumable-'.$consumable->id, $file);
//Log the upload to the log
$consumable->logUpload($file_name, e($request->input('notes')));
@@ -4,28 +4,27 @@ namespace App\Http\Controllers\Licenses;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\License;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class LicenseFilesController extends Controller
{
/**
* Validates and stores files associated with a license.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $licenseId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @todo Switch to using the AssetFileRequest form request validator.
*/
public function store(AssetFileRequest $request, $licenseId = null)
public function store(UploadFileRequest $request, $licenseId = null)
{
$license = License::find($licenseId);
@@ -38,30 +37,7 @@ class LicenseFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/licenses/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/licenses/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/licenses/','license-'.$license->id, $file);
//Log the upload to the log
$license->logUpload($file_name, e($request->input('notes')));
+1 -1
View File
@@ -168,7 +168,7 @@ class LocationsController extends Controller
{
$this->authorize('delete', Location::class);
if (is_null($location = Location::find($locationId))) {
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.not_found'));
return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist'));
}
if ($location->users()->count() > 0) {
+2 -2
View File
@@ -295,7 +295,7 @@ class ReportsController extends Controller
$actionlog->present()->actionType(),
e($actionlog->itemType()),
($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name,
($actionlog->item->serial) ? $actionlog->item->serial : null,
($actionlog->item) ? $actionlog->item->serial : null,
($actionlog->item->model) ? htmlspecialchars($actionlog->item->model->name, ENT_NOQUOTES) : null,
($actionlog->item->model) ? $actionlog->item->model->model_number : null,
$target_name,
@@ -616,7 +616,7 @@ class ReportsController extends Controller
}
if ($request->filled('url')) {
$header[] = trans('admin/manufacturers/table.url');
$header[] = trans('general.url');
}
+15 -13
View File
@@ -28,6 +28,7 @@ use App\Http\Requests\SlackSettingsRequest;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Validator;
use Carbon\Carbon;
/**
* This controller handles all actions related to Settings for
@@ -356,6 +357,7 @@ class SettingsController extends Controller
}
$setting->default_eula_text = $request->input('default_eula_text');
$setting->load_remote = $request->input('load_remote', 0);
$setting->thumbnail_max_h = $request->input('thumbnail_max_h');
$setting->privacy_policy_link = $request->input('privacy_policy_link');
@@ -635,21 +637,21 @@ class SettingsController extends Controller
// 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) {
// Be careful - this could be a negative number
// 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');
// Grab all of the assets that have an existing next_audit_date
$assets = Asset::whereNotNull('next_audit_date')->get();
// Update all of the assets' next_audit_date values
foreach ($assets as $asset) {
if ($asset->next_audit_date != '') {
$old_next_audit = new \DateTime($asset->next_audit_date);
$asset->next_audit_date = $old_next_audit->modify($audit_diff_months.' month')->format('Y-m-d');
$asset->forceSave();
}
}
}
$alert_email = rtrim($request->input('alert_email'), ',');
@@ -4,14 +4,13 @@ namespace App\Http\Controllers\Users;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
class UserFilesController extends Controller
@@ -19,14 +18,14 @@ class UserFilesController extends Controller
/**
* Return JSON response with a list of user details for the getIndex() view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
* @param AssetFileRequest $request
* @param UploadFileRequest $request
* @param int $userId
* @return string JSON
* @throws \Illuminate\Auth\Access\AuthorizationException
*@author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.6]
*/
public function store(AssetFileRequest $request, $userId = null)
public function store(UploadFileRequest $request, $userId = null)
{
$user = User::find($userId);
$destinationPath = config('app.private_uploads').'/users';
@@ -41,31 +40,7 @@ class UserFilesController extends Controller
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
foreach ($files as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'user-'.$user->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/users/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/users/'.$file_name, file_get_contents($file));
}
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
//Log the uploaded file to the log
$logAction = new Actionlog();
+52 -5
View File
@@ -37,23 +37,33 @@ class SlackSettingsForm extends Component
public function mount() {
$this->webhook_text= [
"slack" => array(
"slack" => array(
"name" => trans('admin/settings/general.slack') ,
"icon" => 'fab fa-slack',
"placeholder" => "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX",
"link" => 'https://api.slack.com/messaging/webhooks',
"icon" => 'fab fa-slack',
"placeholder" => "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX",
"link" => 'https://api.slack.com/messaging/webhooks',
"test" => "testWebhook"
),
"general"=> array(
"name" => trans('admin/settings/general.general_webhook'),
"icon" => "fab fa-hashtag",
"placeholder" => trans('general.url'),
"link" => "",
"test" => "testWebhook"
),
"google" => array(
"name" => trans('admin/settings/general.google_workspaces'),
"icon" => "fa-brands fa-google",
"placeholder" => "https://chat.googleapis.com/v1/spaces/xxxxxxxx/messages?key=xxxxxx",
"link" => "https://developers.google.com/chat/how-tos/webhooks#register_the_incoming_webhook",
"test" => "googleWebhookTest"
),
"microsoft" => array(
"name" => trans('admin/settings/general.ms_teams'),
"icon" => "fa-brands fa-microsoft",
"placeholder" => "https://abcd.webhook.office.com/webhookb2/XXXXXXX",
"link" => "https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=dotnet#create-incoming-webhooks-1",
"test" => "msTeamTestWebhook"
),
];
@@ -64,10 +74,14 @@ class SlackSettingsForm extends Component
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"];
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"];
$this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"];
$this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"];
$this->webhook_endpoint = $this->setting->webhook_endpoint;
$this->webhook_channel = $this->setting->webhook_channel;
$this->webhook_botname = $this->setting->webhook_botname;
$this->webhook_options = $this->setting->webhook_selected;
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
$this->webhook_channel = '#NA';
}
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
@@ -87,10 +101,14 @@ class SlackSettingsForm extends Component
$this->webhook_placeholder = $this->webhook_text[$this->webhook_selected]["placeholder"];
$this->webhook_endpoint = null;
$this->webhook_link = $this->webhook_text[$this->webhook_selected]["link"];
$this->webhook_test = $this->webhook_text[$this->webhook_selected]["test"];
if($this->webhook_selected != 'slack'){
$this->isDisabled= '';
$this->save_button = trans('general.save');
}
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
$this->webhook_channel = '#NA';
}
}
@@ -151,6 +169,7 @@ class SlackSettingsForm extends Component
}
public function clearSettings(){
if (Helper::isDemoMode()) {
@@ -187,7 +206,35 @@ class SlackSettingsForm extends Component
}
}
public function msTeamTestWebhook(){
public function googleWebhookTest(){
$payload = [
"text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
];
try {
$response = Http::withHeaders([
'content-type' => 'applications/json',
])->post($this->webhook_endpoint,
$payload)->throw();
if (($response->getStatusCode() == 302) || ($response->getStatusCode() == 301)) {
return session()->flash('error', trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
}
$this->isDisabled='';
$this->save_button = trans('general.save');
return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
} catch (\Exception $e) {
$this->isDisabled='disabled';
$this->save_button = trans('admin/settings/general.webhook_presave');
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
}
}
public function msTeamTestWebhook(){
$payload =
[
-30
View File
@@ -1,30 +0,0 @@
<?php
namespace App\Http\Requests;
class AssetFileRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
return [
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp|max:'.$max_file_size,
];
}
}
+22 -24
View File
@@ -103,16 +103,35 @@ class ImageUploadRequest extends Request
\Log::info('File name will be: '.$file_name);
\Log::debug('File extension is: '.$ext);
if (($image->getClientOriginalExtension() !== 'webp') && ($image->getClientOriginalExtension() !== 'svg')) {
if ($image->getMimeType() == 'image/webp') {
// If the file is a webp, we need to just move it since webp support
// needs to be compiled into gd for resizing to be available
\Log::debug('This is a webp, just move it');
Storage::disk('public')->put($path.'/'.$file_name, file_get_contents($image));
} elseif($image->getMimeType() == 'image/svg+xml') {
// If the file is an SVG, we need to clean it and NOT encode it
\Log::debug('This is an SVG');
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($image->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::disk('public')->put($path . '/' . $file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug($e);
}
} else {
\Log::debug('Not an SVG or webp - resize');
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
try {
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
$upload = Image::make($image->getRealPath())->setFileInfoFromPath($image->getRealPath())->resize(null, $w, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
})->orientate();
} catch(NotReadableException $e) {
\Log::debug($e);
$validator = \Validator::make([], []);
@@ -124,27 +143,6 @@ class ImageUploadRequest extends Request
// This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
} else {
// If the file is a webp, we need to just move it since webp support
// needs to be compiled into gd for resizing to be available
if ($image->getClientOriginalExtension() == 'webp') {
\Log::debug('This is a webp, just move it');
Storage::disk('public')->put($path.'/'.$file_name, file_get_contents($image));
// If the file is an SVG, we need to clean it and NOT encode it
} else {
\Log::debug('This is an SVG');
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($image->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
\Log::debug('Trying to upload to: '.$path.'/'.$file_name);
Storage::disk('public')->put($path.'/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
}
}
// Remove Current image if exists
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace App\Http\Requests;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
class UploadFileRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
return [
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp|max:'.$max_file_size,
];
}
/**
* Sanitizes (if needed) and Saves a file to the appropriate location
* Returns the 'short' (storage-relative) filename
*
* TODO - this has a lot of similarities to UploadImageRequest's handleImage; is there
* a way to merge them or extend one into the other?
*/
public function handleFile(string $dirname, string $name_prefix, $file): string
{
$extension = $file->getClientOriginalExtension();
$file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$file->guessExtension();
\Log::debug("Your filetype IS: ".$file->getMimeType());
// Check for SVG and sanitize it
if ($file->getMimeType() === 'image/svg+xml') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put($dirname.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
$put_results = Storage::put($dirname.$file_name, file_get_contents($file));
\Log::debug("Here are the '$put_results' (should be 0 or 1 or true or false or something?)");
}
return $file_name;
}
}
@@ -28,12 +28,20 @@ class AssetMaintenancesTransformer
'id' => (int) $assetmaintenance->asset->id,
'name'=> ($assetmaintenance->asset->name) ? e($assetmaintenance->asset->name) : null,
'asset_tag'=> e($assetmaintenance->asset->asset_tag),
'serial'=> e($assetmaintenance->asset->serial),
'deleted_at'=> e($assetmaintenance->asset->deleted_at),
'created_at'=> e($assetmaintenance->asset->created_at),
] : null,
'model' => (($assetmaintenance->asset) && ($assetmaintenance->asset->model)) ? [
'id' => (int) $assetmaintenance->asset->model->id,
'name'=> ($assetmaintenance->asset->model->name) ? e($assetmaintenance->asset->model->name).' '.e($assetmaintenance->asset->model->model_number) : null,
] : null,
'status_label' => ($assetmaintenance->asset->assetstatus) ? [
'id' => (int) $assetmaintenance->asset->assetstatus->id,
'name'=> e($assetmaintenance->asset->assetstatus->name),
'status_type'=> e($assetmaintenance->asset->assetstatus->getStatuslabelType()),
'status_meta' => e($assetmaintenance->asset->present()->statusMeta),
] : null,
'company' => (($assetmaintenance->asset) && ($assetmaintenance->asset->company)) ? [
'id' => (int) $assetmaintenance->asset->company->id,
'name'=> ($assetmaintenance->asset->company->name) ? e($assetmaintenance->asset->company->name) : null,
@@ -64,7 +72,7 @@ class AssetMaintenancesTransformer
];
$permissions_array['available_actions'] = [
'update' => Gate::allows('update', Asset::class),
'update' => (Gate::allows('update', Asset::class) && ($assetmaintenance->asset->deleted_at=='')) ? true : false,
'delete' => Gate::allows('delete', Asset::class),
];
+2 -3
View File
@@ -46,10 +46,9 @@ class AccessoryImporter extends ItemImporter
$this->item['min_amt'] = $this->findCsvMatch($row, "min_amt");
$accessory->fill($this->sanitizeItemForStoring($accessory));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
// $accessory->unsetEventDispatcher();
// This sets an attribute on the Loggable trait for the action log
$accessory->setImported(true);
if ($accessory->save()) {
$accessory->logCreate('Imported using CSV Importer');
$this->log('Accessory '.$this->item['name'].' was created');
return;
+2 -2
View File
@@ -135,10 +135,10 @@ class AssetImporter extends ItemImporter
$asset->{$custom_field} = $val;
}
}
// This sets an attribute on the Loggable trait for the action log
$asset->setImported(true);
if ($asset->save()) {
$asset->logCreate(trans('general.importer.import_note'));
$this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
// If we have a target to checkout to, lets do so.
+3 -3
View File
@@ -48,10 +48,10 @@ class ComponentImporter extends ItemImporter
$this->log('No matching component, creating one');
$component = new Component;
$component->fill($this->sanitizeItemForStoring($component));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
$component->unsetEventDispatcher();
// This sets an attribute on the Loggable trait for the action log
$component->setImported(true);
if ($component->save()) {
$component->logCreate('Imported using CSV Importer');
$this->log('Component '.$this->item['name'].' was created');
// If we have an asset tag, checkout to that asset.
+3 -3
View File
@@ -45,10 +45,10 @@ class ConsumableImporter extends ItemImporter
$this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number'));
$this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt"));
$consumable->fill($this->sanitizeItemForStoring($consumable));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
$consumable->unsetEventDispatcher();
// This sets an attribute on the Loggable trait for the action log
$consumable->setImported(true);
if ($consumable->save()) {
$consumable->logCreate('Imported using CSV Importer');
$this->log('Consumable '.$this->item['name'].' was created');
return;
+3 -3
View File
@@ -85,10 +85,10 @@ class LicenseImporter extends ItemImporter
} else {
$license->fill($this->sanitizeItemForStoring($license));
}
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.
// $license->unsetEventDispatcher();
// This sets an attribute on the Loggable trait for the action log
$license->setImported(true);
if ($license->save()) {
$license->logCreate('Imported using csv importer');
$this->log('License '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');
// Lets try to checkout seats if the fields exist and we have seats.
+8 -6
View File
@@ -60,16 +60,18 @@ class CheckoutableListener
if ($this->shouldSendWebhookNotification()) {
//slack doesn't include the url in its messaging format so this is needed to hit the endpoint
if(Setting::getSettings()->webhook_selected =='slack') {
if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') {
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
->notify($this->getCheckoutNotification($event));
}
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
} catch (Exception $e) {
Log::error("Exception caught during checkout notification: " . $e->getMessage());
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
}
}
@@ -113,7 +115,7 @@ class CheckoutableListener
);
}
//slack doesn't include the url in its messaging format so this is needed to hit the endpoint
if(Setting::getSettings()->webhook_selected =='slack') {
if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') {
if ($this->shouldSendWebhookNotification()) {
Notification::route('slack', Setting::getSettings()->webhook_endpoint)
@@ -122,9 +124,9 @@ class CheckoutableListener
}
} catch (ClientException $e) {
Log::debug("Exception caught during checkout notification: " . $e->getMessage());
Log::warning("Exception caught during checkout notification: " . $e->getMessage());
} catch (Exception $e) {
Log::error("Exception caught during checkin notification: " . $e->getMessage());
Log::warning("Exception caught during checkin notification: " . $e->getMessage());
}
}
+15 -1
View File
@@ -19,6 +19,9 @@ class Actionlog extends SnipeModel
{
use HasFactory;
// This is to manually set the source (via setActionSource()) for determineActionSource()
protected ?string $source = null;
protected $presenter = \App\Presenters\ActionlogPresenter::class;
use SoftDeletes;
use Presentable;
@@ -341,7 +344,12 @@ class Actionlog extends SnipeModel
* @since v6.3.0
* @return string
*/
public function determineActionSource() {
public function determineActionSource(): string
{
// This is a manually set source
if($this->source) {
return $this->source;
}
// This is an API call
if (((request()->header('content-type') && (request()->header('accept'))=='application/json'))
@@ -358,4 +366,10 @@ class Actionlog extends SnipeModel
return 'cli/unknown';
}
// Manually sets $this->source for determineActionSource()
public function setActionSource($source = null): void
{
$this->source = $source;
}
}
+1 -1
View File
@@ -1560,7 +1560,7 @@ class Asset extends Depreciable
*
* In short, this set of statements tells the query builder to ONLY query against an
* actual field that's being passed if it doesn't meet known relational fields. This
* allows us to query custom fields directly in the assetsv table
* allows us to query custom fields directly in the assets table
* (regardless of their name) and *skip* any fields that we already know can only be
* searched through relational searches that we do earlier in this method.
*
+41 -2
View File
@@ -62,7 +62,15 @@ class AssetMaintenance extends Model implements ICompanyableChild
*
* @var array
*/
protected $searchableAttributes = ['title', 'notes', 'asset_maintenance_type', 'cost', 'start_date', 'completion_date'];
protected $searchableAttributes =
[
'title',
'notes',
'asset_maintenance_type',
'cost',
'start_date',
'completion_date'
];
/**
* The relations and their attributes that should be included when searching the model.
@@ -70,9 +78,10 @@ class AssetMaintenance extends Model implements ICompanyableChild
* @var array
*/
protected $searchableRelations = [
'asset' => ['name', 'asset_tag'],
'asset' => ['name', 'asset_tag', 'serial'],
'asset.model' => ['name', 'model_number'],
'asset.supplier' => ['name'],
'asset.assetstatus' => ['name'],
'supplier' => ['name'],
];
@@ -197,6 +206,7 @@ class AssetMaintenance extends Model implements ICompanyableChild
->orderBy('suppliers_maintenances.name', $order);
}
/**
* Query builder scope to order on admin user
*
@@ -239,4 +249,33 @@ class AssetMaintenance extends Model implements ICompanyableChild
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
->orderBy('assets.name', $order);
}
/**
* Query builder scope to order on serial
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param string $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderByAssetSerial($query, $order)
{
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
->orderBy('assets.serial', $order);
}
/**
* Query builder scope to order on status label name
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderStatusName($query, $order)
{
return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id')
->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id')
->orderBy('maintained_asset_status.name', $order);
}
}
+8
View File
@@ -113,6 +113,14 @@ final class Company extends SnipeModel
}
}
/**
* Get the company id for the current user taking into
* account the full multiple company support setting
* and if the current user is a super user.
*
* @param $unescaped_input
* @return int|mixed|string|null
*/
public static function getIdForCurrentUser($unescaped_input)
{
if (! static::isFullMultipleCompanySupportEnabled()) {
+8
View File
@@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Auth;
trait Loggable
{
// an attribute for setting whether or not the item was imported
public ?bool $imported = false;
/**
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.4]
@@ -18,6 +21,11 @@ trait Loggable
return $this->morphMany(Actionlog::class, 'item');
}
public function setImported(bool $bool): void
{
$this->imported = $bool;
}
/**
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
* @since [v3.4]
-1
View File
@@ -352,7 +352,6 @@ class Setting extends Model
'ldap_client_tls_cert',
'ldap_default_group',
'ldap_dept',
'ldap_emp_num',
'ldap_phone_field',
'ldap_jobtitle',
'ldap_manager',
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -38,13 +43,17 @@ class CheckinAccessoryNotification extends Notification
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -54,34 +63,8 @@ class CheckinAccessoryNotification extends Notification
if ($this->target instanceof User && $this->target->email != '') {
\Log::debug('The target is a user');
/**
* Send an email if the asset requires acceptance,
* so the user can accept or decline the asset
*/
if (($this->item->requireAcceptance()) || ($this->item->getEula()) || ($this->item->checkin_email())) {
$notifyBy[] = 'mail';
}
/**
* Send an email if the asset requires acceptance,
* so the user can accept or decline the asset
*/
if ($this->item->requireAcceptance()) {
\Log::debug('This accessory requires acceptance');
}
/**
* Send an email if the item has a EULA, since the user should always receive it
*/
if ($this->item->getEula()) {
\Log::debug('This accessory has a EULA');
}
/**
* Send an email if an email should be sent at checkin/checkout
*/
if ($this->item->checkin_email()) {
\Log::debug('This accessory has a checkin_email()');
$notifyBy[] = 'mail';
}
}
@@ -132,6 +115,32 @@ class CheckinAccessoryNotification extends Notification
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Accessory_Checkin_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.checked_into').': '.$item->location->name ? $item->location->name : '',
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
trans('admin/hardware/form.notes').": ".$note ?: '',
)
->onClick(route('accessories.show', $item->id))
)
)
);
}
/**
* Get the mail representation of the notification.
+37 -1
View File
@@ -10,6 +10,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -46,12 +51,16 @@ class CheckinAssetNotification extends Notification
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
\Log::debug('use webhook');
$notifyBy[] = 'slack';
}
@@ -108,6 +117,33 @@ class CheckinAssetNotification extends Notification
->fact(trans('admin/hardware/form.status'), $item->assetstatus->name)
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Asset_Checkin_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.checked_into') ?: '',
$item->location->name ? $item->location->name : '',
trans('admin/hardware/form.status').": ".$item->assetstatus->name,
)
->onClick(route('hardware.show', $item->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -43,12 +48,16 @@ class CheckinLicenseSeatNotification extends Notification
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -113,6 +122,34 @@ class CheckinLicenseSeatNotification extends Notification
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.License_Checkin_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.checkedin_from') ?: '',
$target->present()->fullName() ?: '',
trans('admin/consumables/general.remaining').': '.$item->availCount()->count(),
)
->onClick(route('licenses.show', $item->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -37,13 +42,17 @@ class CheckoutAccessoryNotification extends Notification
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -123,6 +132,34 @@ class CheckoutAccessoryNotification extends Notification
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Accessory_Checkout_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->name ?: '',
trans('admin/consumables/general.remaining').": ". $item->numRemaining(),
)
->onClick(route('users.show', $target->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -11,6 +11,13 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\Enums\Icon;
use NotificationChannels\GoogleChat\Enums\ImageStyle;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -54,13 +61,20 @@ class CheckoutAssetNotification extends Notification
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft'){
return [MicrosoftTeamsChannel::class];
$notifyBy[] = MicrosoftTeamsChannel::class;
}
$notifyBy = [];
if ((Setting::getSettings()) && (Setting::getSettings()->webhook_selected == 'slack')) {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
\Log::debug('use webhook');
$notifyBy[] = 'slack';
}
@@ -143,6 +157,33 @@ class CheckoutAssetNotification extends Notification
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Asset_Checkout_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->name ?: '',
$note ?: '',
)
->onClick(route('users.show', $target->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -44,13 +49,17 @@ class CheckoutConsumableNotification extends Notification
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -128,6 +137,33 @@ class CheckoutConsumableNotification extends Notification
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.Consumable_checkout_notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->fullName() ?: '',
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
)
->onClick(route('users.show', $target->id))
)
)
);
}
/**
* Get the mail representation of the notification.
@@ -9,6 +9,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\GoogleChat\Card;
use NotificationChannels\GoogleChat\GoogleChatChannel;
use NotificationChannels\GoogleChat\GoogleChatMessage;
use NotificationChannels\GoogleChat\Section;
use NotificationChannels\GoogleChat\Widgets\KeyValue;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
@@ -43,15 +48,18 @@ class CheckoutLicenseSeatNotification extends Notification
*/
public function via()
{
$notifyBy = [];
if (Setting::getSettings()->webhook_selected == 'google'){
$notifyBy[] = GoogleChatChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'microsoft'){
$notifyBy[] = MicrosoftTeamsChannel::class;
}
if (Setting::getSettings()->webhook_selected == 'slack') {
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
$notifyBy[] = 'slack';
}
@@ -129,6 +137,33 @@ class CheckoutLicenseSeatNotification extends Notification
->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count())
->fact(trans('mail.notes'), $note ?: '');
}
public function toGoogleChat()
{
$target = $this->target;
$item = $this->item;
$note = $this->note;
return GoogleChatMessage::create()
->to($this->settings->webhook_endpoint)
->card(
Card::create()
->header(
'<strong>'.trans('mail.License_Checkout_Notification').'</strong>' ?: '',
htmlspecialchars_decode($item->present()->name) ?: '',
)
->section(
Section::create(
KeyValue::create(
trans('mail.assigned_to') ?: '',
$target->present()->name ?: '',
trans('admin/consumables/general.remaining').': '.$item->availCount()->count(),
)
->onClick(route('users.show', $target->id))
)
)
);
}
/**
* Get the mail representation of the notification.
+3
View File
@@ -38,6 +38,9 @@ class AccessoryObserver
$logAction->item_id = $accessory->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($accessory->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+3
View File
@@ -109,6 +109,9 @@ class AssetObserver
$logAction->item_id = $asset->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($asset->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+3
View File
@@ -38,6 +38,9 @@ class ComponentObserver
$logAction->item_id = $component->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($component->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+3
View File
@@ -38,6 +38,9 @@ class ConsumableObserver
$logAction->item_id = $consumable->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($consumable->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
+3
View File
@@ -38,6 +38,9 @@ class LicenseObserver
$logAction->item_id = $license->id;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->user_id = Auth::id();
if($license->imported) {
$logAction->setActionSource('importer');
}
$logAction->logaction('create');
}
@@ -41,6 +41,19 @@ class AssetMaintenancesPresenter extends Presenter
'sortable' => true,
'title' => trans('admin/hardware/table.asset_tag'),
'formatter' => 'assetTagLinkFormatter',
], [
'field' => 'serial',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/hardware/table.serial'),
'formatter' => 'assetSerialLinkFormatter',
], [
'field' => 'status_label',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/hardware/table.status'),
'visible' => true,
'formatter' => 'statuslabelsLinkObjFormatter',
], [
'field' => 'model',
'searchable' => true,
+1 -1
View File
@@ -45,7 +45,7 @@ class ManufacturerPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('admin/manufacturers/table.url'),
'title' => trans('general.url'),
'visible' => true,
'formatter' => 'externalLinkFormatter',
],
+1
View File
@@ -42,6 +42,7 @@
"guzzlehttp/guzzle": "^7.0.1",
"intervention/image": "^2.5",
"javiereguiluz/easyslugger": "^1.0",
"laravel-notification-channels/google-chat": "^1.0",
"laravel-notification-channels/microsoft-teams": "^1.1",
"laravel/framework": "^8.46",
"laravel/helpers": "^1.4",
Generated
+80 -23
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "89580c52de91168aac8321460bd428e2",
"content-hash": "9cca85cd0074df9154765b1ab52f83fa",
"packages": [
{
"name": "alek13/slack",
@@ -3188,6 +3188,63 @@
},
"time": "2015-04-12T19:57:10+00:00"
},
{
"name": "laravel-notification-channels/google-chat",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/laravel-notification-channels/google-chat.git",
"reference": "843078439403a925b484cef99a26b447e30a9c32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel-notification-channels/google-chat/zipball/843078439403a925b484cef99a26b447e30a9c32",
"reference": "843078439403a925b484cef99a26b447e30a9c32",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.3 || ^7.0",
"illuminate/notifications": "~8.0",
"illuminate/support": "~8.0",
"php": ">=7.3"
},
"require-dev": {
"orchestra/testbench": "^6.0",
"phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"NotificationChannels\\GoogleChat\\GoogleChatServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"NotificationChannels\\GoogleChat\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank Dixon",
"email": "frank@thetreehouse.family",
"homepage": "https://thetreehouse.family",
"role": "Developer"
}
],
"description": "Google Chat Notification Channel for Laravel (fka. Hangouts Chat)",
"homepage": "https://github.com/laravel-notification-channels/google-chat",
"support": {
"issues": "https://github.com/laravel-notification-channels/google-chat/issues",
"source": "https://github.com/laravel-notification-channels/google-chat/tree/v1.0.1"
},
"time": "2021-07-15T22:40:51+00:00"
},
{
"name": "laravel-notification-channels/microsoft-teams",
"version": "v1.1.4",
@@ -4747,16 +4804,16 @@
},
{
"name": "league/oauth2-server",
"version": "8.3.5",
"version": "8.4.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth2-server.git",
"reference": "7aeb7c42b463b1a6fe4d084d3145e2fa22436876"
"reference": "007dc5f6c0151a73b133fec36c9686cc956209d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/7aeb7c42b463b1a6fe4d084d3145e2fa22436876",
"reference": "7aeb7c42b463b1a6fe4d084d3145e2fa22436876",
"url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/007dc5f6c0151a73b133fec36c9686cc956209d3",
"reference": "007dc5f6c0151a73b133fec36c9686cc956209d3",
"shasum": ""
},
"require": {
@@ -4823,7 +4880,7 @@
],
"support": {
"issues": "https://github.com/thephpleague/oauth2-server/issues",
"source": "https://github.com/thephpleague/oauth2-server/tree/8.3.5"
"source": "https://github.com/thephpleague/oauth2-server/tree/8.4.2"
},
"funding": [
{
@@ -4831,7 +4888,7 @@
"type": "github"
}
],
"time": "2022-05-03T21:21:28+00:00"
"time": "2023-08-02T22:54:39+00:00"
},
{
"name": "league/uri",
@@ -9612,16 +9669,16 @@
},
{
"name": "symfony/http-kernel",
"version": "v5.4.10",
"version": "v5.4.20",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "255ae3b0a488d78fbb34da23d3e0c059874b5948"
"reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/255ae3b0a488d78fbb34da23d3e0c059874b5948",
"reference": "255ae3b0a488d78fbb34da23d3e0c059874b5948",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/aaeec341582d3c160cc9ecfa8b2419ba6c69954e",
"reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e",
"shasum": ""
},
"require": {
@@ -9704,7 +9761,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-kernel/tree/v5.4.10"
"source": "https://github.com/symfony/http-kernel/tree/v5.4.20"
},
"funding": [
{
@@ -9720,7 +9777,7 @@
"type": "tidelift"
}
],
"time": "2022-06-26T16:57:59+00:00"
"time": "2023-02-01T08:18:48+00:00"
},
{
"name": "symfony/mime",
@@ -12590,16 +12647,16 @@
},
{
"name": "composer/composer",
"version": "2.6.6",
"version": "2.7.1",
"source": {
"type": "git",
"url": "https://github.com/composer/composer.git",
"reference": "683557bd2466072777309d039534bb1332d0dda5"
"reference": "aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/composer/zipball/683557bd2466072777309d039534bb1332d0dda5",
"reference": "683557bd2466072777309d039534bb1332d0dda5",
"url": "https://api.github.com/repos/composer/composer/zipball/aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc",
"reference": "aaf6ed5ccd27c23f79a545e351b4d7842a99d0bc",
"shasum": ""
},
"require": {
@@ -12617,7 +12674,7 @@
"seld/jsonlint": "^1.4",
"seld/phar-utils": "^1.2",
"seld/signal-handler": "^2.0",
"symfony/console": "^5.4.11 || ^6.0.11",
"symfony/console": "^5.4.11 || ^6.0.11 || ^7",
"symfony/filesystem": "^5.4 || ^6.0 || ^7",
"symfony/finder": "^5.4 || ^6.0 || ^7",
"symfony/polyfill-php73": "^1.24",
@@ -12631,7 +12688,7 @@
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan-strict-rules": "^1",
"phpstan/phpstan-symfony": "^1.2.10",
"symfony/phpunit-bridge": "^6.0 || ^7"
"symfony/phpunit-bridge": "^6.4.1 || ^7.0.1"
},
"suggest": {
"ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages",
@@ -12644,7 +12701,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.6-dev"
"dev-main": "2.7-dev"
},
"phpstan": {
"includes": [
@@ -12684,7 +12741,7 @@
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/composer/issues",
"security": "https://github.com/composer/composer/security/policy",
"source": "https://github.com/composer/composer/tree/2.6.6"
"source": "https://github.com/composer/composer/tree/2.7.1"
},
"funding": [
{
@@ -12700,7 +12757,7 @@
"type": "tidelift"
}
],
"time": "2023-12-08T17:32:26+00:00"
"time": "2024-02-09T14:26:28+00:00"
},
{
"name": "composer/metadata-minifier",
@@ -17031,5 +17088,5 @@
"ext-pdo": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"plugin-api-version": "2.6.0"
}
+5 -5
View File
@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v6.3.0',
'full_app_version' => 'v6.3.0 - build 12490-g9136415bb',
'build_version' => '12490',
'app_version' => 'v6.3.1',
'full_app_version' => 'v6.3.1 - build 12672-g00cea3eb3',
'build_version' => '12672',
'prerelease_version' => '',
'hash_version' => 'g9136415bb',
'full_hash' => 'v6.3.0-729-g9136415bb',
'hash_version' => 'g00cea3eb3',
'full_hash' => 'v6.3.1-180-g00cea3eb3',
'branch' => 'master',
);
+40 -1
View File
@@ -8,6 +8,7 @@ use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Supplier;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\Factory;
class AccessoryFactory extends Factory
@@ -33,7 +34,7 @@ class AccessoryFactory extends Factory
$this->faker->randomElement(['Keyboard', 'Wired'])
),
'user_id' => User::factory()->superuser(),
'category_id' => Category::factory(),
'category_id' => Category::factory()->forAccessories(),
'model_number' => $this->faker->numberBetween(1000000, 50000000),
'location_id' => Location::factory(),
'qty' => 1,
@@ -114,4 +115,42 @@ class AccessoryFactory extends Factory
];
});
}
public function withoutItemsRemaining()
{
return $this->state(function () {
return [
'qty' => 1,
];
})->afterCreating(function ($accessory) {
$user = User::factory()->create();
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => now(),
'user_id' => $user->id,
'assigned_to' => $user->id,
'note' => '',
]);
});
}
public function requiringAcceptance()
{
return $this->afterCreating(function ($accessory) {
$accessory->category->update(['require_acceptance' => 1]);
});
}
public function checkedOutToUser(User $user = null)
{
return $this->afterCreating(function (Accessory $accessory) use ($user) {
$accessory->users()->attach($accessory->id, [
'accessory_id' => $accessory->id,
'created_at' => Carbon::now(),
'user_id' => 1,
'assigned_to' => $user->id ?? User::factory()->create()->id,
]);
});
}
}
+6
View File
@@ -172,4 +172,10 @@ class CategoryFactory extends Factory
]);
}
public function forAccessories()
{
return $this->state([
'category_type' => 'accessory',
]);
}
}
+25
View File
@@ -91,4 +91,29 @@ class ConsumableFactory extends Factory
];
});
}
public function withoutItemsRemaining()
{
return $this->state(function () {
return [
'qty' => 1,
];
})->afterCreating(function (Consumable $consumable) {
$user = User::factory()->create();
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
'user_id' => $user->id,
'assigned_to' => $user->id,
'note' => '',
]);
});
}
public function requiringAcceptance()
{
return $this->afterCreating(function (Consumable $consumable) {
$consumable->category->update(['require_acceptance' => 1]);
});
}
}
+69 -30
View File
@@ -1565,9 +1565,9 @@
}
},
"@types/eslint": {
"version": "8.56.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.1.tgz",
"integrity": "sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ==",
"version": "8.56.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz",
"integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==",
"requires": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -2350,9 +2350,9 @@
}
},
"alpinejs": {
"version": "3.13.3",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
"integrity": "sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==",
"version": "3.13.5",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.5.tgz",
"integrity": "sha512-1d2XeNGN+Zn7j4mUAKXtAgdc4/rLeadyTMWeJGXF5DzwawPBxwTiBhFFm6w/Ei8eJxUZeyNWWSD9zknfdz1kEw==",
"requires": {
"@vue/reactivity": "~3.1.1"
}
@@ -3348,9 +3348,9 @@
"integrity": "sha1-EQPWvADPv6jPyaJZmrUYxVZD2j8="
},
"bootstrap-table": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.22.1.tgz",
"integrity": "sha512-Nw8p+BmaiMDSfoer/p49YeI3vJQAWhudxhyKMuqnJBb3NRvCRewMk7JDgiN9SQO3YeSejOirKtcdWpM0dtddWg=="
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.22.2.tgz",
"integrity": "sha512-ZjZGcEXm/N7N/wAykmANWKKV+U+7AxgoNuBwWLrKbvAGT8XXS2f0OCiFmuMwpkqg7pDbF+ff9bEf/lOAlxcF1w=="
},
"brace-expansion": {
"version": "1.1.11",
@@ -3611,6 +3611,7 @@
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz",
"integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001286",
"electron-to-chromium": "^1.4.17",
@@ -3731,7 +3732,8 @@
"caniuse-lite": {
"version": "1.0.30001292",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001292.tgz",
"integrity": "sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw=="
"integrity": "sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw==",
"dev": true
},
"canvg": {
"version": "3.0.10",
@@ -4931,7 +4933,8 @@
"electron-to-chromium": {
"version": "1.4.28",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.28.tgz",
"integrity": "sha512-Gzbf0wUtKfyPaqf0Plz+Ctinf9eQIzxEqBHwSvbGfeOm9GMNdLxyu1dNiCUfM+x6r4BE0xUJNh3Nmg9gfAtTmg=="
"integrity": "sha512-Gzbf0wUtKfyPaqf0Plz+Ctinf9eQIzxEqBHwSvbGfeOm9GMNdLxyu1dNiCUfM+x6r4BE0xUJNh3Nmg9gfAtTmg==",
"dev": true
},
"elliptic": {
"version": "6.5.4",
@@ -17418,7 +17421,8 @@
"node-releases": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz",
"integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA=="
"integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==",
"dev": true
},
"normalize-path": {
"version": "3.0.0",
@@ -20097,18 +20101,18 @@
"dev": true
},
"webpack": {
"version": "5.89.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
"integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
"version": "5.90.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
"integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
"requires": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
"@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.11.5",
"@webassemblyjs/wasm-edit": "^1.11.5",
"@webassemblyjs/wasm-parser": "^1.11.5",
"acorn": "^8.7.1",
"acorn-import-assertions": "^1.9.0",
"browserslist": "^4.14.5",
"browserslist": "^4.21.10",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.15.0",
"es-module-lexer": "^1.2.1",
@@ -20122,15 +20126,15 @@
"neo-async": "^2.6.2",
"schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.7",
"terser-webpack-plugin": "^5.3.10",
"watchpack": "^2.4.0",
"webpack-sources": "^3.2.3"
},
"dependencies": {
"@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA=="
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.15",
@@ -20138,9 +20142,9 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.20",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
"version": "0.3.22",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
"integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
"requires": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
@@ -20151,16 +20155,42 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"browserslist": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"requires": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
"node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13"
}
},
"caniuse-lite": {
"version": "1.0.30001587",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz",
"integrity": "sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA=="
},
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"electron-to-chromium": {
"version": "1.4.670",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.670.tgz",
"integrity": "sha512-hcijYOWjOtjKrKPtNA6tuLlA/bTLO3heFG8pQA6mLpq7dRydSWicXova5lyxDzp1iVJaYhK7J2OQlGE52KYn7A=="
},
"graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
},
"schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@@ -20172,17 +20202,17 @@
}
},
"serialize-javascript": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz",
"integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==",
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"requires": {
"randombytes": "^2.1.0"
}
},
"terser": {
"version": "5.26.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz",
"integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==",
"version": "5.27.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz",
"integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==",
"requires": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -20202,6 +20232,15 @@
"terser": "^5.26.0"
}
},
"update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
"requires": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
}
},
"webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
+3 -3
View File
@@ -33,13 +33,13 @@
"acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18",
"ajv": "^6.12.6",
"alpinejs": "^3.13.3",
"alpinejs": "^3.13.5",
"blueimp-file-upload": "^9.34.0",
"bootstrap": "^3.4.1",
"bootstrap-colorpicker": "^2.5.3",
"bootstrap-datepicker": "^1.10.0",
"bootstrap-less": "^3.3.8",
"bootstrap-table": "1.22.1",
"bootstrap-table": "1.22.2",
"chart.js": "^2.9.4",
"clipboard": "^2.0.11",
"css-loader": "^5.0.0",
@@ -59,6 +59,6 @@
"tableexport.jquery.plugin": "1.28.0",
"tether": "^1.4.0",
"vue-resource": "^1.5.2",
"webpack": "^5.89.0"
"webpack": "^5.90.0"
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -1,5 +1,5 @@
{
"/js/build/app.js": "/js/build/app.js?id=72071a8a4dc754c61b0440d3c4119cbf",
"/js/build/app.js": "/js/build/app.js?id=cad71122a8a3f0cd6c5960e95f4f0f8a",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=392cc93cfc0be0349bab9697669dd091",
"/css/build/overrides.css": "/css/build/overrides.css?id=6a7f37afafaaf9ccea99a7391cdf02b2",
"/css/build/app.css": "/css/build/app.css?id=ea3875faceb1d09c162d00fbf5b4df57",
@@ -18,7 +18,7 @@
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
"/css/dist/all.css": "/css/dist/all.css?id=4c76e55563c6761a252a7df48c5d9d66",
"/css/dist/all.css": "/css/dist/all.css?id=2ef1965d45a0a72336dd8e9b93f82d80",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=69e5d8e4e818f05fd882cceb758d1eba",
@@ -29,11 +29,11 @@
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=a0feb384c3c6071947a49708f2b0bc85",
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=e24ec0b8661f7fa333b29444df39e399",
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=e11465c0eff0549edd4e8ea6bbcf242f",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=2bd29fa7f9d666800c246a52ce708633",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=afa255bf30b2a7c11a97e3165128d183",
"/js/build/vendor.js": "/js/build/vendor.js?id=a2b971da417306a63385c8098acfe4af",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=1f678160a05960c3087fb8263168ff41",
"/js/dist/all.js": "/js/dist/all.js?id=256c2e7da22a51ce49b03ffa24527cee",
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=7f9a130eda6916eaa32a0a57e81918f3",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=29340c70d13855fa0165cd4d799c6f5b",
"/js/dist/all.js": "/js/dist/all.js?id=a9bde3f908f73634c0d1b4cd3f835092",
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=19ccc62a8f1ea103dede4808837384d4",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=0ed42b67f9b02a74815e885bfd9e3f66",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=1f33ca3d860461c1127ec465ab3ebb6b",
+1 -6
View File
@@ -191,17 +191,12 @@ $(document).ready(function () {
* Select2
*/
var iOS = /iPhone|iPad|iPod/.test(navigator.userAgent) && !window.MSStream;
if(!iOS)
{
// Vue collision: Avoid overriding a vue select2 instance
// by checking to see if the item has already been select2'd.
$('select.select2:not(".select2-hidden-accessible")').each(function (i,obj) {
{
$(obj).select2();
}
});
}
// $('.datepicker').datepicker();
// var datepicker = $.fn.datepicker.noConflict(); // return $.fn.datepicker to previously assigned value
@@ -67,9 +67,10 @@ return [
'footer_text' => 'Additional Footer Text ',
'footer_text_help' => 'This text will appear in the right-side footer. Links are allowed using <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>. Line breaks, headers, images, etc may result in unpredictable results.',
'general_settings' => 'General Settings',
'general_settings_keywords' => 'company support, signature, acceptance, email format, username format, images, per page, thumbnail, eula, tos, dashboard, privacy',
'general_settings_keywords' => 'company support, signature, acceptance, email format, username format, images, per page, thumbnail, eula, gravatar, tos, dashboard, privacy',
'general_settings_help' => 'Default EULA and more',
'generate_backup' => 'Generate Backup',
'google_workspaces' => 'Google Workspaces',
'header_color' => 'Header Color',
'info' => 'These settings let you customize certain aspects of your installation.',
'label_logo' => 'Label Logo',
@@ -86,7 +87,6 @@ return [
'ldap_integration' => 'LDAP Integration',
'ldap_settings' => 'LDAP Settings',
'ldap_client_tls_cert_help' => 'Client-Side TLS Certificate and Key for LDAP connections are usually only useful in Google Workspace configurations with "Secure LDAP." Both are required.',
'ldap_client_tls_key' => 'LDAP Client-Side TLS key',
'ldap_location' => 'LDAP Location',
'ldap_location_help' => 'The Ldap Location field should be used if <strong>an OU is not being used in the Base Bind DN.</strong> Leave this blank if an OU search is being used.',
'ldap_login_test_help' => 'Enter a valid LDAP username and password from the base DN you specified above to test whether your LDAP login is configured correctly. YOU MUST SAVE YOUR UPDATED LDAP SETTINGS FIRST.',
@@ -121,8 +121,8 @@ return [
'ldap_test' => 'Test LDAP',
'ldap_test_sync' => 'Test LDAP Synchronization',
'license' => 'Software License',
'load_remote_text' => 'Remote Scripts',
'load_remote_help_text' => 'This Snipe-IT install can load scripts from the outside world.',
'load_remote' => 'Use Gravatar',
'load_remote_help_text' => 'Uncheck this box if your install cannot load scripts from the outside internet. This will prevent Snipe-IT from trying load images from Gravatar.',
'login' => 'Login Attempts',
'login_attempt' => 'Login Attempt',
'login_ip' => 'IP Address',
+2
View File
@@ -459,6 +459,7 @@ return [
'no_autoassign_licenses_help' => 'Do not include user for bulk-assigning through the license UI or cli tools.',
'modal_confirm_generic' => 'Are you sure?',
'cannot_be_deleted' => 'This item cannot be deleted',
'cannot_be_edited' => 'This item cannot be edited.',
'undeployable_tooltip' => 'This item cannot be checked out. Check the quantity remaining.',
'serial_number' => 'Serial Number',
'item_notes' => ':item Notes',
@@ -501,5 +502,6 @@ return [
'action_source' => 'Action Source',
'or' => 'or',
'url' => 'URL',
'edit_fieldset' => 'Edit fieldset fields and options',
];
+1
View File
@@ -42,6 +42,7 @@ return [
'checkin_date' => 'Checkin Date:',
'checkout_date' => 'Checkout Date:',
'checkedout_from' => 'Checked out from',
'checkedin_from' => 'Checked in from',
'checked_into' => 'Checked into',
'click_on_the_link_accessory' => 'Please click on the link at the bottom to confirm that you have received the accessory.',
'click_on_the_link_asset' => 'Please click on the link at the bottom to confirm that you have received the asset.',
+4 -4
View File
@@ -21,7 +21,7 @@
<i class="fas fa-barcode" aria-hidden="true"></i>
</span>
<span class="hidden-xs hidden-sm">{{ trans('general.assets') }}
{!! (($company->assets) && ($company->assets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($company->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
{!! ($company->assets()->AssetsForShow()->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($company->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
</span>
</a>
@@ -33,7 +33,7 @@
<i class="far fa-save"></i>
</span>
<span class="hidden-xs hidden-sm">{{ trans('general.licenses') }}
{!! (($company->licenses) && ($company->licenses->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($company->licenses->count()).'</badge>' : '' !!}
{!! ($company->licenses->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($company->licenses->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -43,7 +43,7 @@
<span class="hidden-lg hidden-md">
<i class="far fa-keyboard"></i>
</span> <span class="hidden-xs hidden-sm">{{ trans('general.accessories') }}
{!! (($company->accessories) && ($company->accessories->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($company->accessories->count()).'</badge>' : '' !!}
{!! ($company->accessories->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($company->accessories->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -53,7 +53,7 @@
<span class="hidden-lg hidden-md">
<i class="fas fa-tint"></i></span>
<span class="hidden-xs hidden-sm">{{ trans('general.consumables') }}
{!! (($company->consumables) && ($company->consumables->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($company->consumables->count()).'</badge>' : '' !!}
{!! ($company->consumables->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($company->consumables->count()).'</badge>' : '' !!}
</span>
</a>
</li>
+1 -1
View File
@@ -279,7 +279,7 @@
</strong>
</div>
<div class="col-md-12">
{!! nl2br(e($component->notes)) !!}
{!! nl2br(Helper::parseEscapedMarkedownInline($component->notes)) !!}
</div>
</div>
@endif
@@ -77,7 +77,7 @@
@can('update', $custom_fieldset)
<form method="post" action="{{ route('fields.disassociate', [$field, $custom_fieldset->id]) }}">
@csrf
<button type="submit" class="btn btn-sm btn-danger">{{ trans('button.remove') }}</button>
<button type="submit" class="btn btn-sm btn-danger"><i class="fa fa-trash icon-white" aria-hidden="true"></i></button>
</form>
@endcan
</td>
@@ -90,35 +90,34 @@
<td colspan="8">
{{ Form::open(['route' =>
["fieldsets.associate",$custom_fieldset->id],
'class'=>'form-horizontal',
'class'=>'form-inline',
'id' => 'ordering']) }}
<div class="form-group col-md-4">
<div class="form-group">
<label for="field_id" class="sr-only">
{{ trans('admin/custom-field/general.add_field_to_fieldset')}}
</label>
{{ Form::select("field_id",$custom_fields_list,"",['aria-label'=>'field_id', 'class'=>'select2']) }}
{{ Form::select("field_id",$custom_fields_list,"",['aria-label'=>'field_id', 'class'=>'select2', 'style' => 'min-width:400px;']) }}
</div>
<div class="form-group col-md-2" style="vertical-align: middle;">
<label class="form-control">
{{ Form::checkbox('required', 'on', old('required'), array('aria-label'=>'required')) }}
{{ trans('admin/custom_fields/general.required') }}
</label>
</div>
<div class="form-group col-md-2" style="display: none;">
{{ Form::text('order', $maxid, array('class' => 'form-control col-sm-1 col-md-1', 'style'=> 'width: 80px; padding-;right: 10px;', 'aria-label'=>'order', 'maxlength'=>'3', 'size'=>'3')) }}
<div class="form-group" style="display: none;">
{{ Form::text('order', $maxid, array('aria-label'=>'order', 'maxlength'=>'3', 'size'=>'3')) }}
<label for="order">{{ trans('admin/custom_fields/general.order') }}</label>
</div>
<div class="form-group col-md-3">
<button type="submit" class="btn btn-primary"> {{ trans('general.save') }}</button>
<div class="checkbox-inline">
<label>
{{ Form::checkbox('required', 'on', old('required')) }}
<span style="padding-left: 10px;">{{ trans('admin/custom_fields/general.required') }}</span>
</label>
</div>
<span style="padding-left: 10px;">
<button type="submit" class="btn btn-primary"> {{ trans('general.save') }}</button>
</span>
{{ Form::close() }}
</td>
+13 -6
View File
@@ -73,7 +73,14 @@
<nobr>
@can('update', $fieldset)
<a href="{{ route('fieldsets.edit', $fieldset->id) }}" class="btn btn-warning btn-sm">
<a href="{{ route('fieldsets.show', ['fieldset' => $fieldset->id]) }}" data-tooltip="true" title="{{ trans('general.edit_fieldset') }}">
<button type="submit" class="btn btn-info btn-sm">
<i class="fa-regular fa-rectangle-list"></i>
</button>
</a>
<a href="{{ route('fieldsets.edit', $fieldset->id) }}" class="btn btn-warning btn-sm" data-tooltip="true" title="{{ trans('general.update') }}">
<i class="fas fa-pencil-alt" aria-hidden="true"></i>
<span class="sr-only">{{ trans('button.edit') }}</span>
</a>
@@ -82,9 +89,9 @@
@can('delete', $fieldset)
{{ Form::open(['route' => array('fieldsets.destroy', $fieldset->id), 'method' => 'delete','style' => 'display:inline-block']) }}
@if($fieldset->models->count() > 0)
<button type="submit" class="btn btn-danger btn-sm disabled" disabled><i class="fas fa-trash"></i></button>
<button type="submit" class="btn btn-danger btn-sm disabled" data-tooltip="true" title="{{ trans('general.cannot_be_deleted') }}" disabled><i class="fas fa-trash"></i></button>
@else
<button type="submit" class="btn btn-danger btn-sm"><i class="fas fa-trash"></i></button>
<button type="submit" class="btn btn-danger btn-sm" data-tooltip="true" title="{{ trans('general.delete') }}"><i class="fas fa-trash"></i></button>
@endif
{{ Form::close() }}
@endcan
@@ -188,7 +195,7 @@
<nobr>
{{ Form::open(array('route' => array('fields.destroy', $field->id), 'method' => 'delete', 'style' => 'display:inline-block')) }}
@can('update', $field)
<a href="{{ route('fields.edit', $field->id) }}" class="btn btn-warning btn-sm">
<a href="{{ route('fields.edit', $field->id) }}" class="btn btn-warning btn-sm" data-tooltip="true" title="{{ trans('general.update') }}">
<i class="fas fa-pencil-alt" aria-hidden="true"></i>
<span class="sr-only">{{ trans('button.edit') }}</span>
</a>
@@ -197,11 +204,11 @@
@can('delete', $field)
@if($field->fieldset->count()>0)
<button type="submit" class="btn btn-danger btn-sm disabled" disabled>
<button type="submit" class="btn btn-danger btn-sm disabled" data-tooltip="true" title="{{ trans('general.cannot_be_deleted') }}" disabled>
<i class="fas fa-trash" aria-hidden="true"></i>
<span class="sr-only">{{ trans('button.delete') }}</span></button>
@else
<button type="submit" class="btn btn-danger btn-sm">
<button type="submit" class="btn btn-danger btn-sm" data-tooltip="true" title="{{ trans('general.delete') }}">
<i class="fas fa-trash" aria-hidden="true"></i>
<span class="sr-only">{{ trans('button.delete') }}</span>
</button>
+2
View File
@@ -872,11 +872,13 @@
@can('update', $asset)
@if ($asset->deleted_at=='')
<div class="col-md-12" style="padding-top: 5px;">
<a href="{{ route('hardware.edit', $asset->id) }}" style="width: 100%;" class="btn btn-sm btn-primary hidden-print">
{{ trans('admin/hardware/general.edit') }}
</a>
</div>
@endif
@endcan
@can('create', $asset)
+7 -9
View File
@@ -140,17 +140,15 @@
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
@can('index', \App\Models\Asset::class)
<li aria-hidden="true"
{!! (Request::is('hardware*') ? ' class="active"' : '') !!} tabindex="-1">
<li aria-hidden="true"{!! (Request::is('hardware*') ? ' class="active"' : '') !!}>
<a href="{{ url('hardware') }}" accesskey="1" tabindex="-1">
<i class="fas fa-barcode fa-fw" aria-hidden="true"></i>
<i class="fas fa-barcode fa-fw"></i>
<span class="sr-only">{{ trans('general.assets') }}</span>
</a>
</li>
@endcan
@can('view', \App\Models\License::class)
<li aria-hidden="true"
{!! (Request::is('licenses*') ? ' class="active"' : '') !!} tabindex="-1">
<li aria-hidden="true"{!! (Request::is('licenses*') ? ' class="active"' : '') !!}>
<a href="{{ route('licenses.index') }}" accesskey="2" tabindex="-1">
<i class="far fa-save fa-fw"></i>
<span class="sr-only">{{ trans('general.licenses') }}</span>
@@ -158,8 +156,7 @@
</li>
@endcan
@can('index', \App\Models\Accessory::class)
<li aria-hidden="true"
{!! (Request::is('accessories*') ? ' class="active"' : '') !!} tabindex="-1">
<li aria-hidden="true"{!! (Request::is('accessories*') ? ' class="active"' : '') !!}>
<a href="{{ route('accessories.index') }}" accesskey="3" tabindex="-1">
<i class="far fa-keyboard fa-fw"></i>
<span class="sr-only">{{ trans('general.accessories') }}</span>
@@ -233,7 +230,8 @@
<li {!! (Request::is('accessories/create') ? 'class="active"' : '') !!}>
<a href="{{ route('accessories.create') }}" tabindex="-1">
<i class="far fa-keyboard fa-fw" aria-hidden="true"></i>
{{ trans('general.accessory') }}</a>
{{ trans('general.accessory') }}
</a>
</li>
@endcan
@can('create', \App\Models\Consumable::class)
@@ -982,7 +980,7 @@
container: 'body',
animation: true,
});
$('[data-toggle="popover"]').popover();
$('.select2 span').addClass('needsclick');
$('.select2 span').removeAttr('title');
@@ -133,8 +133,10 @@
<i class="fa-solid fa-list-check" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.import') }}</span>
</button>
<a href="#" wire:click="$set('activeFile',null)">
<button class="btn btn-sm btn-danger" wire:click="destroy({{ $currentFile->id }})">
<i class="fas fa-trash icon-white" aria-hidden="true"></i><span class="sr-only"></span></button>
</a>
</td>
</tr>
@@ -61,9 +61,9 @@
<div class="col-md-9 required" wire:ignore>
@if (Helper::isDemoMode())
{{ Form::select('webhook_selected', array('slack' => trans('admin/settings/general.slack'), 'general' => trans('admin/settings/general.general_webhook'), 'microsoft' => trans('admin/settings/general.ms_teams')), old('webhook_selected', $webhook_selected), array('class'=>'select2 form-control', 'aria-label' => 'webhook_selected', 'id' => 'select2', 'style'=>'width:100%', 'disabled')) }}
{{ Form::select('webhook_selected', array('slack' => trans('admin/settings/general.slack'), 'general' => trans('admin/settings/general.general_webhook'),'google' => trans('admin/settings/general.google_workspaces'), 'microsoft' => trans('admin/settings/general.ms_teams')), old('webhook_selected', $webhook_selected), array('class'=>'select2 form-control', 'aria-label' => 'webhook_selected', 'id' => 'select2', 'style'=>'width:100%', 'disabled')) }}
@else
{{ Form::select('webhook_selected', array('slack' => trans('admin/settings/general.slack'), 'general' => trans('admin/settings/general.general_webhook'), 'microsoft' => trans('admin/settings/general.ms_teams')), old('webhook_selected', $webhook_selected), array('class'=>'select2 form-control', 'aria-label' => 'webhook_selected', 'id' => 'select2', 'data-minimum-results-for-search' => '-1', 'style'=>'width:100%')) }}
{{ Form::select('webhook_selected', array('slack' => trans('admin/settings/general.slack'), 'general' => trans('admin/settings/general.general_webhook'),'google' => trans('admin/settings/general.google_workspaces'), 'microsoft' => trans('admin/settings/general.ms_teams')), old('webhook_selected', $webhook_selected), array('class'=>'select2 form-control', 'aria-label' => 'webhook_selected', 'id' => 'select2', 'data-minimum-results-for-search' => '-1', 'style'=>'width:100%')) }}
@endif
</div>
@@ -90,23 +90,25 @@
<!-- Webhook channel -->
<div class="form-group{{ $errors->has('webhook_channel') ? ' error' : '' }}">
<div class="col-md-2">
{{ Form::label('webhook_channel', trans('admin/settings/general.webhook_channel',['app' => $webhook_name ])) }}
</div>
<div class="col-md-9 required">
<input type="text" wire:model.lazy="webhook_channel" class="form-control" placeholder="#IT-Ops" value="{{ old('webhook_channel', $webhook_channel) }}"{{ Helper::isDemoMode() ? ' disabled' : ''}}>
@if($webhook_selected != 'microsoft' && $webhook_selected!= 'google')
<div class="form-group{{ $errors->has('webhook_channel') ? ' error' : '' }}">
<div class="col-md-2">
{{ Form::label('webhook_channel', trans('admin/settings/general.webhook_channel',['app' => $webhook_name ])) }}
</div>
<div class="col-md-9 required">
<input type="text" wire:model.lazy="webhook_channel" class="form-control" placeholder="#IT-Ops" value="{{ old('webhook_channel', $webhook_channel) }}"{{ Helper::isDemoMode() ? ' disabled' : ''}}>
{!! $errors->first('webhook_channel', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
{!! $errors->first('webhook_channel', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
</div>
@endif
@if (Helper::isDemoMode())
@include('partials.forms.demo-mode')
@endif
<!-- Webhook botname -->
@if($webhook_selected != 'microsoft')
@if($webhook_selected != 'microsoft' && $webhook_selected != 'google')
<div class="form-group{{ $errors->has('webhook_botname') ? ' error' : '' }}">
<div class="col-md-2">
{{ Form::label('webhook_botname', trans('admin/settings/general.webhook_botname',['app' => $webhook_name ])) }}
@@ -122,14 +124,11 @@
@endif
<!--Webhook Integration Test-->
@if($webhook_endpoint != null && $webhook_channel != null)
<div class="form-group">
<div class="col-md-offset-2 col-md-9">
@if($webhook_selected == "microsoft")
<a href="#" wire:click.prevent="msTeamTestWebhook"
@else
<a href="#" wire:click.prevent="testWebhook"
@endif
<a href="#" wire:click.prevent="{{$webhook_test}}"
class="btn btn-default btn-sm pull-left">
<i class="{{$webhook_icon}}" aria-hidden="true"></i>
{!! trans('admin/settings/general.webhook_test',['app' => ucwords($webhook_selected) ]) !!}
+7 -7
View File
@@ -25,7 +25,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.users') }}
{!! (($location->users) && ($location->users->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->users->count()).'</badge>' : '' !!}
{!! ($location->users->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($location->users->count()).'</badge>' : '' !!}
</span>
</a>
@@ -38,7 +38,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('admin/locations/message.current_location') }}
{!! (($location->assets) && ($location->assets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
{!! ($location->assets()->AssetsForShow()->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($location->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -51,7 +51,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('admin/hardware/form.default_location') }}
{!! (($location->rtd_assets) && ($location->rtd_assets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->rtd_assets()->AssetsForShow()->count()).'</badge>' : '' !!}
{!! ($location->rtd_assets()->AssetsForShow()->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($location->rtd_assets()->AssetsForShow()->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -63,7 +63,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('admin/locations/message.assigned_assets') }}
{!! (($location->rtd_assets) && ($location->assignedAssets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->assignedAssets()->AssetsForShow()->count()).'</badge>' : '' !!}
{!! ($location->assignedAssets()->AssetsForShow()->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($location->assignedAssets()->AssetsForShow()->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -76,7 +76,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.accessories') }}
{!! (($location->accessories) && ($location->accessories->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->accessories->count()).'</badge>' : '' !!}
{!! ($location->accessories->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($location->accessories->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -88,7 +88,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.consumables') }}
{!! (($location->consumables) && ($location->consumables->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->consumables->count()).'</badge>' : '' !!}
{!! ($location->consumables->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($location->consumables->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -100,7 +100,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.components') }}
{!! (($location->components) && ($location->components->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($location->components->count()).'</badge>' : '' !!}
{!! ($location->components->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($location->components->count()).'</badge>' : '' !!}
</span>
</a>
</li>
+4 -4
View File
@@ -41,7 +41,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.assets') }}
{!! (($manufacturer->assets) && ($manufacturer->assets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
{!! ($manufacturer->assets()->AssetsForShow()->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
</span>
</a>
@@ -55,7 +55,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.licenses') }}
{!! (($manufacturer->licenses) && ($manufacturer->licenses->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->licenses->count()).'</badge>' : '' !!}
{!! ($manufacturer->licenses->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->licenses->count()).'</badge>' : '' !!}
</span>
</a>
@@ -68,7 +68,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.accessories') }}
{!! (($manufacturer->accessories) && ($manufacturer->accessories->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->accessories->count()).'</badge>' : '' !!}
{!! ($manufacturer->accessories->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->accessories->count()).'</badge>' : '' !!}
</span>
</a>
@@ -81,7 +81,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.consumables') }}
{!! (($manufacturer->consumables) && ($manufacturer->consumables->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->consumables->count()).'</badge>' : '' !!}
{!! ($manufacturer->consumables->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->consumables->count()).'</badge>' : '' !!}
</span>
</a>
+2 -2
View File
@@ -43,7 +43,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.assets') }}
{!! (($model->assets_count) && ($model->assets_count > 0 )) ? '<badge class="badge badge-secondary">'.number_format($model->assets_count).'</badge>' : '' !!}
{!! ($model->assets_count > 0 ) ? '<badge class="badge badge-secondary">'.number_format($model->assets_count).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -309,7 +309,7 @@
@if ($model->notes)
<li>
{{ trans('general.notes') }}:
{{ $model->notes }}
{!! nl2br(Helper::parseEscapedMarkedownInline($model->notes)) !!}
</li>
@endif
@@ -296,6 +296,10 @@
if ((row.available_actions) && (row.available_actions.update === true)) {
actions += '<a href="{{ config('app.url') }}/' + dest + '/' + row.id + '/edit" class="actions btn btn-sm btn-warning" data-tooltip="true" title="{{ trans('general.update') }}"><i class="fas fa-pencil-alt" aria-hidden="true"></i><span class="sr-only">{{ trans('general.update') }}</span></a>&nbsp;';
} else {
if ((row.available_actions) && (row.available_actions.update != true)) {
actions += '<span data-tooltip="true" title="{{ trans('general.cannot_be_edited') }}"><a class="btn btn-warning btn-sm disabled" onClick="return false;"><i class="fas fa-pencil-alt"></i></a></span>&nbsp;';
}
}
if ((row.available_actions) && (row.available_actions.delete === true)) {
@@ -623,6 +627,9 @@
function assetTagLinkFormatter(value, row) {
if ((row.asset) && (row.asset.id)) {
if (row.asset.deleted_at!='') {
return '<span style="white-space: nowrap;"><i class="fas fa-times text-danger"></i><span class="sr-only">deleted</span> <del><a href="{{ config('app.url') }}/hardware/' + row.asset.id + '" data-tooltip="true" title="{{ trans('admin/hardware/general.deleted') }}">' + row.asset.asset_tag + '</a></del></span>';
}
return '<a href="{{ config('app.url') }}/hardware/' + row.asset.id + '">' + row.asset.asset_tag + '</a>';
}
return '';
@@ -640,7 +647,17 @@
if ((row.asset) && (row.asset.name)) {
return '<a href="{{ config('app.url') }}/hardware/' + row.asset.id + '">' + row.asset.name + '</a>';
}
}
function assetSerialLinkFormatter(value, row) {
if ((row.asset) && (row.asset.serial)) {
if (row.asset.deleted_at!='') {
return '<span style="white-space: nowrap;"><i class="fas fa-times text-danger"></i><span class="sr-only">deleted</span> <del><a href="{{ config('app.url') }}/hardware/' + row.asset.id + '" data-tooltip="true" title="{{ trans('admin/hardware/general.deleted') }}">' + row.asset.serial + '</a></del></span>';
}
return '<a href="{{ config('app.url') }}/hardware/' + row.asset.id + '">' + row.asset.serial + '</a>';
}
return '';
}
function trueFalseFormatter(value) {
@@ -36,7 +36,7 @@
{!! $errors->first('image', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
<div class="col-md-4 col-md-offset-3" aria-hidden="true">
<img id="uploadFile-imagePreview" style="max-width: 300px; display: none;" alt="{{ trans('partials/forms/general.alt_uploaded_image_thumbnail') }}">
<img id="uploadFile-imagePreview" style="max-width: 300px; display: none;" alt="{{ trans('general.alt_uploaded_image_thumbnail') }}">
</div>
</div>
+21 -1
View File
@@ -128,6 +128,25 @@
</div>
</div>
<!-- Load gravatar -->
<div class="form-group {{ $errors->has('load_remote') ? 'error' : '' }}">
<div class="col-md-3">
<strong>{{ trans('admin/settings/general.load_remote') }}</strong>
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('load_remote', '1', old('load_remote', $setting->load_remote)) }}
{{ trans('general.yes') }}
{!! $errors->first('load_remote', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</label>
<p class="help-block">
{{ trans('admin/settings/general.load_remote_help_text') }}
</p>
</div>
</div>
<!-- unique serial -->
<div class="form-group">
<div class="col-md-3">
@@ -137,8 +156,9 @@
<label class="form-control">
{{ Form::checkbox('unique_serial', '1', Request::old('unique_serial', $setting->unique_serial),array('class' => 'minimal')) }}
{{ trans('general.yes') }}
{!! $errors->first('unique_serial', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</label>
{!! $errors->first('unique_serial', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
<p class="help-block">
{{ trans('admin/settings/general.unique_serial_help_text') }}
</p>
+6 -6
View File
@@ -35,7 +35,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.assets') }}
{!! (($supplier->assets) && ($supplier->assets()->AssetsForShow()->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($supplier->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
{!! ($supplier->assets()->AssetsForShow()->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($supplier->assets()->AssetsForShow()->count()).'</badge>' : '' !!}
</span>
</a>
@@ -48,7 +48,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.accessories') }}
{!! (($supplier->accessories) && ($supplier->accessories->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($supplier->accessories->count()).'</badge>' : '' !!}
{!! ($supplier->accessories->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($supplier->accessories->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -60,7 +60,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.licenses') }}
{!! (($supplier->licenses) && ($supplier->licenses->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($supplier->licenses->count()).'</badge>' : '' !!}
{!! ($supplier->licenses->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($supplier->licenses->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -72,7 +72,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.components') }}
{!! (($supplier->components) && ($supplier->components->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($supplier->components->count()).'</badge>' : '' !!}
{!! ($supplier->components->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($supplier->components->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -84,7 +84,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.consumables') }}
{!! (($supplier->consumables) && ($supplier->consumables->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($supplier->consumables->count()).'</badge>' : '' !!}
{!! ($supplier->consumables->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($supplier->consumables->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -96,7 +96,7 @@
</span>
<span class="hidden-xs hidden-sm">
{{ trans('admin/asset_maintenances/general.asset_maintenances') }}
{!! (($supplier->asset_maintenances) && ($supplier->asset_maintenances->count() > 0 )) ? '<badge class="badge badge-secondary">'.number_format($supplier->asset_maintenances->count()).'</badge>' : '' !!}
{!! ($supplier->asset_maintenances->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($supplier->asset_maintenances->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -131,14 +131,13 @@
$(":submit").attr("disabled", "disabled");
$("[name='status_id']").on('select2:select', function (e) {
if (e.params.data.id != ""){
console.log(e.params.data.id);
$(":submit").removeAttr("disabled");
}
else {
$(":submit").attr("disabled", "disabled");
}
});
$("[name='status_id']").on('select2:select', function (e) {
if (e.params.data.id != "") {
console.log(e.params.data.id);
$(":submit").removeAttr("disabled");
} else {
$(":submit").attr("disabled", "disabled");
}
});
</script>
@stop
+2
View File
@@ -0,0 +1,2 @@
Item Name,Purchase Date,Purchase Cost,Location,Company,Order Number,Serial number,Category,Quantity
RTX 4080,2024-01-01,5000.00,Austin,Grokability,2790,123456789,GPU,10
1 Item Name Purchase Date Purchase Cost Location Company Order Number Serial number Category Quantity
2 RTX 4080 2024-01-01 5000.00 Austin Grokability 2790 123456789 GPU 10
@@ -0,0 +1,96 @@
<?php
namespace Tests\Feature\Api\Accessories;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\User;
use App\Notifications\CheckoutAccessoryNotification;
use Illuminate\Support\Facades\Notification;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class AccessoryCheckoutTest extends TestCase
{
use InteractsWithSettings;
public function testCheckingOutAccessoryRequiresCorrectPermission()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.accessories.checkout', Accessory::factory()->create()))
->assertForbidden();
}
public function testValidationWhenCheckingOutAccessory()
{
$this->actingAsForApi(User::factory()->checkoutAccessories()->create())
->postJson(route('api.accessories.checkout', Accessory::factory()->create()), [
// missing assigned_to
])
->assertStatusMessageIs('error');
}
public function testAccessoryMustBeAvailableWhenCheckingOut()
{
$this->actingAsForApi(User::factory()->checkoutAccessories()->create())
->postJson(route('api.accessories.checkout', Accessory::factory()->withoutItemsRemaining()->create()), [
'assigned_to' => User::factory()->create()->id,
])
->assertStatusMessageIs('error');
}
public function testAccessoryCanBeCheckedOut()
{
$accessory = Accessory::factory()->create();
$user = User::factory()->create();
$this->actingAsForApi(User::factory()->checkoutAccessories()->create())
->postJson(route('api.accessories.checkout', $accessory), [
'assigned_to' => $user->id,
]);
$this->assertTrue($accessory->users->contains($user));
}
public function testUserSentNotificationUponCheckout()
{
Notification::fake();
$accessory = Accessory::factory()->requiringAcceptance()->create();
$user = User::factory()->create();
$this->actingAsForApi(User::factory()->checkoutAccessories()->create())
->postJson(route('api.accessories.checkout', $accessory), [
'assigned_to' => $user->id,
]);
Notification::assertSentTo($user, CheckoutAccessoryNotification::class);
}
public function testActionLogCreatedUponCheckout()
{
$accessory = Accessory::factory()->create();
$actor = User::factory()->checkoutAccessories()->create();
$user = User::factory()->create();
$this->actingAsForApi($actor)
->postJson(route('api.accessories.checkout', $accessory), [
'assigned_to' => $user->id,
'note' => 'oh hi there',
]);
$this->assertEquals(
1,
Actionlog::where([
'action_type' => 'checkout',
'target_id' => $user->id,
'target_type' => User::class,
'item_id' => $accessory->id,
'item_type' => Accessory::class,
'user_id' => $actor->id,
'note' => 'oh hi there',
])->count(),
'Log entry either does not exist or there are more than expected'
);
}
}
@@ -0,0 +1,96 @@
<?php
namespace Tests\Feature\Api\Consumables;
use App\Models\Actionlog;
use App\Models\Consumable;
use App\Models\User;
use App\Notifications\CheckoutConsumableNotification;
use Illuminate\Support\Facades\Notification;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class ConsumableCheckoutTest extends TestCase
{
use InteractsWithSettings;
public function testCheckingOutConsumableRequiresCorrectPermission()
{
$this->actingAsForApi(User::factory()->create())
->postJson(route('api.consumables.checkout', Consumable::factory()->create()))
->assertForbidden();
}
public function testValidationWhenCheckingOutConsumable()
{
$this->actingAsForApi(User::factory()->checkoutConsumables()->create())
->postJson(route('api.consumables.checkout', Consumable::factory()->create()), [
// missing assigned_to
])
->assertStatusMessageIs('error');
}
public function testConsumableMustBeAvailableWhenCheckingOut()
{
$this->actingAsForApi(User::factory()->checkoutConsumables()->create())
->postJson(route('api.consumables.checkout', Consumable::factory()->withoutItemsRemaining()->create()), [
'assigned_to' => User::factory()->create()->id,
])
->assertStatusMessageIs('error');
}
public function testConsumableCanBeCheckedOut()
{
$consumable = Consumable::factory()->create();
$user = User::factory()->create();
$this->actingAsForApi(User::factory()->checkoutConsumables()->create())
->postJson(route('api.consumables.checkout', $consumable), [
'assigned_to' => $user->id,
]);
$this->assertTrue($user->consumables->contains($consumable));
}
public function testUserSentNotificationUponCheckout()
{
Notification::fake();
$consumable = Consumable::factory()->requiringAcceptance()->create();
$user = User::factory()->create();
$this->actingAsForApi(User::factory()->checkoutConsumables()->create())
->postJson(route('api.consumables.checkout', $consumable), [
'assigned_to' => $user->id,
]);
Notification::assertSentTo($user, CheckoutConsumableNotification::class);
}
public function testActionLogCreatedUponCheckout()
{$consumable = Consumable::factory()->create();
$actor = User::factory()->checkoutConsumables()->create();
$user = User::factory()->create();
$this->actingAsForApi($actor)
->postJson(route('api.consumables.checkout', $consumable), [
'assigned_to' => $user->id,
'note' => 'oh hi there',
]);
$this->assertEquals(
1,
Actionlog::where([
'action_type' => 'checkout',
'target_id' => $user->id,
'target_type' => User::class,
'item_id' => $consumable->id,
'item_type' => Consumable::class,
'user_id' => $actor->id,
'note' => 'oh hi there',
])->count(),
'Log entry either does not exist or there are more than expected'
);
}
}
@@ -0,0 +1,91 @@
<?php
namespace Tests\Feature\Checkins;
use App\Events\CheckoutableCheckedIn;
use App\Models\Accessory;
use App\Models\User;
use App\Notifications\CheckinAccessoryNotification;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class AccessoryCheckinTest extends TestCase
{
use InteractsWithSettings;
public function testCheckingInAccessoryRequiresCorrectPermission()
{
$accessory = Accessory::factory()->checkedOutToUser()->create();
$this->actingAs(User::factory()->create())
->post(route('accessories.checkin.store', $accessory->users->first()->pivot->id))
->assertForbidden();
}
public function testAccessoryCanBeCheckedIn()
{
Event::fake([CheckoutableCheckedIn::class]);
$user = User::factory()->create();
$accessory = Accessory::factory()->checkedOutToUser($user)->create();
$this->assertTrue($accessory->users->contains($user));
$this->actingAs(User::factory()->checkinAccessories()->create())
->post(route('accessories.checkin.store', $accessory->users->first()->pivot->id));
$this->assertFalse($accessory->fresh()->users->contains($user));
Event::assertDispatched(CheckoutableCheckedIn::class, 1);
}
public function testEmailSentToUserIfSettingEnabled()
{
Notification::fake();
$user = User::factory()->create();
$accessory = Accessory::factory()->checkedOutToUser($user)->create();
$accessory->category->update(['checkin_email' => true]);
event(new CheckoutableCheckedIn(
$accessory,
$user,
User::factory()->checkinAccessories()->create(),
'',
));
Notification::assertSentTo(
[$user],
function (CheckinAccessoryNotification $notification, $channels) {
return in_array('mail', $channels);
},
);
}
public function testEmailNotSentToUserIfSettingDisabled()
{
Notification::fake();
$user = User::factory()->create();
$accessory = Accessory::factory()->checkedOutToUser($user)->create();
$accessory->category->update(['checkin_email' => false]);
event(new CheckoutableCheckedIn(
$accessory,
$user,
User::factory()->checkinAccessories()->create(),
'',
));
Notification::assertNotSentTo(
[$user],
function (CheckinAccessoryNotification $notification, $channels) {
return in_array('mail', $channels);
},
);
}
}
@@ -0,0 +1,96 @@
<?php
namespace Tests\Feature\Checkouts;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\User;
use App\Notifications\CheckoutAccessoryNotification;
use Illuminate\Support\Facades\Notification;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class AccessoryCheckoutTest extends TestCase
{
use InteractsWithSettings;
public function testCheckingOutAccessoryRequiresCorrectPermission()
{
$this->actingAs(User::factory()->create())
->post(route('accessories.checkout.store', Accessory::factory()->create()))
->assertForbidden();
}
public function testValidationWhenCheckingOutAccessory()
{
$this->actingAs(User::factory()->checkoutAccessories()->create())
->post(route('accessories.checkout.store', Accessory::factory()->create()), [
// missing assigned_to
])
->assertSessionHas('error');
}
public function testAccessoryMustBeAvailableWhenCheckingOut()
{
$this->actingAs(User::factory()->checkoutAccessories()->create())
->post(route('accessories.checkout.store', Accessory::factory()->withoutItemsRemaining()->create()), [
'assigned_to' => User::factory()->create()->id,
])
->assertSessionHas('error');
}
public function testAccessoryCanBeCheckedOut()
{
$accessory = Accessory::factory()->create();
$user = User::factory()->create();
$this->actingAs(User::factory()->checkoutAccessories()->create())
->post(route('accessories.checkout.store', $accessory), [
'assigned_to' => $user->id,
]);
$this->assertTrue($accessory->users->contains($user));
}
public function testUserSentNotificationUponCheckout()
{
Notification::fake();
$accessory = Accessory::factory()->requiringAcceptance()->create();
$user = User::factory()->create();
$this->actingAs(User::factory()->checkoutAccessories()->create())
->post(route('accessories.checkout.store', $accessory), [
'assigned_to' => $user->id,
]);
Notification::assertSentTo($user, CheckoutAccessoryNotification::class);
}
public function testActionLogCreatedUponCheckout()
{
$accessory = Accessory::factory()->create();
$actor = User::factory()->checkoutAccessories()->create();
$user = User::factory()->create();
$this->actingAs($actor)
->post(route('accessories.checkout.store', $accessory), [
'assigned_to' => $user->id,
'note' => 'oh hi there',
]);
$this->assertEquals(
1,
Actionlog::where([
'action_type' => 'checkout',
'target_id' => $user->id,
'target_type' => User::class,
'item_id' => $accessory->id,
'item_type' => Accessory::class,
'user_id' => $actor->id,
'note' => 'oh hi there',
])->count(),
'Log entry either does not exist or there are more than expected'
);
}
}
@@ -0,0 +1,96 @@
<?php
namespace Tests\Feature\Checkouts;
use App\Models\Actionlog;
use App\Models\Consumable;
use App\Models\User;
use App\Notifications\CheckoutConsumableNotification;
use Illuminate\Support\Facades\Notification;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
class ConsumableCheckoutTest extends TestCase
{
use InteractsWithSettings;
public function testCheckingOutConsumableRequiresCorrectPermission()
{
$this->actingAs(User::factory()->create())
->post(route('consumables.checkout.store', Consumable::factory()->create()))
->assertForbidden();
}
public function testValidationWhenCheckingOutConsumable()
{
$this->actingAs(User::factory()->checkoutConsumables()->create())
->post(route('consumables.checkout.store', Consumable::factory()->create()), [
// missing assigned_to
])
->assertSessionHas('error');
}
public function testConsumableMustBeAvailableWhenCheckingOut()
{
$this->actingAs(User::factory()->checkoutConsumables()->create())
->post(route('consumables.checkout.store', Consumable::factory()->withoutItemsRemaining()->create()), [
'assigned_to' => User::factory()->create()->id,
])
->assertSessionHas('error');
}
public function testConsumableCanBeCheckedOut()
{
$consumable = Consumable::factory()->create();
$user = User::factory()->create();
$this->actingAs(User::factory()->checkoutConsumables()->create())
->post(route('consumables.checkout.store', $consumable), [
'assigned_to' => $user->id,
]);
$this->assertTrue($user->consumables->contains($consumable));
}
public function testUserSentNotificationUponCheckout()
{
Notification::fake();
$consumable = Consumable::factory()->create();
$user = User::factory()->create();
$this->actingAs(User::factory()->checkoutConsumables()->create())
->post(route('consumables.checkout.store', $consumable), [
'assigned_to' => $user->id,
]);
Notification::assertSentTo($user, CheckoutConsumableNotification::class);
}
public function testActionLogCreatedUponCheckout()
{
$consumable = Consumable::factory()->create();
$actor = User::factory()->checkoutConsumables()->create();
$user = User::factory()->create();
$this->actingAs($actor)
->post(route('consumables.checkout.store', $consumable), [
'assigned_to' => $user->id,
'note' => 'oh hi there',
]);
$this->assertEquals(
1,
Actionlog::where([
'action_type' => 'checkout',
'target_id' => $user->id,
'target_type' => User::class,
'item_id' => $consumable->id,
'item_type' => Consumable::class,
'user_id' => $actor->id,
'note' => 'oh hi there',
])->count(),
'Log entry either does not exist or there are more than expected'
);
}
}
@@ -22,7 +22,7 @@ class AccessoryWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedOut(
Accessory::factory()->appleBtKeyboard()->create(),
@@ -60,7 +60,7 @@ class AccessoryWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedIn(
Accessory::factory()->appleBtKeyboard()->create(),
@@ -33,7 +33,7 @@ class AssetWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedOut(
$this->createAsset(),
@@ -73,7 +73,7 @@ class AssetWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedIn(
$this->createAsset(),
@@ -20,7 +20,7 @@ class ComponentWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedOut(
Component::factory()->ramCrucial8()->create(),
@@ -36,7 +36,7 @@ class ComponentWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedIn(
Component::factory()->ramCrucial8()->create(),
@@ -20,7 +20,7 @@ class ConsumableWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedOut(
Consumable::factory()->cardstock()->create(),
@@ -32,7 +32,7 @@ class LicenseWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedOut(
LicenseSeat::factory()->create(),
@@ -72,7 +72,7 @@ class LicenseWebhookTest extends TestCase
{
Notification::fake();
$this->settings->enableWebhook();
$this->settings->enableSlackWebhook();
event(new CheckoutableCheckedIn(
LicenseSeat::factory()->create(),
+3 -1
View File
@@ -39,9 +39,10 @@ class Settings
return $this->update(['full_multiple_companies_support' => 0]);
}
public function enableWebhook(): Settings
public function enableSlackWebhook(): Settings
{
return $this->update([
'webhook_selected' => 'slack',
'webhook_botname' => 'SnipeBot5000',
'webhook_endpoint' => 'https://hooks.slack.com/services/NZ59/Q446/672N',
'webhook_channel' => '#it',
@@ -51,6 +52,7 @@ class Settings
public function disableWebhook(): Settings
{
return $this->update([
'webhook_selected' => '',
'webhook_botname' => '',
'webhook_endpoint' => '',
'webhook_channel' => '',
+3
View File
@@ -7,6 +7,9 @@ use Exception;
use Tests\Support\InteractsWithSettings;
use Tests\TestCase;
/**
* @group ldap
*/
class LdapTest extends TestCase
{
use InteractsWithSettings;

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