Compare commits

...

230 Commits

Author SHA1 Message Date
Brady Wetherington
bf6c48d695 Clean up the fieldset experience for custom fields for users 2023-09-21 14:56:29 +01:00
Brady Wetherington
8428b2a04a Merge branch 'has_custom_fields_trait_rebase' into custom_fields_on_user 2023-09-18 13:45:35 +01:00
Brady Wetherington
71e745d966 Clean up this migration so it runs forwards and backwards OK 2023-09-14 21:14:26 +01:00
Brady Wetherington
a3a786f2af Merge branch 'develop' into has_custom_fields_trait_rebase 2023-09-14 20:27:06 +01:00
snipe
f6a11ac0ed Merge pull request #13586 from inietov/fixes/call_to_member_function_checkedOutToUser
Fixed Call to a member function checkedOutToUser() on null [rollbar-3598]
2023-09-12 08:35:30 +01:00
Ivan Nieto Vivanco
9cbd2d032c Add a boolean variable and condition to handle the call to checkedOutToUser() method 2023-09-11 17:40:59 -06:00
snipe
a49d3fe131 Merge pull request #13526 from Godmartinz/asset-model-notifs
Added threshold notifications and min qty for Asset models
2023-09-11 17:55:34 +01:00
Godfrey Martinez
a26b96185b Merge branch 'develop' into asset-model-notifs 2023-09-11 09:37:22 -07:00
Godfrey M
0eb50ceb3d removes commented code 2023-09-11 09:36:38 -07:00
snipe
dc74fb133f Merge pull request #13577 from marcusmoore/fixes/improve-testcase
Fixed failing test
2023-09-08 07:08:21 +01:00
Marcus Moore
093bf57448 Update assertion and add failure messages 2023-09-07 16:42:49 -07:00
snipe
06836663c8 Merge pull request #13575 from marcusmoore/fixes/improve-api-messaging
Added validation around department_id in API patch request
2023-09-07 21:36:20 +01:00
Marcus Moore
c6c1c64c1e Remove todo 2023-09-07 13:30:05 -07:00
Marcus Moore
4caa501996 Relax property type check 2023-09-07 13:28:32 -07:00
Marcus Moore
a7a70f6981 Test permissions update 2023-09-07 13:21:27 -07:00
Marcus Moore
39ff575ac1 Remove unused test cases 2023-09-07 13:16:08 -07:00
snipe
02c187b0a0 Merge pull request #13566 from snipe/features/13562_add_inline_file_link
Fixed #13562 - Added inline file link
2023-09-07 20:50:13 +01:00
snipe
7f892bf5ef Merge pull request #13574 from inietov/fixes/unaccepted_assets_report_incorrect
Fixed Unaccepted Assets report has incorrect people [freshdesk-37808]
2023-09-07 20:49:40 +01:00
Marcus Moore
56e6205667 Formatting 2023-09-07 12:45:03 -07:00
Ivan Nieto Vivanco
abd2ed3b81 Filter unaccepted assets that are not assigned to users 2023-09-07 13:39:16 -06:00
Marcus Moore
899c2eb19b Implement test case 2023-09-07 12:34:50 -07:00
snipe
894c34ff4f Update to only use relative paths
Signed-off-by: snipe <snipe@snipe.net>
2023-09-07 20:22:14 +01:00
snipe
b7c2b9374c Merge pull request #13573 from snipe/upgrade_scim_server
Upgrade to new branch for our fork of laravel-scim-server
2023-09-07 20:07:59 +01:00
Brady Wetherington
3184f795c2 Upgrade to new branch for our fork of laravel-scim-server 2023-09-07 18:29:23 +01:00
snipe
67cad9c751 Merge pull request #13571 from inietov/fixes/require_acceptance_on_null
Fixed Attempt to read property require_acceptance on null [rollbar-3557]
2023-09-07 18:11:42 +01:00
snipe
ec5238ff06 Merge pull request #13548 from marcusmoore/update-testing-documentation
Updated testing documentation
2023-09-07 11:25:53 +01:00
Marcus Moore
1c3c36f2a0 Begin to implement patch test 2023-09-06 16:14:14 -07:00
Ivan Nieto Vivanco
1509c512a5 Add guard clauses around some License and LicenseSeat models functions 2023-09-06 11:54:11 -06:00
snipe
e920199626 Merge pull request #13567 from johnson-yi/fixes/add_default_location_to_checkin_actionlog
Add default location to tracked changes on checkin actionlog
2023-09-06 09:37:04 +01:00
johnson-yi
5897f4d6d9 Add rtd_location_id to tracked changes on checkin actionlog 2023-09-06 00:36:48 +00:00
Brady Wetherington
0086adab86 Refactor out common code for 'custom fields view' partial 2023-09-05 20:45:43 +01:00
snipe
a67888f3d3 Merge pull request #13547 from inietov/fixes/Attempt_to_read_property_asset_tag
Fixed ErrorException: Attempt to read property "asset_tag" on null (rollbar #3541)
2023-09-05 20:26:21 +01:00
Ivan Nieto Vivanco
91b1cc7121 Move the early return to the controller instead of the Label model 2023-09-05 13:23:51 -06:00
Brady Wetherington
0f91e898fd Remove some code duplication 2023-09-05 19:18:04 +01:00
snipe
d6dd332b09 Merge pull request #13557 from johnson-yi/fixes/add_changes_to_checkinout_actionlog
Added/Fixed: track changes on asset checkin/out
2023-09-05 19:08:49 +01:00
snipe
65a76c599c Added inline=true to image preview modal
Signed-off-by: snipe <snipe@snipe.net>
2023-09-05 18:35:07 +01:00
snipe
f53db8ba75 Fixed #13562 - allow inline view for uploaded files
Signed-off-by: snipe <snipe@snipe.net>
2023-09-05 18:28:01 +01:00
snipe
e486fe2794 Removed soft-delete query
Signed-off-by: snipe <snipe@snipe.net>
2023-09-05 16:10:20 +01:00
snipe
26452a8a29 Merge pull request #13565 from snipe/fixes/asset_history_500_on_hard_deleted_models
Account for hard-deleted models, suppliers
2023-09-05 16:06:40 +01:00
snipe
f85df6bb8c Use same method of accessing companies as others
Signed-off-by: snipe <snipe@snipe.net>
2023-09-05 16:02:28 +01:00
Brady Wetherington
636da2ab5e Wiring up custom fields for users - still some big UI challenges tho 2023-09-05 16:01:53 +01:00
snipe
2acf2b880e Account for hard-deleted models, suppliers
Signed-off-by: snipe <snipe@snipe.net>
2023-09-05 15:58:21 +01:00
snipe
b17af38d8e Merge pull request #12901 from Godmartinz/user_total_cost
Added users total cost of assets to user profile
2023-09-05 13:52:34 +01:00
snipe
a62e2f092b Merge pull request #13498 from marcusmoore/feature/add-checkin-date-range-filter-to-custom-asset-report
Added last check in column and filter to custom asset report
2023-09-05 13:22:15 +01:00
Johnson Yi
09f7b3debe Track changes on asset checkin/out 2023-09-01 13:12:07 +10:00
Brady Wetherington
4f182c0a50 Got a chunk of Custom Fields for Users worked out, still needs cleanup 2023-08-31 19:34:25 +01:00
Marcus Moore
da7d6f6f77 Merge branch 'develop' into update-testing-documentation 2023-08-31 10:36:06 -07:00
snipe
f08cef8664 Merge pull request #13556 from snipe/feature/sc-23684
Added model name and number to dynamic url
2023-08-31 18:23:33 +01:00
snipe
e192cbbbe1 Merge pull request #13550 from marcusmoore/remove-dusk
Removed Dusk
2023-08-31 18:06:04 +01:00
snipe
83b178f5b2 Added model name and number to dynamic url
Signed-off-by: snipe <snipe@snipe.net>
2023-08-31 18:03:32 +01:00
snipe
c51574099f Merge pull request #13507 from Godmartinz/ignore_encrypt_and_other_changes
Added eager loading for `changedinfo` and removed encrypted values from Asset history
2023-08-31 13:04:39 +01:00
snipe
9a0f691f05 Merge pull request #13546 from snipe/fixes/make_boolean_user_fields_more_consistant
Don’t cast as boolean, do validate as boolean for User validation
2023-08-31 12:01:20 +01:00
Marcus Moore
a799659610 Scaffold tests and add context 2023-08-30 17:33:23 -07:00
Marcus Moore
999605f832 Add failing test 2023-08-30 17:10:50 -07:00
Marcus Moore
5828d29952 Remove Dusk 2023-08-30 16:43:18 -07:00
Marcus Moore
417b2c8331 Update testing documentation 2023-08-30 11:27:26 -07:00
snipe
663faffcc1 Un-cast byod, validate as boolean
Signed-off-by: snipe <snipe@snipe.net>
2023-08-30 19:13:55 +01:00
snipe
fb455be406 Added tests
Signed-off-by: snipe <snipe@snipe.net>
2023-08-30 19:13:38 +01:00
Brady Wetherington
a384d0173a Merge branch 'snipeit_v7_laravel10' into has_custom_fields_trait_rebase 2023-08-30 18:33:17 +01:00
Ivan Nieto Vivanco
085a993340 Early return if no asset is found 2023-08-30 11:05:58 -06:00
Marcus Moore
07c3fe1fce Update assertions to account for type change 2023-08-30 09:42:53 -07:00
snipe
2a93c38830 Don’t cast as boolean, validate as boolean
Signed-off-by: snipe <snipe@snipe.net>
2023-08-30 16:40:28 +01:00
snipe
c0cbdb1fc4 Fixed bug introduced in #13528
Signed-off-by: snipe <snipe@snipe.net>
2023-08-30 15:48:35 +01:00
snipe
5cf8c36698 Updated assets
Signed-off-by: snipe <snipe@snipe.net>
2023-08-30 15:43:06 +01:00
snipe
1bcc74f156 Upgrade @fortawesome/fontawesome-free from 6.4.0 to 6.4.2 #13527
Signed-off-by: snipe <snipe@snipe.net>
2023-08-30 15:41:11 +01:00
snipe
9ebcde4472 Upgrade less from 4.1.2 to 4.2.0 #13534
Signed-off-by: snipe <snipe@snipe.net>
2023-08-30 15:40:56 +01:00
snipe
fbc04cfd47 Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2023-08-30 15:07:38 +01:00
snipe
b109ee281a Merge pull request #13544 from marcusmoore/bug/sc-23675
Fixed user search not adhering to company scoping
2023-08-30 08:54:41 +01:00
Marcus Moore
806ab2cb9d Ensure users are scoped by company in index method 2023-08-29 16:17:29 -07:00
Marcus Moore
aafa1ab70e Add failing test 2023-08-29 16:15:13 -07:00
snipe
22d136df46 Merge pull request #13528 from inietov/fixes/cloning_model_blanks_fieldset
Fixed #12548 Cloning a model blanks fieldset of new one
2023-08-29 19:14:38 +01:00
Godfrey Martinez
0ac5d4d582 Merge pull request #8 from Godmartinz/fix-companyable
Fix companyable
2023-08-28 19:38:27 -07:00
Godfrey Martinez
d86c63cf23 Merge branch 'ignore_encrypt_and_other_changes' into fix-companyable 2023-08-28 19:38:16 -07:00
Godfrey M
74f45a4473 reworks company queries for asset history 2023-08-28 19:35:46 -07:00
Ivan Nieto Vivanco
4e4ba38038 Pass the model_id variable in the controller and get it only if we are cloning the asset model 2023-08-28 16:23:26 -06:00
snipe
367484a766 Merge pull request #13537 from snipe/fixes/13500_prevent_autocomplete_on_ldap_password
Fixed #13500 - Try to prevent the browser from pre-filling the LDAP password
2023-08-28 20:42:58 +01:00
snipe
81365ef911 Merge pull request #13518 from marcusmoore/chore/tests-via-github-actions
Run tests for PHP 7.4, 8.0, and 8.1.1 via GitHub Actions
2023-08-28 20:28:42 +01:00
snipe
6020927e24 Merge pull request #13538 from snipe/fixes/13516_use_int_not_string_for_port_default
Fixed #13516 - Use int not string if no DB_PORT specified
2023-08-28 19:38:41 +01:00
snipe
82981290d4 Use int not string if no DB_PORT specified
Signed-off-by: snipe <snipe@snipe.net>
2023-08-28 19:34:08 +01:00
snipe
7787ca328c Try to prevent the browser from pre-filling the LDAP password
Signed-off-by: snipe <snipe@snipe.net>
2023-08-28 18:36:06 +01:00
Godfrey Martinez
92e88a0ae2 Merge branch 'develop' into ignore_encrypt_and_other_changes 2023-08-28 10:14:41 -07:00
Brady Wetherington
427f8b1522 Merge branch 'develop' into snipeit_v7_laravel10
Upgraded composer due to lockfile conflicts
2023-08-28 16:04:21 +01:00
Brady Wetherington
8902145288 This is a squashed branch of all of the various commits that make up the new HasCustomFields trait.
This should allow us to add custom fields to just about anything we want to within Snipe-IT.

Below are the commits that have been squashed together:

Initial decoupling of custom field behavior from Assets for re-use

Add new DB columns to Custom Fields and fieldsets for 'type'

WIP: trying to figure out UI for custom fields for things other than Assets, find problematic places

Real progress towards getting to where this stuff might actually work...

Fix the table-name determining code for Custom Fields

Getting it closer to where Assets at least work

Rename the trait to it's new, even better name

Solid progress on the new Trait!

WIP: HasCustomFields, still working some stuff out

Got some basics working; creating custom fields and stuff

HasCustomFields now validates and saves

Starting to yank the other boilerplate code as things start to work (!)

Got the start of defaultValuesForCustomField() working

More progress (squash me!)

Add migrations for default_values_for_custom_fields table

WIP: more towards hasCustomFields trait

Progress cleaning up the PR, fixing FIXME's

New, passing HasCustomFieldsTrait test!

Fix date formatter helper for custom fields

Fixed more FIXME's
2023-08-28 13:07:33 +01:00
snipe
535ca0e3c0 Merge pull request #13535 from snipe/fixes/13521_make_modal_upload_button_wider
Fixed #13521 - make modal “select files” button wider
2023-08-28 12:41:27 +01:00
snipe
582cad2dc5 Fixed #13521 - make modal “select files” button wider
Signed-off-by: snipe <snipe@snipe.net>
2023-08-28 12:37:42 +01:00
snipe
e3a9c34818 Merge pull request #13533 from snipe/improvements/labels_clearer_barcode_text
Clearer labels barcode text
2023-08-26 18:08:10 +01:00
snipe
20e9f05a64 Tighter layout
Signed-off-by: snipe <snipe@snipe.net>
2023-08-26 17:43:02 +01:00
snipe
844fe0938c Added link to the help docs
Signed-off-by: snipe <snipe@snipe.net>
2023-08-26 17:42:56 +01:00
Ivan Nieto Vivanco
a12a68e4e9 Add a variable so I dont ended rewriting the original model 2023-08-24 15:28:12 -06:00
Ivan Nieto Vivanco
950536f59f Added a condition to send correct model id when cloning one 2023-08-24 15:18:51 -06:00
Godfrey M
e56628499c fixed lost changes 2023-08-24 11:45:23 -07:00
Godfrey M
2b7a899ef8 removed unnecessary code 2023-08-24 11:43:44 -07:00
Godfrey M
0b956b2a46 revert changes of composer.lock 2023-08-24 10:45:59 -07:00
Godfrey M
18cb514a53 revert unwanted changes 2023-08-24 10:40:44 -07:00
Godfrey M
a3b6e0fbe6 adds some spacing 2023-08-24 10:36:09 -07:00
Godfrey M
34ba0c4440 adds id to history info 2023-08-24 10:36:08 -07:00
Godfrey M
739fc152c2 Adds readable asset history in the action log transformer 2023-08-24 10:36:08 -07:00
Godfrey M
011c09a3dd working on getting notifications 2023-08-24 10:35:40 -07:00
Godfrey M
38d5691b88 fix conflicts 2023-08-24 10:35:40 -07:00
Godfrey M
c26ef224f4 fixing my mess 2023-08-24 10:31:01 -07:00
Godfrey M
8ebb9afedd adds min_amt to asset model edit, index, and table 2023-08-24 10:30:07 -07:00
snipe
26dd992d3c Merge pull request #13525 from marcusmoore/bug/sc-23660
Fixed undeclared variable in ActionlogsTransformer
2023-08-24 17:09:30 +01:00
Marcus Moore
e32c07be02 Clear commented log statements 2023-08-24 08:15:40 -07:00
Marcus Moore
5a0b0522b0 Explicitly declare $clean_meta variable 2023-08-24 08:15:07 -07:00
Marcus Moore
20457bd89e Run tests for PHP 7.4, 8.0, and 8.1.1 2023-08-23 15:53:34 -07:00
Godfrey Martinez
28cf533d19 Merge branch 'develop' into ignore_encrypt_and_other_changes 2023-08-23 00:42:59 -07:00
Godfrey M
0fc79ec936 fixes conflicts 2023-08-23 00:40:59 -07:00
Godfrey M
ffe1b11419 merged develop 2023-08-23 00:38:58 -07:00
Godfrey M
27488c1009 adds soft deletes to eager loading 2023-08-23 00:34:09 -07:00
Godfrey M
ce60db009c adds soft deletes to eager loading 2023-08-23 00:32:43 -07:00
snipe
d393bd5c97 Merge pull request #13511 from snipe/fixes/13510_no_translations_in_migrations
Fixed #13510 - do not use translations in migrations
2023-08-23 08:21:18 +01:00
snipe
ea37325806 Remove trans() from default values
Signed-off-by: snipe <snipe@snipe.net>
2023-08-23 08:19:05 +01:00
snipe
31a7758ab1 Merge pull request #13509 from marcusmoore/bug/sc-23636
Fixed asset model query in action log transformer
2023-08-23 06:58:41 +01:00
Marcus Moore
bee680683d Add withTrashed to asset model query 2023-08-22 17:05:06 -07:00
Godfrey M
92ddf8fc67 removed dead space 2023-08-22 12:38:50 -07:00
Godfrey M
1019287c76 retarget key 2023-08-22 12:36:43 -07:00
Godfrey M
9e438c3ed0 eager loaded changedInfo queries, reworked encrypted data change log changes 2023-08-22 12:34:45 -07:00
Godfrey M
c7f2acf2c6 removes encrypted info from change log, renames asset_eol_date in the change log 2023-08-22 11:39:38 -07:00
snipe
79b330f492 Merge pull request #13400 from inietov/fixes/asset_acceptance_user_error
Fixed Asset acceptance error when user company and asset company don't match
2023-08-22 15:49:56 +01:00
snipe
3da21e73e2 Merge pull request #13501 from snipe/fixes/checkbox_layout_in_settings
Small fixes to checkbox layout in general settings blade
2023-08-22 14:12:15 +01:00
snipe
da38945a53 Small fixes to checkbox layout in general settings blade
Signed-off-by: snipe <snipe@snipe.net>
2023-08-22 14:10:39 +01:00
snipe
a48762c64d Merge pull request #13496 from snipe/features/setting_for_name_order
Fixed #13495 added setting for name order
2023-08-22 12:50:58 +01:00
snipe
23237e5cd3 Removed unused translation
Signed-off-by: snipe <snipe@snipe.net>
2023-08-22 12:50:32 +01:00
snipe
aafb7668f5 Merge pull request #13453 from inietov/fixes/accessories_declined_issue
Fixed #13317 Accessories declined by user remain assigned
2023-08-22 12:42:55 +01:00
snipe
fea11ec7f1 Merge pull request #12761 from spencerrlongg/bulk_edit_custom_fields
Bulk Editing Custom Fields
2023-08-22 12:40:18 +01:00
snipe
303b45c9e9 Merge pull request #13485 from Godmartinz/history_info_clean_up
Added better handling of information of asset history
2023-08-22 12:25:17 +01:00
snipe
8c7925e703 Merge pull request #13436 from marcusmoore/chore/dependency-updates
Bumped dependencies
2023-08-21 22:58:52 +01:00
Marcus Moore
191c4f959f Bump guzzlehttp/psr7 to 2.4.5 2023-08-21 14:46:08 -07:00
Marcus Moore
1e10a7ee23 Bump nyholm/psr7 to 1.6.1 2023-08-21 14:45:21 -07:00
Marcus Moore
4e8537a1c7 Merge branch 'develop' into chore/dependency-updates
# Conflicts:
#	composer.lock
2023-08-21 14:44:15 -07:00
Marcus Moore
4e2ef4f056 Default to using the current date if last check in end date is not provided 2023-08-21 14:35:15 -07:00
snipe
79a4d915db Merge pull request #13456 from marcusmoore/fixes/auto-incrementing-on-74
Fixed passing invalid argument to `strpos()`
2023-08-21 22:25:52 +01:00
Marcus Moore
c332b98456 Add last checkin options to report front end 2023-08-21 13:44:49 -07:00
Godfrey M
b54aaefefb adds some spacing 2023-08-21 13:43:06 -07:00
Godfrey M
5076b45a0d adds id to history info 2023-08-21 13:40:39 -07:00
Marcus Moore
6fc06f2ee1 Add simple tests around asset check in 2023-08-21 12:31:51 -07:00
snipe
36a343365e Switched from fullName() to getFullNameAttribute()
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:14:07 +01:00
snipe
c617bf89b6 Tweak layout
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:13:45 +01:00
snipe
ba0643f6a4 Added name display format, tweaked some
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:13:28 +01:00
snipe
bfd674b622 Switched to getFullNameAttribute() from fullName() in User Presenter
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:12:25 +01:00
snipe
d73d15b8a2 Added form macro for name format
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:11:59 +01:00
snipe
8660d41aa3 Changed width of locale field
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:11:45 +01:00
snipe
c39579b170 New strings
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:11:25 +01:00
snipe
354550b52e Removed getCompleteNameAttribute(), modified getFullNameAttribute()
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:11:17 +01:00
snipe
f3460b5a4f Switch to getFullNameAttribute() in user transformer
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:10:48 +01:00
snipe
474c03e3fc Added name order to settings save controller method
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:10:03 +01:00
snipe
749002b768 Added migration to add name order to settings
Signed-off-by: snipe <snipe@snipe.net>
2023-08-21 20:09:48 +01:00
Marcus Moore
489d30c685 Set last_checkin in ui and api controllers 2023-08-21 11:57:33 -07:00
snipe
2346bab8ed Merge pull request #13492 from snipe/features/add_dymo_labelwriter
Added Dymo labelwriter
2023-08-18 23:27:37 +01:00
snipe
45898deb1a Don’t 500 if the 1D barcode doesn’t match the format requested - log an error instead
Signed-off-by: snipe <snipe@snipe.net>
2023-08-18 22:18:38 +01:00
snipe
ffc7c4e99a use number format to constrain large number displays
Signed-off-by: snipe <snipe@snipe.net>
2023-08-18 22:18:09 +01:00
snipe
1e82c2bfad Changed example asset name
Signed-off-by: snipe <snipe@snipe.net>
2023-08-18 22:17:54 +01:00
snipe
d12f4564e1 Added Dymo Labelwriter template
Signed-off-by: snipe <snipe@snipe.net>
2023-08-18 21:44:12 +01:00
snipe
1dcca14c37 Values are not sortable, so don’t show them as sortable
Signed-off-by: snipe <snipe@snipe.net>
2023-08-18 21:21:54 +01:00
snipe
63f847f125 Merge pull request #13491 from snipe/fixes/remove_border_from_default_label
Removed the black label from around the default labels
2023-08-18 18:40:45 +01:00
snipe
d56c671410 Removed the black label from around the default labels
Signed-off-by: snipe <snipe@snipe.net>
2023-08-18 18:39:00 +01:00
snipe
802651a1b4 Merge pull request #13489 from snipe/fixes/override_0_cols_rows_in_default_for_new_engine
Make sure the columns and rows can never be 0
2023-08-18 18:14:01 +01:00
snipe
0cb76a049a Make sure the columns and rows can never be 0
Signed-off-by: snipe <snipe@snipe.net>
2023-08-18 18:13:16 +01:00
Marcus Moore
8b2716d2b7 Fix update statement 2023-08-17 18:33:01 -07:00
Marcus Moore
56fb45f1ea WIP: Add last_checkin to assets table 2023-08-17 18:01:26 -07:00
Godfrey M
a62876d4bc Adds readable asset history in the action log transformer 2023-08-17 16:14:01 -07:00
Brady Wetherington
8b52bad16f Merge branch 'develop' into snipeit_v7_laravel10 2023-08-15 21:25:16 +01:00
Ivan Nieto Vivanco
25c58a8486 Fix typo in language variable name 2023-08-15 14:17:39 -06:00
Ivan Nieto Vivanco
71cb16118d Change error string for a better (?) one 2023-08-14 20:52:00 -06:00
Ivan Nieto Vivanco
d365565b6d Add message in the acceptance assets view to indicate when the user can\'t accept nor deny the asset 2023-08-14 20:51:04 -06:00
Marcus Moore
dc1a8840f1 Ensure empty string is not passed to strpos() 2023-08-14 16:40:34 -07:00
Ivan Nieto Vivanco
96440834bd Move the declinedCheckout function so it don/'t separate the class properties 2023-08-14 16:16:28 -06:00
Ivan Nieto Vivanco
8da2a8a79c Allows to save signature for declined items 2023-08-14 14:58:10 -06:00
Ivan Nieto Vivanco
4796598bb6 Add declinedCheckout method to Accessory model 2023-08-14 14:35:31 -06:00
Marcus Moore
3a2b15313c Bump guzzlehttp/psr7 to 2.4.5 2023-08-10 11:53:20 -07:00
Marcus Moore
6b6bb61400 Bump nyholm/psr7 to 1.6.1 2023-08-10 11:52:42 -07:00
Ivan Nieto Vivanco
053d3fc9ed Prevent asset to be checked out if full company support is enabled and companies not match 2023-08-02 19:23:28 -06:00
Ivan Nieto Vivanco
9ca163e8cf Stop asset acceptances from shown to user if full company support is enabled and companies not match 2023-08-02 19:22:35 -06:00
Brady Wetherington
daed0b60bc Merge branch 'develop' into snipeit_v7_laravel10 2023-07-31 19:40:56 +01:00
Brady Wetherington
4654f7aa37 Porting Snipe-IT v7 to Laravel v10 2023-07-31 14:07:12 +01:00
Brady Wetherington
70e87dad1c Merge branch 'develop' into snipeit_v7 2023-07-27 16:21:31 +01:00
Brady Wetherington
ba8d8a6f05 Merge branch 'develop' into snipeit_v7 2023-07-20 17:16:04 +01:00
Brady Wetherington
605d267fe8 Merge branch 'develop' into snipeit_v7 2023-07-19 11:47:51 +01:00
Brady Wetherington
8f2a17585e Merge branch 'develop' into snipeit_v7
Had to do a lot of conflict work here, so this could get ugly :(
2023-07-10 16:53:35 +01:00
slong753
8cee3a8218 ok, @marcusmoore was right all along
i just didn't get it
2023-06-29 14:06:52 -05:00
slong753
826ea0ded8 move colon to blade based on a comment 2023-06-29 10:40:04 -05:00
slong753
ea61f634fb get rid of nice nullsafe 2023-06-28 16:48:52 -05:00
slong753
afe6fe207a resolved, missed adding errors in a case 2023-06-28 16:45:18 -05:00
slong753
8923206ac8 translation string 2023-06-28 13:08:34 -05:00
slong753
3b8ab2d682 clean formatting a little 2023-06-28 13:00:59 -05:00
slong753
2d27941105 added to textarea too 2023-06-28 12:59:54 -05:00
slong753
50a518e5f3 disable input when field is unique 2023-06-28 12:57:23 -05:00
slong753
8b8e7cb5ee couple translation strings 2023-06-28 10:17:28 -05:00
Brady Wetherington
51424d01a9 Merge branch 'develop' into snipeit_v7 2023-06-28 14:25:33 +01:00
slong753
e8988bf51e add list 2023-06-26 20:22:27 -05:00
slong753
2736161909 oops, fixed translation 2023-06-26 16:37:06 -05:00
slong753
8cbff0179c translation strings 2023-06-26 16:35:53 -05:00
slong753
2a352619f7 clean up 2023-06-26 16:25:48 -05:00
slong753
691faf6340 ok, this kind of works - pr needs some clean up 2023-06-26 14:56:07 -05:00
Spencer Long
f646623a5e Merge branch 'develop' into bulk_edit_custom_fields 2023-06-22 18:00:31 -05:00
Brady Wetherington
f5ff9b2208 Merge branch 'develop' into snipeit_v7 - 2023-06-12 2023-06-12 16:56:23 +01:00
Brady Wetherington
a9c7dbd17a Shift PHP version requiremeents for Snipe-IT v7 to be php 8.0-8.2 2023-05-17 17:51:02 -07:00
Brady Wetherington
09fdc946a0 Merge branch 'develop' into snipeit_v7
Had to do some asset rebuilds, and a composer update
2023-05-17 17:37:10 -07:00
slong753
5e34ffa2b0 wip 2023-05-10 14:08:12 -05:00
slong753
1d2596fc54 wip 2023-05-09 14:58:59 -05:00
slong753
815c77f943 wip 2023-05-03 14:49:31 -05:00
snipe
31f1bce16b Merge pull request #12958 from uberbrady/snipeit_v7_merge_develop_05_01_2022
Snipeit v7 merge develop 05 01 2022
2023-05-03 10:40:18 -07:00
slong753
cdda4a56d8 aha, ok thisd kind of works 2023-05-02 19:11:43 -05:00
slong753
b2c2097e8b just more troubleshooting stuff, still no solution 2023-05-02 18:54:20 -05:00
Brady Wetherington
2f3ddaec20 Merge branch 'develop' into snipeit_v7 2023-05-01 18:09:12 -07:00
snipe
7cd37e6e95 Merge pull request #12957 from uberbrady/fix_webpack_for_v7
Fix webpack for v7
2023-05-01 18:08:46 -07:00
Brady Wetherington
a938009074 Rebuilt assets with new Node and node_modules and package.json 2023-05-01 18:06:51 -07:00
Brady Wetherington
21d08ff742 Whoops! Forgot to commit this before 2023-05-01 18:05:06 -07:00
snipe
da8b41b12a Merge pull request #12956 from uberbrady/upgrade_node_and_packages_for_v7
Updated Node version, and various packages to more recent
2023-05-01 17:33:20 -07:00
Brady Wetherington
6e031727fa Updated Node version, and various packages to more recent 2023-05-01 17:05:10 -07:00
Brady Wetherington
381890b578 Merge branch 'develop' into laravel_v9 2023-04-27 13:56:04 -07:00
slong753
52c9fefbe0 ok, this works except error display 2023-04-26 15:06:50 -05:00
slong753
6bec9cf880 fix a couple things 2023-04-25 23:26:16 -05:00
Godfrey M
b8c424fca0 adds users total cost to user profile 2023-04-24 09:17:42 -07:00
Godfrey M
97df39001d adds optional breakdowns for total cost 2023-04-19 17:51:37 -07:00
Godfrey M
6872f8da7b adding total cost to user view 2023-04-19 17:31:09 -07:00
Brady Wetherington
71fa6d765f Update to Latest Livewire files 2023-04-18 11:05:35 -07:00
Brady Wetherington
9793130f6c Merge branch 'develop' into laravel_v9 2023-04-18 11:01:00 -07:00
slong753
52dc99588e pushing for now, needs validation work 2023-04-17 13:57:48 -05:00
slong753
8b9aea8874 very much WIP, but dupe queries reduced
but i think this can be cleaned up a bit more
2023-04-12 14:46:48 -05:00
slong753
830e3e5594 cleanup 2023-04-05 15:27:28 -05:00
slong753
d56a4e7173 cleanup+formatting 2023-04-05 15:16:46 -05:00
slong753
3929526a57 ok, this works but needs to be tested more 2023-04-04 18:57:51 -05:00
slong753
29e3d202fe checking something, quick push 2023-04-03 17:54:31 -05:00
slong753
b0fa298d8f delete a couple things 2023-03-29 15:14:48 -05:00
slong753
65bbecd145 this all works - could use some eyes 2023-03-29 14:46:31 -05:00
slong753
032ae4348e custom fields display, need to get saving
+some clean up
2023-03-28 21:45:31 -05:00
slong753
804a788a27 initial fetch working 2023-03-28 20:31:24 -05:00
Brady Wetherington
aadfa9aa9c Back out Laravel Vite stuff - we're so far away from that right now 2023-02-21 19:54:14 -08:00
Brady Wetherington
645bba96cd WIP: First stabs at getting Laravel Vite to work; no luck so far :(
Probably going to back out all the 'vite' stuff anyways :/
2023-02-21 19:35:37 -08:00
198 changed files with 13770 additions and 301975 deletions

View File

@@ -6,7 +6,6 @@ environment:
services:
- mysql: 5.7
- dusk:
on:
push:
@@ -43,19 +42,3 @@ pipeline:
- name: PHPUnit Feature Tests
cmd: |
php artisan test --testsuite Feature
# - name: Browser Tests
# cmd: |
# cp -v .env.dusk.example .env.dusk.ci
# sed -i "s@APP_ENV=.*@APP_ENV=ci@g" .env.dusk.ci
# sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
# #sed -i "s@DB_HOST=.*@DB_HOST=mysql@g" .env.dusk.ci
# sed -i "s@DB_HOST=.*@DB_HOST=$DB_HOST@g" .env.dusk.ci
# sed -i "s@DB_USERNAME=.*@DB_USERNAME=chipperci@g" .env.dusk.ci
# sed -i "s@DB_DATABASE=.*@DB_DATABASE=chipperci@g" .env.dusk.ci
# sed -i "s@DB_PASSWORD=.*@DB_PASSWORD=secret@g" .env.dusk.ci
#
# php -S [::0]:8000 -t public 2>server.log &
# sleep 2
# php artisan dusk:chrome-driver $CHROME_DRIVER
# php artisan dusk --env=ci

73
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
name: Tests
on:
push:
branches:
- master
- develop
pull_request:
jobs:
tests:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: snipeit
ports:
- 33306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
strategy:
fail-fast: false
matrix:
php-version:
- "7.4"
- "8.0"
- "8.1.1"
name: PHP ${{ matrix.php-version }}
steps:
- uses: shivammathur/setup-php@v2
with:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v3
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Copy .env
run: |
cp -v .env.testing.example .env
cp -v .env.testing.example .env.testing
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Generate key
run: php artisan key:generate
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: mysql
DB_DATABASE: snipeit
DB_PORT: ${{ job.services.mysql.ports[3306] }}
DB_USERNAME: root
run: php artisan test --parallel

2
.gitignore vendored
View File

@@ -1,8 +1,6 @@
.couscous
.DS_Store
.env
.env.dusk.*
!.env.dusk.example
.env.testing
phpstan.neon
.idea

2
.nvmrc
View File

@@ -1 +1 @@
v12.22.1
v18.16.0

View File

@@ -9,7 +9,39 @@ Before starting, follow the [instructions](README.md#installation) for installin
Before attempting to run the test suite copy the example environment file for tests and update the values to match your environment:
`cp .env.testing.example .env.testing`
> Since the data in the database is flushed after each test it is recommended you create a separate mysql database for specifically for tests
The following should work for running tests in memory with sqlite:
```
# --------------------------------------------
# REQUIRED: BASIC APP SETTINGS
# --------------------------------------------
APP_ENV=testing
APP_DEBUG=true
APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU=
APP_URL=http://localhost:8000
APP_TIMEZONE='UTC'
APP_LOCALE=en
# --------------------------------------------
# REQUIRED: DATABASE SETTINGS
# --------------------------------------------
DB_CONNECTION=sqlite_testing
#DB_HOST=127.0.0.1
#DB_PORT=3306
#DB_DATABASE=null
#DB_USERNAME=null
#DB_PASSWORD=null
```
To use MySQL you should update the `DB_` variables to match your local test database:
```
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE={}
DB_USERNAME={}
DB_PASSWORD={}
```
Now you are ready to run the entire test suite from your terminal:
@@ -18,34 +50,3 @@ Now you are ready to run the entire test suite from your terminal:
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`
## Browser Tests
Browser tests are run via [Laravel Dusk](https://laravel.com/docs/8.x/dusk) and require Google Chrome to be installed.
Before attempting to run Dusk tests copy the example environment file for Dusk and update the values to match your environment:
`cp .env.dusk.example .env.dusk.local`
> `local` refers to the value of `APP_ENV` in your `.env` so if you have it set to `dev` then the file should be named `.env.dusk.dev`.
**Important**: Dusk tests cannot be run using an in-memory SQLite database. Additionally, the Dusk test suite uses the `DatabaseMigrations` trait which will leave the database in a fresh state after running. Therefore, it is recommended that you create a test database and point `DB_DATABASE` in `.env.dusk.local` to it.
### Running Browser Tests
Your application needs to be configured and up and running in order for the browser tests to actually run. When running the tests locally, you can start the application using the following command:
`php artisan serve`
Now you are ready to run the test suite. Use the following command from another terminal tab or window:
`php artisan dusk`
To run individual test files, you can pass the path to the test that you want to run:
`php artisan dusk tests/Browser/LoginTest.php`
If you get an error when attempting to run Dusk tests that says `Couldn't connect to server` run:
`php artisan dusk:chrome-driver --detect`
This command will install the specific ChromeDriver Dusk needs for your operating system and Chrome version.

View File

@@ -15,18 +15,20 @@ class CheckoutableCheckedIn
public $checkedInBy;
public $note;
public $action_date; // Date setted in the hardware.checkin view at the checkin_at input, for the action log
public $originalValues;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null)
public function __construct($checkoutable, $checkedOutTo, User $checkedInBy, $note, $action_date = null, $originalValues = [])
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedInBy = $checkedInBy;
$this->note = $note;
$this->action_date = $action_date ?? date('Y-m-d');
$this->originalValues = $originalValues;
}
}

View File

@@ -14,17 +14,19 @@ class CheckoutableCheckedOut
public $checkedOutTo;
public $checkedOutBy;
public $note;
public $originalValues;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note)
public function __construct($checkoutable, $checkedOutTo, User $checkedOutBy, $note, $originalValues = [])
{
$this->checkoutable = $checkoutable;
$this->checkedOutTo = $checkedOutTo;
$this->checkedOutBy = $checkedOutBy;
$this->note = $note;
$this->originalValues = $originalValues;
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace App\Helpers;
use Illuminate\Support\Facades\Gate;
/*********************
* These two helper methods are more designed for being re-used with the new HasCustomFields Trait
*
* The 'transform' method is designed for BlahTransformer things that need to return custom field values.
*
* The 'present' method is designed for when you're trying to generate fieldlists for use in Bootstrap tables
* - typically the 'dataTableLayout' method
*
*********************/
class CustomFieldHelper {
static function transform($fieldset, $item) {
if ($fieldset && ($fieldset->fields->count() > 0)) {
$fields_array = [];
foreach ($fieldset->fields as $field) {
if ($field->isFieldDecryptable($item->{$field->db_column})) {
$decrypted = Helper::gracefulDecrypt($field, $item->{$field->db_column});
$value = (Gate::allows('assets.view.encrypted_custom_fields')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
if ($field->format == 'DATE'){
if (Gate::allows('assets.view.encrypted_custom_fields')){
$value = Helper::getFormattedDateObject($value, 'date', false);
} else {
$value = strtoupper(trans('admin/custom_fields/general.encrypted'));
}
}
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'field_format' => $field->format,
'element' => $field->element,
];
} else {
$value = $item->{$field->db_column};
if (($field->format == 'DATE') && (!is_null($value)) && ($value!='')){
$value = Helper::getFormattedDateObject($value, 'date', false);
}
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'field_format' => $field->format,
'element' => $field->element,
];
}
return $fields_array;
}
} else {
return new \stdClass; // HACK to force generation of empty object instead of empty list
}
}
static function present($field) {
return [
'field' => 'custom_fields.'.$field->db_column,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => $field->name,
'formatter'=> 'customFieldsFormatter',
'escape' => true,
'class' => ($field->field_encrypted == '1') ? 'css-padlock' : '',
'visible' => ($field->show_in_listview == '1') ? true : false,
];
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Helpers;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\CustomField;
@@ -573,6 +575,17 @@ class Helper
return $customfields;
}
/**
* Get all of the different types of custom fields there are
* TODO - how to make this more general? Or more useful? or more dynamic?
* idea - key of classname, *value* of trans? (thus having to make this a method, which is fine)
*/
static $itemtypes_having_custom_fields = [
0 => \App\Models\Asset::class,
1 => \App\Models\User::class,
// 2 => \App\Models\Accessory::class
];
/**
* Get the list of custom field formats in an array to make a dropdown menu
*
@@ -643,6 +656,7 @@ class Helper
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
$components = Component::whereNotNull('min_amt')->get();
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
$avail_consumables = 0;
$items_array = [];
@@ -705,6 +719,28 @@ class Helper
}
}
foreach ($asset_models as $asset_model){
$asset = new Asset();
$total_owned = $asset->where('model_id', '=', $asset_model->id)->count();
$avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count();
if ($avail < ($asset_model->min_amt)+ \App\Models\Setting::getSettings()->alert_threshold) {
if ($avail > 0) {
$percent = number_format((($avail / $total_owned) * 100), 0);
} else {
$percent = 100;
}
$items_array[$all_count]['id'] = $asset_model->id;
$items_array[$all_count]['name'] = $asset_model->name;
$items_array[$all_count]['type'] = 'models';
$items_array[$all_count]['percent'] = $percent;
$items_array[$all_count]['remaining'] = $avail;
$items_array[$all_count]['min_amt'] = $asset_model->min_amt;
$all_count++;
}
}
return $items_array;
}

View File

@@ -161,22 +161,19 @@ class AccessoriesFilesController extends Controller
->header('Content-Type', 'text/plain');
} else {
// Display the file inline
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
// won't work, as they're not accessible via the web
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
} else {
if ($download != 'true') {
\Log::debug('display the file');
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(['error' => 'Failed validation: '], 500);
}
return StorageHelper::downloader($file);
}
}
}

View File

@@ -69,7 +69,7 @@ class AcceptanceController extends Controller
}
if (! Company::isCurrentUserHasAccess($acceptance->checkoutable)) {
return redirect()->route('account.accept')->with('error', trans('general.insufficient_permissions'));
return redirect()->route('account.accept')->with('error', trans('general.error_user_company'));
}
return view('account/accept.create', compact('acceptance'));
@@ -245,6 +245,36 @@ class AcceptanceController extends Controller
$return_msg = trans('admin/users/message.accepted');
} else {
/**
* Check for the eula-pdfs directory
*/
if (! Storage::exists('private_uploads/eula-pdfs')) {
Storage::makeDirectory('private_uploads/eula-pdfs', 775);
}
if (Setting::getSettings()->require_accept_signature == '1') {
// Check if the signature directory exists, if not create it
if (!Storage::exists('private_uploads/signatures')) {
Storage::makeDirectory('private_uploads/signatures', 775);
}
// The item was accepted, check for a signature
if ($request->filled('signature_output')) {
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
$data_uri = $request->input('signature_output');
$encoded_image = explode(',', $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
// No image data is present, kick them back.
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
} else {
return redirect()->back()->with('error', trans('general.shitty_browser'));
}
}
// Format the data to send the declined notification
$branding_settings = SettingsController::getPDFBranding();
@@ -281,11 +311,18 @@ class AcceptanceController extends Controller
'item_model' => $display_model,
'item_serial' => $item->serial,
'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'),
'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null,
'assigned_to' => $assigned_to,
'company_name' => $branding_settings->site_name,
'date_settings' => $branding_settings->date_display_format,
];
if ($pdf_view_route!='') {
\Log::debug($pdf_filename.' is the filename, and the route was specified.');
$pdf = Pdf::loadView($pdf_view_route, $data);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf->output());
}
$acceptance->decline($sig_filename);
$acceptance->notify(new AcceptanceAssetDeclinedNotification($data));
event(new CheckoutDeclined($acceptance));

View File

@@ -38,6 +38,7 @@ class AssetModelsController extends Controller
'image',
'name',
'model_number',
'min_amt',
'eol',
'notes',
'created_at',
@@ -52,6 +53,7 @@ class AssetModelsController extends Controller
'models.image',
'models.name',
'model_number',
'min_amt',
'eol',
'requestable',
'models.notes',
@@ -63,7 +65,7 @@ class AssetModelsController extends Controller
'models.deleted_at',
'models.updated_at',
])
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues')
->with('category', 'depreciation', 'manufacturer')
->withCount('assets as assets_count');
if ($request->input('status')=='deleted') {

View File

@@ -110,7 +110,7 @@ class AssetsController extends Controller
$filter = json_decode($request->input('filter'), true);
}
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
$all_custom_fields = CustomField::where('type', Asset::class); //used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[] = $field->db_column_name();
}
@@ -573,42 +573,8 @@ class AssetsController extends Controller
$asset = $request->handleImages($asset);
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
$model = AssetModel::find($request->get('model_id'));
$asset->customFill($request, Auth::user(), true);
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
// Set the field value based on what was sent in the request
$field_val = $request->input($field->db_column, null);
// If input value is null, use custom field's default value
if ($field_val == null) {
\Log::debug('Field value for '.$field->db_column.' is null');
$field_val = $field->defaultValue($request->get('model_id'));
\Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id')));
}
// if the field is set to encrypted, make sure we encrypt the value
if ($field->field_encrypted == '1') {
\Log::debug('This model field is encrypted in this fieldset.');
if (Gate::allows('admin')) {
// If input value is null, use custom field's default value
if (($field_val == null) && ($request->has('model_id') != '')) {
$field_val = \Crypt::encrypt($field->defaultValue($request->get('model_id')));
} else {
$field_val = \Crypt::encrypt($request->input($field->db_column));
}
}
}
$asset->{$field->db_column} = $field_val;
}
}
if ($asset->save()) {
if ($request->get('assigned_user')) {
@@ -668,21 +634,7 @@ class AssetsController extends Controller
$asset = $request->handleImages($asset);
// Update custom fields
if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->db_column)) {
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
}
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
$asset->customFill($request,Auth::user());
if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
@@ -905,6 +857,7 @@ class AssetsController extends Controller
$asset->expected_checkin = null;
$asset->last_checkout = null;
$asset->last_checkin = now();
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
@@ -924,10 +877,14 @@ class AssetsController extends Controller
}
$checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s');
$originalValues = $asset->getRawOriginal();
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
$originalValues['action_date'] = $checkin_at;
}
if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
}

View File

@@ -35,7 +35,7 @@ class CustomFieldsetsController extends Controller
public function index()
{
$this->authorize('index', CustomField::class);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
$fieldsets = CustomFieldset::withCount('fields as fields_count')->get();
return (new CustomFieldsetsTransformer)->transformCustomFieldsets($fieldsets, $fieldsets->count());
}
@@ -125,7 +125,7 @@ class CustomFieldsetsController extends Controller
$this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
$modelsCount = $fieldset->models->count();
$modelsCount = $fieldset->customizables()->count();
$fieldsCount = $fieldset->fields->count();
if (($modelsCount > 0) || ($fieldsCount > 0)) {

View File

@@ -13,6 +13,7 @@ use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\UsersTransformer;
use App\Models\Asset;
use App\Models\Company;
use App\Models\CustomField;
use App\Models\License;
use App\Models\User;
use App\Notifications\CurrentInventory;
@@ -36,7 +37,7 @@ class UsersController extends Controller
{
$this->authorize('view', User::class);
$users = User::select([
$allowed_columns = [
'users.activated',
'users.created_by',
'users.address',
@@ -73,9 +74,13 @@ class UsersController extends Controller
'users.vip',
'users.autoassign_licenses',
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
];
foreach(CustomField::where('type', User::class)->get() as $field) {
$allowed_columns[] = $field->db_column_name();
}
$users = User::select()->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',)
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
$users = Company::scopeCompanyables($users);
if ($request->filled('activated')) {
@@ -271,6 +276,8 @@ class UsersController extends Controller
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed();
}
$users = Company::scopeCompanyables($users);
$total = $users->count();
$users = $users->skip($offset)->take($limit)->get();
@@ -366,7 +373,9 @@ class UsersController extends Controller
$user->password = bcrypt($request->get('password', $tmp_pass));
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
$user->customFill($request,Auth::user());
if ($user->save()) {
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
@@ -457,7 +466,9 @@ class UsersController extends Controller
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');
$user->customFill($request,Auth::user());
if ($user->save()) {
// Sync group memberships:

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\AssetModel;
use App\Models\DefaultValuesForCustomFields;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\View;
@@ -76,6 +77,7 @@ class AssetModelsController extends Controller
$model->depreciation_id = $request->input('depreciation_id');
$model->name = $request->input('name');
$model->model_number = $request->input('model_number');
$model->min_amt = $request->input('min_amt');
$model->manufacturer_id = $request->input('manufacturer_id');
$model->category_id = $request->input('category_id');
$model->notes = $request->input('notes');
@@ -153,12 +155,13 @@ class AssetModelsController extends Controller
$model->eol = $request->input('eol');
$model->name = $request->input('name');
$model->model_number = $request->input('model_number');
$model->min_amt = $request->input('min_amt');
$model->manufacturer_id = $request->input('manufacturer_id');
$model->category_id = $request->input('category_id');
$model->notes = $request->input('notes');
$model->requestable = $request->input('requestable', '0');
$this->removeCustomFieldsDefaultValues($model);
DefaultValuesForCustomFields::forPivot($model, Asset::class)->delete();
if ($request->input('fieldset_id') == '') {
$model->fieldset_id = null;
@@ -286,6 +289,7 @@ class AssetModelsController extends Controller
return view('models/edit')
->with('depreciation_list', Helper::depreciationList())
->with('item', $model)
->with('model_id', $model_to_clone->id)
->with('clone_model', $model_to_clone);
}
@@ -448,7 +452,7 @@ class AssetModelsController extends Controller
}
/**
* Adds default values to a model (as long as they are truthy)
* Adds default values to a model (as long as they are truthy) (does this mean I cannot set a default value of 0?)
*
* @param AssetModel $model
* @param array $defaultValues
@@ -483,22 +487,12 @@ class AssetModelsController extends Controller
}
foreach ($defaultValues as $customFieldId => $defaultValue) {
if(is_array($defaultValue)){
$model->defaultValues()->attach($customFieldId, ['default_value' => implode(', ', $defaultValue)]);
}elseif ($defaultValue) {
$model->defaultValues()->attach($customFieldId, ['default_value' => $defaultValue]);
if(is_array($defaultValue)) {
$defaultValue = implode(', ', $defaultValue);
}
DefaultValuesForCustomFields::updateOrCreate(['custom_field_id' => $customFieldId,'item_pivot_id' => $model->id], ['default_value' => $defaultValue]);
}
return true;
}
/**
* Removes all default values
*
* @return void
*/
private function removeCustomFieldsDefaultValues(AssetModel $model)
{
$model->defaultValues()->detach();
}
}

View File

@@ -78,7 +78,7 @@ class AssetModelsFilesController extends Controller
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($modelId = null, $fileId = null, $download = true)
public function show($modelId = null, $fileId = null)
{
$model = AssetModel::find($modelId);
// the asset is valid
@@ -99,12 +99,13 @@ class AssetModelsFilesController extends Controller
->header('Content-Type', 'text/plain');
}
if ($download != 'true') {
if ($contents = file_get_contents(Storage::url($file))) {
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
if (request('inline') == 'true') {
return JsonResponse::create(['error' => 'Failed validation: '], 500);
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);

View File

@@ -68,6 +68,7 @@ class AssetCheckinController extends Controller
$asset->expected_checkin = null;
$asset->last_checkout = null;
$asset->last_checkin = now();
$asset->assigned_to = null;
$asset->assignedTo()->disassociate($asset);
$asset->assigned_type = null;
@@ -108,8 +109,11 @@ class AssetCheckinController extends Controller
}
}
$originalValues = $asset->getRawOriginal();
$checkin_at = date('Y-m-d H:i:s');
if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) {
$originalValues['action_date'] = $checkin_at;
$checkin_at = $request->get('checkin_at');
}
@@ -132,7 +136,7 @@ class AssetCheckinController extends Controller
// Was the asset updated?
if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues));
if ((isset($user)) && ($backto == 'user')) {
return redirect()->route('users.show', $user->id)->with('success', trans('admin/hardware/message.checkin.success'));

View File

@@ -89,6 +89,14 @@ class AssetCheckoutController extends Controller
}
}
$settings = \App\Models\Setting::getSettings();
if ($settings->full_multiple_companies_support){
if ($target->company_id != $asset->company_id){
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company'));
}
}
if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $request->get('name'))) {
return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success'));
}

View File

@@ -79,7 +79,7 @@ class AssetFilesController extends Controller
* @return View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($assetId = null, $fileId = null, $download = true)
public function show($assetId = null, $fileId = null)
{
$asset = Asset::find($assetId);
// the asset is valid
@@ -103,12 +103,13 @@ class AssetFilesController extends Controller
->header('Content-Type', 'text/plain');
}
if ($download != 'true') {
if ($contents = file_get_contents(Storage::url($file))) {
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
if (request('inline') == 'true') {
return JsonResponse::create(['error' => 'Failed validation: '], 500);
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);

View File

@@ -164,29 +164,7 @@ class AssetsController extends Controller
$asset = $request->handleImages($asset);
}
// Update custom fields in the database.
// Validation for these fields is handled through the AssetRequest form request
$model = AssetModel::find($request->get('model_id'));
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field) {
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
}
}
} else {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = implode(', ', $request->input($field->db_column));
} else {
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
$asset->customFill($request, Auth::user()); // Update custom fields in the database.
// Validate the asset before saving
if ($asset->isValid() && $asset->save()) {

View File

@@ -14,6 +14,7 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use App\Http\Requests\AssetCheckoutRequest;
use App\Models\CustomField;
class BulkAssetsController extends Controller
{
@@ -31,7 +32,7 @@ class BulkAssetsController extends Controller
public function edit(Request $request)
{
$this->authorize('view', Asset::class);
if (! $request->filled('ids')) {
return redirect()->back()->with('error', trans('admin/hardware/message.update.no_assets_selected'));
}
@@ -41,13 +42,30 @@ class BulkAssetsController extends Controller
session(['bulk_back_url' => $bulk_back_url]);
$asset_ids = array_values(array_unique($request->input('ids')));
//custom fields logic
$asset_custom_field = Asset::with(['model.fieldset.fields', 'model'])->whereIn('id', $asset_ids)->whereHas('model', function ($query) {
return $query->where('fieldset_id', '!=', null);
})->get();
$models = $asset_custom_field->unique('model_id');
$modelNames = [];
foreach($models as $model) {
$modelNames[] = $model->model->name;
}
if ($request->filled('bulk_actions')) {
switch ($request->input('bulk_actions')) {
case 'labels':
$this->authorize('view', Asset::class);
$assets_found = Asset::find($asset_ids);
if ($assets_found->isEmpty()){
return redirect()->back();
}
return (new Label)
->with('assets', Asset::find($asset_ids))
->with('assets', $assets_found)
->with('settings', Setting::getSettings())
->with('bulkedit', true)
->with('count', 0);
@@ -74,7 +92,9 @@ class BulkAssetsController extends Controller
$this->authorize('update', Asset::class);
return view('hardware/bulk')
->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList());
->with('statuslabel_list', Helper::statusLabelList())
->with('models', $models->pluck(['model']))
->with('modelNames', $modelNames);
}
}
@@ -92,6 +112,7 @@ class BulkAssetsController extends Controller
public function update(Request $request)
{
$this->authorize('update', Asset::class);
$error_bag = [];
// Get the back url from the session and then destroy the session
$bulk_back_url = route('hardware.index');
@@ -100,12 +121,21 @@ class BulkAssetsController extends Controller
}
if (! $request->filled('ids') || count($request->input('ids')) <= 0) {
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
if(Session::exists('ids')) {
$assets = Session::get('ids');
} elseif (! $request->filled('ids') || count($request->input('ids')) <= 0) {
return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected'));
}
$assets = array_keys($request->input('ids'));
if ($request->anyFilled($custom_field_columns)) {
$custom_fields_present = true;
} else {
$custom_fields_present = false;
}
if (($request->filled('purchase_date'))
|| ($request->filled('expected_checkin'))
|| ($request->filled('purchase_cost'))
@@ -121,6 +151,7 @@ class BulkAssetsController extends Controller
|| ($request->filled('null_purchase_date'))
|| ($request->filled('null_expected_checkin_date'))
|| ($request->filled('null_next_audit_date'))
|| ($request->anyFilled($custom_field_columns))
) {
foreach ($assets as $assetId) {
@@ -136,6 +167,9 @@ class BulkAssetsController extends Controller
->conditionallyAddItem('supplier_id')
->conditionallyAddItem('warranty_months')
->conditionallyAddItem('next_audit_date');
foreach ($custom_field_columns as $key => $custom_field_column) {
$this->conditionallyAddItem($custom_field_column);
}
if ($request->input('null_purchase_date')=='1') {
$this->update_array['purchase_date'] = null;
@@ -168,11 +202,11 @@ class BulkAssetsController extends Controller
}
$changed = [];
$asset = Asset::where('id' ,$assetId)->get();
$assetCollection = Asset::where('id' ,$assetId)->get();
foreach ($this->update_array as $key => $value) {
if ($this->update_array[$key] != $asset->toArray()[0][$key]) {
$changed[$key]['old'] = $asset->toArray()[0][$key];
if ($this->update_array[$key] != $assetCollection->toArray()[0][$key]) {
$changed[$key]['old'] = $assetCollection->toArray()[0][$key];
$changed[$key]['new'] = $this->update_array[$key];
}
}
@@ -184,17 +218,47 @@ class BulkAssetsController extends Controller
$logAction->user_id = Auth::id();
$logAction->log_meta = json_encode($changed);
$logAction->logaction('update');
DB::table('assets')
->where('id', $assetId)
->update($this->update_array);
} // endforeach
if($custom_fields_present) {
$asset = Asset::find($assetId);
$assetCustomFields = $asset->model()->first()->fieldset;
if($assetCustomFields && $assetCustomFields->fields) {
foreach ($assetCustomFields->fields as $field) {
if (array_key_exists($field->db_column, $this->update_array)) {
$asset->{$field->db_column} = $this->update_array[$field->db_column];
$saved = $asset->save();
if(!$saved) {
$error_bag[] = $asset->getErrors();
}
continue;
} else {
$array = $this->update_array;
array_except($array, $field->db_column);
$asset->save($array);
}
if (!$asset->save()) {
$error_bag[] = $asset->getErrors();
}
}
}
} else {
Asset::find($assetId)->update($this->update_array);
}
}
if(!empty($error_bag)) {
$errors = [];
//find the customfield name from the name of the messagebag items
foreach ($error_bag as $key => $bag) {
foreach($bag->keys() as $key => $value) {
CustomField::where('db_column', $value)->get()->map(function($item) use (&$errors) {
$errors[] = $item->name;
});
}
}
return redirect($bulk_back_url)->with('bulk_errors', array_unique($errors));
}
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
}
// no values given, nothing to update
return redirect($bulk_back_url)->with('warning', trans('admin/hardware/message.update.nothing_updated'));
}

View File

@@ -132,7 +132,7 @@ class ComponentsFilesController extends Controller
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($componentId = null, $fileId = null, $download = true)
public function show($componentId = null, $fileId = null)
{
\Log::debug('Private filesystem is: '.config('filesystems.default'));
$component = Component::find($componentId);
@@ -157,21 +157,17 @@ class ComponentsFilesController extends Controller
->header('Content-Type', 'text/plain');
} else {
// Display the file inline
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
} else {
if ($download != 'true') {
\Log::debug('display the file');
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(['error' => 'Failed validation: '], 500);
}
return StorageHelper::downloader($file);
}
}
}
}

View File

@@ -131,7 +131,7 @@ class ConsumablesFilesController extends Controller
* @return \Symfony\Consumable\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($consumableId = null, $fileId = null, $download = true)
public function show($consumableId = null, $fileId = null)
{
$consumable = Consumable::find($consumableId);
@@ -155,22 +155,19 @@ class ConsumablesFilesController extends Controller
->header('Content-Type', 'text/plain');
} else {
// Display the file inline
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
// won't work, as they're not accessible via the web
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
} else {
if ($download != 'true') {
\Log::debug('display the file');
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(['error' => 'Failed validation: '], 500);
}
return StorageHelper::downloader($file);
}
}
}

View File

@@ -6,10 +6,12 @@ use App\Helpers\Helper;
use App\Http\Requests\CustomFieldRequest;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Redirect;
/**
* This controller handles all actions related to Custom Asset Fields for
* the Snipe-IT Asset Management application.
@@ -21,6 +23,7 @@ use Redirect;
*/
class CustomFieldsController extends Controller
{
/**
* Returns a view with a listing of custom fields.
*
@@ -29,12 +32,16 @@ class CustomFieldsController extends Controller
* @return \Illuminate\Support\Facades\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function index()
public function index(Request $request)
{
$this->authorize('view', CustomField::class);
if ( $request->input('tab') == 1 ) {
// Users section, make sure to auto-create the first fieldset if so
CustomFieldset::firstOrCreate(['type' => Helper::$itemtypes_having_custom_fields[1]], ['name' => 'default']);
}
$fieldsets = CustomFieldset::with('fields', 'models')->get();
$fields = CustomField::with('fieldset')->get();
$fieldsets = CustomFieldset::with('fields')->where("type", Helper::$itemtypes_having_custom_fields[$request->get('tab',0)])->get(); //cannot eager-load 'customizable' because it's not a relation
$fields = CustomField::with('fieldset')->where("type", Helper::$itemtypes_having_custom_fields[$request->get('tab',0)])->get();
return view('custom_fields.index')->with('custom_fieldsets', $fieldsets)->with('custom_fields', $fields);
}
@@ -67,7 +74,7 @@ class CustomFieldsController extends Controller
public function create(Request $request)
{
$this->authorize('create', CustomField::class);
$fieldsets = CustomFieldset::get();
$fieldsets = CustomFieldset::where('type', Helper::$itemtypes_having_custom_fields[$request->get('tab')])->get();
return view('custom_fields.fields.edit', [
'predefinedFormats' => Helper::predefined_formats(),
@@ -110,8 +117,10 @@ class CustomFieldsController extends Controller
"display_in_user_view" => $display_in_user_view,
"auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0),
"show_in_listview" => $request->get("show_in_listview", 0),
"user_id" => Auth::id()
"user_id" => Auth::id(),
]);
// not mass-assignable; must be manual
$field->type = Helper::$itemtypes_having_custom_fields[$request->get('tab')];
if ($request->filled('custom_format')) {
@@ -124,14 +133,17 @@ class CustomFieldsController extends Controller
// Sync fields with fieldsets
$fieldset_array = $request->input('associate_fieldsets');
if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) {
if ($request->get('tab') == 1 ) {
$fieldset_array = [CustomFieldset::firstOrCreate(['type' => User::class],['name' => 'default'])->id => true];
}
if (($request->has('associate_fieldsets') || $request->get('tab') == 1) && (is_array($fieldset_array))) {
$field->fieldset()->sync(array_keys($fieldset_array));
} else {
$field->fieldset()->sync([]);
}
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.create.success'));
return redirect()->route('fields.index',['tab' => $request->get('tab',0)])->with('success', trans('admin/custom_fields/message.field.create.success'));
}
return redirect()->back()->with('selected_fieldsets', $request->input('associate_fieldsets'))->withInput()
@@ -184,12 +196,17 @@ class CustomFieldsController extends Controller
if ($field = CustomField::find($field_id)) {
$this->authorize('delete', $field);
if ($field->type == User::class) {
$field->fieldset()->detach(); // remove from 'default' group (and others, if they exist in the future!)
}
if (($field->fieldset) && ($field->fieldset->count() > 0)) {
return redirect()->back()->withErrors(['message' => 'Field is in-use']);
}
$type = $field->type;
$field->delete();
return redirect()->route("fields.index")
->with("success", trans('admin/custom_fields/message.field.delete.success'));
return redirect()->route('fields.index',['tab' => array_search($type, Helper::$itemtypes_having_custom_fields)])
->with('success', trans('admin/custom_fields/message.field.delete.success'));
}
return redirect()->back()->withErrors(['message' => 'Field does not exist']);
@@ -289,7 +306,7 @@ class CustomFieldsController extends Controller
$field->fieldset()->sync([]);
}
return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.update.success'));
return redirect()->route('fields.index',['tab' => $request->get('tab',0)])->with('success', trans('admin/custom_fields/message.field.update.success'));
}
return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.field.update.error'));

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\AssetModel;
use App\Models\CustomField;
use App\Models\CustomFieldset;
@@ -38,7 +39,7 @@ class CustomFieldsetsController extends Controller
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v1.8]
*/
public function show($id)
public function show( $id)
{
$cfset = CustomFieldset::with('fields')
->where('id', '=', $id)->orderBy('id', 'ASC')->first();
@@ -46,7 +47,7 @@ class CustomFieldsetsController extends Controller
$this->authorize('view', $cfset);
if ($cfset) {
$custom_fields_list = ['' => 'Add New Field to Fieldset'] + CustomField::pluck('name', 'id')->toArray();
$custom_fields_list = ['' => 'Add New Field to Fieldset'] + CustomField::where('type', $cfset->type)->pluck('name', 'id')->toArray();
$maxid = 0;
foreach ($cfset->fields as $field) {
@@ -96,6 +97,8 @@ class CustomFieldsetsController extends Controller
$fieldset = new CustomFieldset([
'name' => $request->get('name'),
'user_id' => Auth::user()->id,
'type' => Helper::$itemtypes_having_custom_fields[$request->get('tab')]
// 'sub' =>
]);
$validator = Validator::make($request->all(), $fieldset->rules);

View File

@@ -30,7 +30,7 @@ class LabelsController extends Controller
$exampleAsset = new Asset();
$exampleAsset->id = 999999;
$exampleAsset->name = 'AST-AB-CD-1234';
$exampleAsset->name = 'JEN-867-5309';
$exampleAsset->asset_tag = 'TCA-00001';
$exampleAsset->serial = 'SN9876543210';

View File

@@ -152,21 +152,19 @@ class LicenseFilesController extends Controller
->header('Content-Type', 'text/plain');
} else {
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
// won't work, as they're not accessible via the web
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
} else {
if ($download != 'true') {
\Log::debug('display the file');
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(['error' => 'Failed validation: '], 500);
}
return StorageHelper::downloader($file);
}
}

View File

@@ -545,6 +545,10 @@ class ReportsController extends Controller
$header[] = trans('admin/hardware/table.checkout_date');
}
if ($request->filled('checkin_date')) {
$header[] = trans('admin/hardware/table.last_checkin_date');
}
if ($request->filled('expected_checkin')) {
$header[] = trans('admin/hardware/form.expected_checkin');
}
@@ -651,6 +655,14 @@ class ReportsController extends Controller
$assets->whereBetween('assets.last_checkout', [$checkout_start, $checkout_end]);
}
if (($request->filled('checkin_date_start'))) {
$assets->whereBetween('last_checkin', [
Carbon::parse($request->input('checkin_date_start'))->startOfDay(),
// use today's date is `checkin_date_end` is not provided
Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(),
]);
}
if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) {
$assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]);
}
@@ -835,6 +847,12 @@ class ReportsController extends Controller
$row[] = ($asset->last_checkout) ? $asset->last_checkout : '';
}
if ($request->filled('checkin_date')) {
$row[] = ($asset->last_checkin)
? Carbon::parse($asset->last_checkin)->format('Y-m-d')
: '';
}
if ($request->filled('expected_checkin')) {
$row[] = ($asset->expected_checkin) ? $asset->expected_checkin : '';
}
@@ -1003,7 +1021,12 @@ class ReportsController extends Controller
$assetsForReport = $acceptances
->filter(function ($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
$acceptance_checkoutable_flag = false;
if ($acceptance->checkoutable){
$acceptance_checkoutable_flag = $acceptance->checkoutable->checkedOutToUser();
}
return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance_checkoutable_flag;
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];

View File

@@ -590,6 +590,7 @@ class SettingsController extends Controller
$setting->date_display_format = $request->input('date_display_format');
$setting->time_display_format = $request->input('time_display_format');
$setting->digit_separator = $request->input('digit_separator');
$setting->name_display_format = $request->input('name_display_format');
if ($setting->save()) {
return redirect()->route('settings.index')

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Users;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
@@ -139,18 +140,25 @@ class UserFilesController extends Controller
// the license is valid
if (isset($user->id)) {
$this->authorize('view', $user);
$log = Actionlog::find($fileId);
$file = $log->get_src('users');
return Response::download($file); //FIXME this doesn't use the new StorageHelper yet, but it's complicated...
// Display the file inline
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
}
return Storage::download('private_uploads/users/'.$log->filename);
}
// Prepare the error message
$error = trans('admin/users/message.user_not_found', ['id' => $userId]);
// Redirect to the licence management page
return redirect()->route('users.index')->with('error', $error);
// Redirect to the user management page if the user doesn't exist
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId]));
}
}

View File

@@ -133,6 +133,9 @@ class UsersController extends Controller
// we have to invoke the
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
\Log::info("About to call customFill, in the 'store' controller!!!");
$user->customFill($request, Auth::user());
if ($user->save()) {
if ($request->filled('groups')) {
$user->groups()->sync($request->input('groups'));
@@ -300,6 +303,8 @@ class UsersController extends Controller
// Handle uploaded avatar
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
\Log::debug("calling custom fill from the UPDATE method!");
$user->customFill($request, Auth::user());
//\Log::debug(print_r($user, true));
// Was the user updated?

View File

@@ -18,13 +18,12 @@ class Kernel extends HttpKernel
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Fideloper\Proxy\TrustProxies::class,
\App\Http\Middleware\CheckForSetup::class,
\App\Http\Middleware\CheckForDebug::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\SecurityHeaders::class,
\App\Http\Middleware\PreventBackHistory::class,
\Fruitcake\Cors\HandleCors::class,
\Illuminate\Http\Middleware\HandleCors::class,
];

View File

@@ -2,6 +2,8 @@
namespace App\Http\Livewire;
use App\Models\Asset;
use App\Models\DefaultValuesForCustomFields;
use Livewire\Component;
use App\Models\CustomFieldset;
@@ -30,7 +32,7 @@ class CustomFieldSetDefaultValuesForModel extends Component
$this->fields = CustomFieldset::find($this->fieldset_id)->fields;
}
$this->add_default_values = ($this->model->defaultValues->count() > 0);
$this->add_default_values = (DefaultValuesForCustomFields::forPivot($this->model, Asset::class)->count() > 0);
}
public function updatedFieldsetId()

View File

@@ -100,7 +100,7 @@ class Importer extends Component
if ($type == "asset") {
// add Custom Fields after a horizontal line
$results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’";
foreach (CustomField::orderBy('name')->get() as $field) {
foreach (CustomField::where('type', \App\Models\Asset::class)->orderBy('name')->get() as $field) { // TODO - generalize?
$results[$field->db_column_name()] = $field->name;
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
@@ -10,7 +10,7 @@ class TrustProxies extends Middleware
/**
* The trusted proxies for this application.
*
* @var array|string|null
* @var array<int, string>|string|null
*/
protected $proxies;
@@ -19,5 +19,10 @@ class TrustProxies extends Middleware
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB;
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@@ -34,12 +34,14 @@ class CustomFieldRequest extends FormRequest
case 'POST':
{
$rules['name'] = 'required|unique:custom_fields';
$rules['tab'] = 'required';
break;
}
// Save all fields
case 'PUT':
$rules['name'] = 'required';
$rules['tab'] = 'required';
break;
// Save only what's passed

View File

@@ -32,6 +32,7 @@ class SaveUserRequest extends FormRequest
public function rules()
{
$rules = [
'department_id' => 'nullable|exists:departments,id',
'manager_id' => 'nullable|exists:users,id',
];

View File

@@ -3,7 +3,12 @@ namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Actionlog;
use App\Models\CustomField;
use App\Models\Setting;
use App\Models\Company;
use App\Models\Supplier;
use App\Models\Location;
use App\Models\AssetModel;
use Illuminate\Database\Eloquent\Collection;
class ActionlogsTransformer
@@ -38,6 +43,7 @@ class ActionlogsTransformer
public function transformActionlog (Actionlog $actionlog, $settings = null)
{
$icon = $actionlog->present()->icon();
$custom_field = CustomField::all();
if ($actionlog->filename!='') {
$icon = e(\App\Helpers\Helper::filetype_icon($actionlog->filename));
}
@@ -46,13 +52,24 @@ class ActionlogsTransformer
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
$meta_array = json_decode($actionlog->log_meta);
$clean_meta = [];
if ($meta_array) {
foreach ($meta_array as $fieldname => $fieldata) {
$clean_meta[$fieldname]['old'] = $this->clean_field($fieldata->old);
$clean_meta[$fieldname]['new'] = $this->clean_field($fieldata->new);
if( str_starts_with($fieldname, '_snipeit_')){
if( $custom_field->where('db_column', '=', $fieldname)->where('field_encrypted', true)){
$clean_meta[$fieldname]['old'] = "encrypted";
$clean_meta[$fieldname]['new'] = "encrypted";
}
}
else {
$clean_meta[$fieldname]['old'] = $this->clean_field($fieldata->old);
$clean_meta[$fieldname]['new'] = $this->clean_field($fieldata->new);
}
}
}
$clean_meta= $this->changedInfo($clean_meta);
}
$file_url = '';
@@ -115,9 +132,10 @@ class ActionlogsTransformer
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
];
//\Log::info("Clean Meta is: ".print_r($clean_meta,true));
// \Log::info("Clean Meta is: ".print_r($clean_meta,true));
//dd($array);
return $array;
}
@@ -132,6 +150,80 @@ class ActionlogsTransformer
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
/**
* This takes the ids of the changed attributes and returns the names instead for the history view of an Asset
*
* @param array $clean_meta
* @return array
*/
public function changedInfo(array $clean_meta)
{ $location = Location::withTrashed()->get();
$supplier = Supplier::withTrashed()->get();
$model = AssetModel::withTrashed()->get();
$company = Company::get();
if(array_key_exists('rtd_location_id',$clean_meta)) {
$clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". $location->find($clean_meta['rtd_location_id']['old'])->name : trans('general.unassigned');
$clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". $location->find($clean_meta['rtd_location_id']['new'])->name : trans('general.unassigned');
$clean_meta['Default Location'] = $clean_meta['rtd_location_id'];
unset($clean_meta['rtd_location_id']);
}
if(array_key_exists('location_id', $clean_meta)) {
$clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ".$location->find($clean_meta['location_id']['old'])->name : trans('general.unassigned');
$clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ".$location->find($clean_meta['location_id']['new'])->name : trans('general.unassigned');
$clean_meta['Current Location'] = $clean_meta['location_id'];
unset($clean_meta['location_id']);
}
if(array_key_exists('model_id', $clean_meta)) {
$oldModel = $model->find($clean_meta['model_id']['old']);
$oldModelName = $oldModel->name ?? trans('admin/models/message.deleted');
$newModel = $model->find($clean_meta['model_id']['new']);
$newModelName = $newModel->name ?? trans('admin/models/message.deleted');
$clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".$oldModelName;
$clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".$newModelName; /** model is required at asset creation */
$clean_meta['Model'] = $clean_meta['model_id'];
unset($clean_meta['model_id']);
}
if(array_key_exists('company_id', $clean_meta)) {
$oldCompany = $company->find($clean_meta['company_id']['old']);
$oldCompanyName = $oldCompany->name ?? trans('admin/companies/message.deleted');
$newCompany = $company->find($clean_meta['company_id']['new']);
$newCompanyName = $newCompany->name ?? trans('admin/companies/message.deleted');
$clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."] ". $oldCompanyName : trans('general.unassigned');
$clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ". $newCompanyName : trans('general.unassigned');
$clean_meta['Company'] = $clean_meta['company_id'];
unset($clean_meta['company_id']);
}
if(array_key_exists('supplier_id', $clean_meta)) {
$oldSupplier = $supplier->find($clean_meta['supplier_id']['old']);
$oldSupplierName = $oldSupplier->name ?? trans('admin/suppliers/message.deleted');
$newSupplier = $supplier->find($clean_meta['supplier_id']['new']);
$newSupplierName = $newSupplier->name ?? trans('admin/suppliers/message.deleted');
$clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ". $oldSupplierName : trans('general.unassigned');
$clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ". $newSupplierName : trans('general.unassigned');
$clean_meta['Supplier'] = $clean_meta['supplier_id'];
unset($clean_meta['supplier_id']);
}
if(array_key_exists('asset_eol_date', $clean_meta)) {
$clean_meta['EOL date'] = $clean_meta['asset_eol_date'];
unset($clean_meta['asset_eol_date']);
}
return $clean_meta;
}

View File

@@ -47,6 +47,7 @@ class AssetModelsTransformer
] : null,
'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null,
'model_number' => e($assetmodel->model_number),
'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null,
'depreciation' => ($assetmodel->depreciation) ? [
'id' => (int) $assetmodel->depreciation->id,
'name'=> e($assetmodel->depreciation->name),

View File

@@ -2,6 +2,7 @@
namespace App\Http\Transformers;
use App\Helpers\CustomFieldHelper;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Setting;
@@ -96,49 +97,7 @@ class AssetsTransformer
];
if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
if ($field->isFieldDecryptable($asset->{$field->db_column})) {
$decrypted = Helper::gracefulDecrypt($field, $asset->{$field->db_column});
$value = (Gate::allows('assets.view.encrypted_custom_fields')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
if ($field->format == 'DATE'){
if (Gate::allows('assets.view.encrypted_custom_fields')){
$value = Helper::getFormattedDateObject($value, 'date', false);
} else {
$value = strtoupper(trans('admin/custom_fields/general.encrypted'));
}
}
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'field_format' => $field->format,
'element' => $field->element,
];
} else {
$value = $asset->{$field->db_column};
if (($field->format == 'DATE') && (!is_null($value)) && ($value!='')){
$value = Helper::getFormattedDateObject($value, 'date', false);
}
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'field_format' => $field->format,
'element' => $field->element,
];
}
$array['custom_fields'] = $fields_array;
}
} else {
$array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list
}
$array['custom_fields'] = CustomFieldHelper::transform($asset->model->fieldset,$asset);
$permissions_array['available_actions'] = [
'checkout' => ($asset->deleted_at=='' && Gate::allows('checkout', Asset::class)) ? true : false,

View File

@@ -3,6 +3,8 @@
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\CustomFieldset;
use Illuminate\Database\Eloquent\Collection;
@@ -21,8 +23,13 @@ class CustomFieldsetsTransformer
public function transformCustomFieldset(CustomFieldset $fieldset)
{
$fields = $fieldset->fields;
$models = $fieldset->models;
$models = [];
$modelsArray = [];
if ($fieldset->type == Asset::class) {
\Log::debug("Item pivot id is: ".$fieldset->item_pivot_id);
$models = AssetModel::where('fieldset_id', $fieldset->id)->get();
\Log::debug("And the models object count is: ".$models->count());
}
foreach ($models as $model) {
$modelsArray[] = [
@@ -30,15 +37,21 @@ class CustomFieldsetsTransformer
'name' => e($model->name),
];
}
\Log::debug("Models array is: ".print_r($modelsArray,true));
$array = [
'id' => (int) $fieldset->id,
'name' => e($fieldset->name),
'fields' => (new CustomFieldsTransformer)->transformCustomFields($fields, $fieldset->fields_count),
'models' => (new DatatablesTransformer)->transformDatatables($modelsArray, $fieldset->models_count),
'customizables' => (new DatatablesTransformer)->transformDatatables($fieldset->customizables(),count($fieldset->customizables())),
'created_at' => Helper::getFormattedDateObject($fieldset->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($fieldset->updated_at, 'datetime'),
'type' => $fieldset->type,
];
if ($fieldset->type == Asset::class) {
// TODO - removeme - legacy column just for Assets?
$array['models'] = (new DatatablesTransformer)->transformDatatables($modelsArray, count($modelsArray));
}
return $array;
}

View File

@@ -26,8 +26,8 @@ class LabelsTransformer
'name' => $label->getName(),
'unit' => $label->getUnit(),
'width' => $label->getWidth(),
'height' => $label->getHeight(),
'width' => number_format($label->getWidth(), 2),
'height' => number_format($label->getHeight(), 2),
'margin_top' => $label->getMarginTop(),
'margin_bottom' => $label->getMarginBottom(),

View File

@@ -2,7 +2,10 @@
namespace App\Http\Transformers;
use App\Helpers\CustomFieldHelper;
use App\Helpers\Helper;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
@@ -24,7 +27,7 @@ class UsersTransformer
$array = [
'id' => (int) $user->id,
'avatar' => e($user->present()->gravatar),
'name' => e($user->first_name).' '.e($user->last_name),
'name' => e($user->getFullNameAttribute()),
'first_name' => e($user->first_name),
'last_name' => e($user->last_name),
'username' => e($user->username),
@@ -77,6 +80,8 @@ class UsersTransformer
'deleted_at' => ($user->deleted_at) ? Helper::getFormattedDateObject($user->deleted_at, 'datetime') : null,
];
$array['custom_fields'] = CustomFieldHelper::transform(CustomFieldset::where('type',User::class)->first(), $user);
$permissions_array['available_actions'] = [
'update' => (Gate::allows('update', User::class) && ($user->deleted_at == '')),
'delete' => (Gate::allows('delete', User::class) && ($user->assets_count == 0) && ($user->licenses_count == 0) && ($user->accessories_count == 0)),

View File

@@ -23,19 +23,20 @@ class AssetImporter extends ItemImporter
// ItemImporter handles the general fetching.
parent::handle($row);
// FIXME : YUP!!!!! This shit needs to go (?) Yeah?
if ($this->customFields) {
foreach ($this->customFields as $customField) {
$customFieldValue = $this->array_smart_custom_field_fetch($row, $customField);
$customFieldValue = $this->array_smart_custom_field_fetch($row, $customField); // TODO/FIXME - this might require a new 'mode' on customFill()?
if ($customFieldValue) {
if ($customField->field_encrypted == 1) {
if ($customField->field_encrypted == 1) { // FIXME - repeated code.
$this->item['custom_fields'][$customField->db_column_name()] = \Crypt::encrypt($customFieldValue);
$this->log('Custom Field '.$customField->name.': '.\Crypt::encrypt($customFieldValue));
} else {
$this->item['custom_fields'][$customField->db_column_name()] = $customFieldValue;
$this->log('Custom Field '.$customField->name.': '.$customFieldValue);
}
} else {
} else { // FIXME - think this through? Do we want to blank this? Is that how other stuff works?
// Clear out previous data.
$this->item['custom_fields'][$customField->db_column_name()] = null;
}

View File

@@ -120,7 +120,7 @@ abstract class Importer
* @author Daniel Meltzer
* @since 5.0
*/
protected function populateCustomFields($headerRow)
protected function populateCustomFields($headerRow) // FIXME - what in the actual fuck is this.
{
// Stolen From https://adamwathan.me/2016/07/14/customizing-keys-when-mapping-collections/
// This 'inverts' the fields such that we have a collection of fields indexed by name.

View File

@@ -33,7 +33,7 @@ class LogListener
*/
public function onCheckoutableCheckedIn(CheckoutableCheckedIn $event)
{
$event->checkoutable->logCheckin($event->checkedOutTo, $event->note, $event->action_date);
$event->checkoutable->logCheckin($event->checkedOutTo, $event->note, $event->action_date, $event->originalValues);
}
/**
@@ -46,7 +46,7 @@ class LogListener
*/
public function onCheckoutableCheckedOut(CheckoutableCheckedOut $event)
{
$event->checkoutable->logCheckout($event->note, $event->checkedOutTo, $event->checkoutable->last_checkout);
$event->checkoutable->logCheckout($event->note, $event->checkedOutTo, $event->checkoutable->last_checkout, $event->originalValues);
}
/**

View File

@@ -349,6 +349,22 @@ class Accessory extends SnipeModel
return (int) $remaining;
}
/**
* Run after the checkout acceptance was declined by the user
*
* @param User $acceptedBy
* @param string $signature
*/
public function declinedCheckout(User $declinedBy, $signature)
{
if (is_null($accessory_user = \DB::table('accessories_users')->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) {
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$accessory_user->limit(1)->delete();
}
/**
* Query builder scope to order on company
*

View File

@@ -9,6 +9,8 @@ use App\Helpers\Helper;
use App\Http\Traits\UniqueSerialTrait;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\Acceptable;
use App\Models\Traits\Customizable;
use App\Models\Traits\HasCustomFields;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use AssetPresenter;
@@ -18,6 +20,7 @@ use DB;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Watson\Validating\ValidatingTrait;
@@ -38,8 +41,19 @@ class Asset extends Depreciable
public const ASSET = 'asset';
public const USER = 'user';
use Acceptable;
use Acceptable, HasCustomFields;
public function getFieldsetKey(): object|int|null {
return $this->model;
}
public static function getFieldsetUsers(int $fieldset_id): array {
$models = [];
foreach(AssetModel::where("fieldset_id",$fieldset_id)->get() as $model) {
$models[route('models.show', $model->id)] = $model->name.(($model->model_number) ? ' ('.$model->model_number.')' : '');
}
return $models;
}
/**
* Run after the checkout acceptance was declined by the user
*
@@ -73,6 +87,7 @@ class Asset extends Depreciable
protected $casts = [
'purchase_date' => 'date',
'last_checkout' => 'datetime',
'last_checkin' => 'datetime',
'expected_checkin' => 'date',
'last_audit_date' => 'datetime',
'next_audit_date' => 'date',
@@ -82,7 +97,6 @@ class Asset extends Depreciable
'location_id' => 'integer',
'rtd_company_id' => 'integer',
'supplier_id' => 'integer',
'byod' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
@@ -105,6 +119,7 @@ class Asset extends Depreciable
'purchase_cost' => 'numeric|nullable|gte:0',
'supplier_id' => 'exists:suppliers,id|nullable',
'asset_eol_date' => 'date|max:10|min:10|nullable',
'byod' => 'boolean',
];
/**
@@ -187,40 +202,6 @@ class Asset extends Depreciable
$this->attributes['expected_checkin'] = $value;
}
/**
* This handles the custom field validation for assets
*
* @var array
*/
public function save(array $params = [])
{
if ($this->model_id != '') {
$model = AssetModel::find($this->model_id);
if (($model) && ($model->fieldset)) {
foreach ($model->fieldset->fields as $field){
if($field->format == 'BOOLEAN'){
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
}
}
$this->rules += $model->fieldset->validation_rules();
foreach ($this->model->fieldset->fields as $field){
if($field->format == 'BOOLEAN'){
$this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN);
}
}
}
}
return parent::save($params);
}
public function getDisplayNameAttribute()
{
return $this->present()->name();
@@ -332,6 +313,13 @@ class Asset extends Depreciable
}
}
$originalValues = $this->getRawOriginal();
// attempt to detect change in value if different from today's date
if ($checkout_at && strpos($checkout_at, date('Y-m-d')) === false) {
$originalValues['action_date'] = date('Y-m-d H:i:s');
}
if ($this->save()) {
if (is_int($admin)) {
$checkedOutBy = User::findOrFail($admin);
@@ -340,7 +328,7 @@ class Asset extends Depreciable
} else {
$checkedOutBy = Auth::user();
}
event(new CheckoutableCheckedOut($this, $target, $checkedOutBy, $note));
event(new CheckoutableCheckedOut($this, $target, $checkedOutBy, $note, $originalValues));
$this->increment('checkout_counter', 1);
@@ -1515,7 +1503,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.
*

View File

@@ -29,6 +29,7 @@ class AssetModel extends SnipeModel
protected $rules = [
'name' => 'required|min:1|max:255',
'model_number' => 'max:255|nullable',
'min_amt' => 'integer|min:0|nullable',
'category_id' => 'required|integer|exists:categories,id',
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
'eol' => 'integer:min:0|max:240|nullable',
@@ -65,6 +66,7 @@ class AssetModel extends SnipeModel
'fieldset_id',
'image',
'manufacturer_id',
'min_amt',
'model_number',
'name',
'notes',
@@ -148,19 +150,13 @@ class AssetModel extends SnipeModel
*/
public function fieldset()
{
// this is actually OK - we don't *need* to do this, but it's okay to make references from Model to fieldset
return $this->belongsTo(\App\Models\CustomFieldset::class, 'fieldset_id');
}
/**
* Establishes the model -> custom field default values relationship
*
* @author hannah tinkler
* @since [v4.3]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function defaultValues()
public function customFields()
{
return $this->belongsToMany(\App\Models\CustomField::class, 'models_custom_fields')->withPivot('default_value');
return $this->fieldset()->first()->fields();
}
/**

View File

@@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Validation\Rule;
use Schema;
use Watson\Validating\ValidatingTrait;
class CustomField extends Model
{
use HasFactory;
@@ -17,7 +16,7 @@ class CustomField extends Model
UniqueUndeletedTrait;
/**
* Custom field predfined formats
* Custom field predefined formats
*
* @var array
*/
@@ -54,6 +53,7 @@ class CustomField extends Model
'field_encrypted' => 'nullable|boolean',
'auto_add_to_fieldsets' => 'boolean',
'show_in_listview' => 'boolean',
'type' => 'required'
];
/**
@@ -76,30 +76,19 @@ class CustomField extends Model
];
/**
* This is confusing, since it's actually the custom fields table that
* we're usually modifying, but since we alter the assets table, we have to
* say that here, otherwise the new fields get added onto the custom fields
* table instead of the assets table.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v3.0]
*/
public static $table_name = 'assets';
/**
* Convert the custom field's name property to a db-safe string.
*
* We could probably have used str_slug() here but not sure what it would
* do with previously existing values. - @snipe
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.4]
* @return string
* @since [v3.4]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public static function name_to_db_name($name)
{
return '_snipeit_'.preg_replace('/[^a-zA-Z0-9]/', '_', strtolower($name));
return '_snipeit_' . preg_replace('/[^a-zA-Z0-9]/', '_', strtolower($name));
}
/**
@@ -110,23 +99,22 @@ class CustomField extends Model
* if they have changed, so we handle that here so that we don't have to remember
* to do it in the controllers.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.4]
* @return bool
* @since [v3.4]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public static function boot()
{
parent::boot();
self::created(function ($custom_field) {
// Column already exists on the assets table - nothing to do here.
// This *shouldn't* happen in the wild.
if (Schema::hasColumn(self::$table_name, $custom_field->db_column)) {
if (Schema::hasColumn($custom_field->getTableName(), $custom_field->db_column)) {
return false;
}
// Update the column name in the assets table
Schema::table(self::$table_name, function ($table) use ($custom_field) {
Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) {
$table->text($custom_field->convertUnicodeDbSlug())->nullable();
});
@@ -139,7 +127,7 @@ class CustomField extends Model
// Column already exists on the assets table - nothing to do here.
if ($custom_field->isDirty('name')) {
if (Schema::hasColumn(self::$table_name, $custom_field->convertUnicodeDbSlug())) {
if (Schema::hasColumn($custom_field->getTableName(), $custom_field->convertUnicodeDbSlug())) {
return true;
}
@@ -149,7 +137,7 @@ class CustomField extends Model
$platform->registerDoctrineTypeMapping('enum', 'string');
// Rename the field if the name has changed
Schema::table(self::$table_name, function ($table) use ($custom_field) {
Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) {
$table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal('name')), $custom_field->convertUnicodeDbSlug());
});
@@ -165,12 +153,19 @@ class CustomField extends Model
// Drop the assets column if we've deleted it from custom fields
self::deleting(function ($custom_field) {
return Schema::table(self::$table_name, function ($table) use ($custom_field) {
return Schema::table($custom_field->getTableName(), function ($table) use ($custom_field) {
$table->dropColumn($custom_field->db_column);
});
});
}
public function getTableName()
{
$type = $this->type;
$instance = new $type();
return $instance->getTable();
}
/**
* Establishes the customfield -> fieldset relationship
*
@@ -182,6 +177,11 @@ class CustomField extends Model
{
return $this->belongsToMany(\App\Models\CustomFieldset::class);
}
public function assetModels()
{
return $this->fieldset()->with('models')->get()->pluck('models')->flatten()->unique('id');
}
/**
* Establishes the customfield -> admin user relationship
@@ -196,31 +196,23 @@ class CustomField extends Model
}
/**
* Establishes the customfield -> default values relationship
*
* @author Hannah Tinkler
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function defaultValues()
{
return $this->belongsToMany(\App\Models\AssetModel::class, 'models_custom_fields')->withPivot('default_value');
}
/**
* Returns the default value for a given model using the defaultValues
* Returns the default value for a given 'item' using the defaultValues
* relationship
*
* @param int $modelId
* @return string
*/
public function defaultValue($modelId)
public function defaultValue($pivot_id)
{
return $this->defaultValues->filter(function ($item) use ($modelId) {
return $item->pivot->asset_model_id == $modelId;
})->map(function ($item) {
return $item->pivot->default_value;
})->first();
/*
below, you might think you need to add:
where('type', $this->type),
but the type can be inferred from by the custom_field itself (which also has a type)
can't use forPivot() here because we don't have an object yet. (TODO?)
*/
DefaultValuesForCustomFields::where('item_pivot_id', $pivot_id)->where('custom_field_id',$this->id)->first()?->default_value; //TODO - php8-only operator!
}
/**

View File

@@ -20,6 +20,7 @@ class CustomFieldset extends Model
*/
public $rules = [
'name' => 'required|unique:custom_fieldsets',
''
];
/**
@@ -48,11 +49,13 @@ class CustomFieldset extends Model
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
* @return \Illuminate\Database\Eloquent\Collection
*/
public function models()
public function customizables() // TODO - I don't like this name, but I can't think of anything better
{
return $this->hasMany(\App\Models\AssetModel::class, 'fieldset_id');
$customizable_class_name = $this->type; //TODO - copypasta from Customizable trait?
\Log::debug("Customizable Class name is: ".$customizable_class_name);
return $customizable_class_name::getFieldsetUsers($this->id);
}
/**
@@ -77,6 +80,7 @@ class CustomFieldset extends Model
*/
public function validation_rules()
{
\Log::debug("CALLING validation_rules FOR customfiledsets!");
$rules = [];
foreach ($this->fields as $field) {
$rule = [];
@@ -90,7 +94,12 @@ class CustomFieldset extends Model
$rule[] = 'unique_undeleted';
}
array_push($rule, $field->attributes['format']);
\Log::debug("Field Format for".$field->name." is: ".$field->format);
if($field->format == 'DATE') { //we do a weird mutator thing, it's confusing - but, yes, it's all-caps
$rule[] = 'date_format:Y-m-d';
} else {
array_push($rule, $field->attributes['format']);
}
$rules[$field->db_column_name()] = $rule;
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Watson\Validating\ValidatingTrait;
class DefaultValuesForCustomFields extends Model
{
use HasFactory, ValidatingTrait;
protected $rules = [
'type' => 'required'
];
public $timestamps = false;
public function field() {
return $this->belongsTo('custom_fields');
}
// There is, effectively, another 'relation' here, but it's weirdly polymorphic
// and impossible to represent in Laravel.
// we have a 'type', and we have an 'item_pivot_id' -
// For example, in Assets the 'type' would be App\Models\Asset, and the 'item_pivot_id' would be a model_id
// I can't come up with any way to represent this in Laravel/Eloquent
// TODO: might be getting overly-fancy here; maybe just want to do an ID? Instead of an Eloquent Model?
public function scopeForPivot(Builder $query, Model $item, string $class) {
return $query->where('item_pivot_id', $item->id)->where('type', $class);
}
}

View File

@@ -37,7 +37,7 @@ class DefaultLabel extends RectangleSheet
private int $columns;
private int $rows;
public function __construct() {
$settings = Setting::getSettings();
@@ -62,6 +62,16 @@ class DefaultLabel extends RectangleSheet
$this->columns = ($usableWidth + $this->labelSpacingH) / ($this->labelWidth + $this->labelSpacingH);
$this->rows = ($usableHeight + $this->labelSpacingV) / ($this->labelHeight + $this->labelSpacingV);
// Make sure the columns and rows are never zero, since that scenario should never happen
if ($this->columns == 0) {
$this->columns = 1;
}
if ($this->rows == 0) {
$this->rows = 1;
}
}
public function getUnit() { return 'in'; }
@@ -76,7 +86,7 @@ class DefaultLabel extends RectangleSheet
public function getColumns() { return $this->columns; }
public function getRows() { return $this->rows; }
public function getLabelBorder() { return 0.01; }
public function getLabelBorder() { return 0; }
public function getLabelWidth() { return $this->labelWidth; }
public function getLabelHeight() { return $this->labelHeight; }
@@ -85,7 +95,7 @@ class DefaultLabel extends RectangleSheet
public function getLabelMarginBottom() { return 0; }
public function getLabelMarginLeft() { return 0; }
public function getLabelMarginRight() { return 0; }
public function getLabelColumnSpacing() { return $this->labelSpacingH; }
public function getLabelRowSpacing() { return $this->labelSpacingV; }
@@ -106,7 +116,7 @@ class DefaultLabel extends RectangleSheet
$textY = 0;
$textX1 = 0;
$textX2 = $this->getLabelWidth();
// 1D Barcode
if ($record->get('barcode1d')) {
static::write1DBarcode(
@@ -115,7 +125,7 @@ class DefaultLabel extends RectangleSheet
$this->getLabelWidth() - 0.1, self::BARCODE1D_SIZE
);
}
// 2D Barcode
if ($record->get('barcode2d')) {
static::write2DBarcode(

View File

@@ -370,7 +370,11 @@ abstract class Label
*/
public final function write1DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) {
if (empty($value)) return;
$pdf->write1DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
try {
$pdf->write1DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
} catch (\Exception $e) {
\Log::error('The 1D barcode ' . $value . ' is not compliant with the barcode type '. $type);
}
}
/**

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Models\Labels\Tapes\Dymo;
use App\Helpers\Helper;
use App\Models\Labels\Label;
abstract class LabelWriter extends Label
{
private const HEIGHT = 1.15;
private const MARGIN_SIDES = 0.1;
private const MARGIN_ENDS = 0.1;
public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'in', $this->getUnit()); }
public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit()); }
public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit());}
public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); }
public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); }
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Models\Labels\Tapes\Dymo;
class LabelWriter_30252 extends LabelWriter
{
private const BARCODE_MARGIN = 1.80;
private const TAG_SIZE = 2.80;
private const TITLE_SIZE = 2.80;
private const TITLE_MARGIN = 0.50;
private const LABEL_SIZE = 2.00;
private const LABEL_MARGIN = - 0.35;
private const FIELD_SIZE = 3.20;
private const FIELD_MARGIN = 0.15;
public function getUnit() { return 'mm'; }
public function getWidth() { return 96.52; }
public function getSupportAssetTag() { return true; }
public function getSupport1DBarcode() { return true; }
public function getSupport2DBarcode() { return true; }
public function getSupportFields() { return 3; }
public function getSupportLogo() { return false; }
public function getSupportTitle() { return true; }
public function preparePDF($pdf) {}
public function write($pdf, $record) {
$pa = $this->getPrintableArea();
$currentX = $pa->x1;
$currentY = $pa->y1;
$usableWidth = $pa->w;
$barcodeSize = $pa->h - self::TAG_SIZE;
if ($record->has('barcode2d')) {
static::writeText(
$pdf, $record->get('tag'),
$pa->x1, $pa->y2 - self::TAG_SIZE,
'freemono', 'b', self::TAG_SIZE, 'C',
$barcodeSize, self::TAG_SIZE, true, 0
);
static::write2DBarcode(
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
$currentX, $currentY,
$barcodeSize, $barcodeSize
);
$currentX += $barcodeSize + self::BARCODE_MARGIN;
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
} else {
static::writeText(
$pdf, $record->get('tag'),
$pa->x1, $pa->y2 - self::TAG_SIZE,
'freemono', 'b', self::TAG_SIZE, 'R',
$usableWidth, self::TAG_SIZE, true, 0
);
}
if ($record->has('title')) {
static::writeText(
$pdf, $record->get('title'),
$currentX, $currentY,
'freesans', '', self::TITLE_SIZE, 'L',
$usableWidth, self::TITLE_SIZE, true, 0
);
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
}
foreach ($record->get('fields') as $field) {
static::writeText(
$pdf, $field['label'],
$currentX, $currentY,
'freesans', '', self::LABEL_SIZE, 'L',
$usableWidth, self::LABEL_SIZE, true, 0, 0
);
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
static::writeText(
$pdf, $field['value'],
$currentX, $currentY,
'freemono', 'B', self::FIELD_SIZE, 'L',
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
);
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
}
}
}

View File

@@ -323,7 +323,10 @@ class License extends Depreciable
*/
public function checkin_email()
{
return $this->category->checkin_email;
if ($this->category) {
return $this->category->checkin_email;
}
return false;
}
/**
@@ -335,7 +338,11 @@ class License extends Depreciable
*/
public function requireAcceptance()
{
return $this->category->require_acceptance;
if ($this->category) {
return $this->category->require_acceptance;
}
return false;
}
/**
@@ -348,14 +355,16 @@ class License extends Depreciable
*/
public function getEula()
{
if ($this->category->eula_text) {
return Helper::parseEscapedMarkedown($this->category->eula_text);
} elseif ($this->category->use_default_eula == '1') {
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
} else {
return false;
if ($this->category){
if ($this->category->eula_text) {
return Helper::parseEscapedMarkedown($this->category->eula_text);
} elseif ($this->category->use_default_eula == '1') {
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
}
}
return false;
}
/**

View File

@@ -48,7 +48,10 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
*/
public function requireAcceptance()
{
return $this->license->category->require_acceptance;
if ($this->license && $this->license->category) {
return $this->license->category->require_acceptance;
}
return false;
}
public function getEula()

View File

@@ -23,7 +23,7 @@ trait Loggable
* @since [v3.4]
* @return \App\Models\Actionlog
*/
public function logCheckout($note, $target, $action_date = null)
public function logCheckout($note, $target, $action_date = null, $originalValues = [])
{
$log = new Actionlog;
$log = $this->determineLogItemType($log);
@@ -62,6 +62,23 @@ trait Loggable
$log->action_date = date('Y-m-d H:i:s');
}
$changed = [];
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','expected_checkin']));
foreach ($originalValues as $key => $value) {
if ($key == 'action_date' && $value != $action_date) {
$changed[$key]['old'] = $value;
$changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');
} elseif ($value != $this->getAttributes()[$key]) {
$changed[$key]['old'] = $value;
$changed[$key]['new'] = $this->getAttributes()[$key];
}
}
if (!empty($changed)){
$log->log_meta = json_encode($changed);
}
$log->logaction('checkout');
return $log;
@@ -89,7 +106,7 @@ trait Loggable
* @since [v3.4]
* @return \App\Models\Actionlog
*/
public function logCheckin($target, $note, $action_date = null)
public function logCheckin($target, $note, $action_date = null, $originalValues = [])
{
$settings = Setting::getSettings();
$log = new Actionlog;
@@ -114,13 +131,9 @@ trait Loggable
}
}
$log->location_id = null;
$log->note = $note;
$log->action_date = $action_date;
if (! $log->action_date) {
$log->action_date = date('Y-m-d H:i:s');
}
if (! $log->action_date) {
$log->action_date = date('Y-m-d H:i:s');
@@ -130,6 +143,23 @@ trait Loggable
$log->user_id = Auth::user()->id;
}
$changed = [];
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','rtd_location_id','expected_checkin']));
foreach ($originalValues as $key => $value) {
if ($key == 'action_date' && $value != $action_date) {
$changed[$key]['old'] = $value;
$changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');
} elseif ($value != $this->getAttributes()[$key]) {
$changed[$key]['old'] = $value;
$changed[$key]['new'] = $this->getAttributes()[$key];
}
}
if (!empty($changed)){
$log->log_meta = json_encode($changed);
}
$log->logaction('checkin from');
// $params = [

View File

@@ -0,0 +1,137 @@
<?php
namespace App\Models\Traits;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Support\Collection;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Event;
use App\Models\DefaultValuesForCustomFields;
/*********************************
* Trait HasCustomFields
* @package App\Models\Traits
*
* How to use: declare a PHP function getFieldset that will return your fieldset (not the ID, the actual set)
*
*/
trait HasCustomFields
{
protected static function bootHasCustomFields()
{
// https://tech.chrishardie.com/2022/define-fire-listen-custom-laravel-model-events-trait/
static::registerModelEvent('validating', function ($model, $event) {
\Log::debug("Uh, something happened? Something good, maybe?");
\Log::debug("model: $model, event: $event");
self::augmentValidationRulesForCustomFields($model);
});
}
/***************
* @return CustomFieldset|null
*
* This function by default will use the "getFieldsetKey()" method to
* return the customFieldset (or null) for this particular item. If
* necessary, you can override this method if your getFieldsetKey()
* cannot respond to `->fieldset` or `->id`.
*/
public function getFieldset(): ?CustomFieldset {
$pivot = $this->getFieldsetKey();
if(is_int($pivot)) { //why does this look just like the other thing? (below, look for is_int()
return CustomFieldset::find($pivot);
}
return $pivot->fieldset;
}
/**********************
* @return Object|int|null
* (if this is in PHP 8.0, can we just put that as the signature?)
*
* This is the main method you have to override. It should either return an
* Object who you can call `->fieldset` on and get a fieldset object, and also
* be able to call `->id` on to get a unique key to be able to show custom fields.
* For example, for Assets, the element that is returned is the 'model' for the Asset.
* For something like Users, which will probably have only one universal set of custom fields,
* it should just return the Fieldset ID for it. Or, if there are no custom fields, it should
* return null
*/
abstract public function getFieldsetKey(): Object|int|null; // php v8 minimum, GOOD. TODO
/***********************
* @param int $fieldset_id
* @return Collection
*
* This is the main method you need to override to return a list of things that are *using* this fieldset
* The format is an array with keys: a URL, and values. So, for assets, it might return
* {
* "models/14" => "MacBook Pro 13 (model no: 12345)"
* }
*/
abstract public static function getFieldsetUsers(int $fieldset_id): array;
public static function augmentValidationRulesForCustomFields($model) {
\Log::debug("Augmenting validation rules for custom fields!!!!!!");
$fieldset = $model->getFieldset();
if ($fieldset) {
foreach ($fieldset->fields as $field){
if($field->format == 'BOOLEAN'){ // TODO - this 'feels' like entanglement of concerns?
$model->{$field->db_column} = filter_var($model->{$model->db_column}, FILTER_VALIDATE_BOOLEAN);
}
}
if(!$model->rules) {
$model->rules = [];
}
$model->rules += $model->getFieldset()->validation_rules();
\Log::debug("FINAL RULES ARE: ".print_r($model->rules,true));
}
}
public function getDefaultValue(CustomField $field)
{
$pivot = $this->getFieldsetKey(); // TODO - feels copypasta-ish?
$key_id = null;
if( is_int($pivot) ) { // TODO: *WHY* does this code repeat?!
$key_id = $pivot; // now we're done
} elseif( is_object($pivot) ) {
$key_id = $pivot?->id;
}
if(is_null($key_id)) {
return;
}
// TODO - begninng to think my custom scope really should be just an integer :/
return DefaultValuesForCustomFields::where('type',self::class)
->where('custom_field_id',$field->id)
->where('item_pivot_id',$key_id)->first()?->default_value;
}
public function customFill(Request $request, User $user, bool $shouldSetDefaults = false) {
if ($this->getFieldset()) {
foreach ($this->getFieldset()->fields as $field) {
if (is_array($request->input($field->db_column))) {
$field_value = implode(', ', $request->input($field->db_column));
} else {
$field_value = $request->input($field->db_column);
}
if ($shouldSetDefaults && (is_null($field_value) || $field_value === '')) {
$field_value = $this->getDefaultValue($field);
}
if ($field->field_encrypted == '1') {
if ($user->can('admin')) {
$this->{$field->db_column} = \Crypt::encrypt($field_value);
}
} else {
$this->{$field->db_column} = $request->input($field->db_column);
}
}
}
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use App\Http\Traits\UniqueUndeletedTrait;
use App\Models\Traits\HasCustomFields;
use App\Models\Traits\Searchable;
use App\Presenters\Presentable;
use DB;
@@ -31,6 +32,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
use Notifiable;
use Presentable;
use Searchable;
use HasCustomFields;
protected $hidden = ['password', 'remember_token', 'permissions', 'reset_password_code', 'persist_code'];
protected $table = 'users';
@@ -69,15 +71,12 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
];
protected $casts = [
'activated' => 'boolean',
'manager_id' => 'integer',
'location_id' => 'integer',
'company_id' => 'integer',
'vip' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
'autoassign_licenses' => 'boolean',
];
/**
@@ -103,6 +102,9 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'state' => 'min:2|max:191|nullable',
'country' => 'min:2|max:191|nullable',
'zip' => 'max:10|nullable',
'vip' => 'boolean',
'remote' => 'boolean',
'activated' => 'boolean',
];
/**
@@ -134,6 +136,20 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'manager' => ['first_name', 'last_name', 'username'],
];
public function getFieldsetKey(): object|int|null
{
// TODO/FIXME - that's hardcoded text, but what language should you use?! I don't know.
// also TODO - is this going to beat on the DB too hard?
return CustomFieldset::where('type', User::class)->first()?->id;
}
public static function getFieldsetUsers(int $fieldset_id): array
{
return [
'no_idea_what_id_to_put' => 'No idea what string to put?' // FIXME obvs.
];
}
/**
* Internally check the user permission for the given section
*
@@ -247,21 +263,12 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/
public function getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
$setting = Setting::getSettings();
/**
* Returns the complete name attribute with username
*
* @todo refactor this so it's less repetitive and dumb
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v2.0]
* @return string
*/
public function getCompleteNameAttribute()
{
return $this->last_name.', '.$this->first_name.' ('.$this->username.')';
if ($setting->name_display_format=='last_first') {
return ($this->last_name) ? $this->last_name.' '.$this->first_name : $this->first_name;
}
return $this->last_name ? $this->first_name.' '.$this->last_name : $this->first_name;
}
@@ -759,4 +766,26 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
{
return $this->locale;
}
public function getUserTotalCost(){
$asset_cost= 0;
$license_cost= 0;
$accessory_cost= 0;
foreach ($this->assets as $asset){
$asset_cost += $asset->purchase_cost;
$this->asset_cost = $asset_cost;
}
foreach ($this->licenses as $license){
$license_cost += $license->purchase_cost;
$this->license_cost = $license_cost;
}
foreach ($this->accessories as $accessory){
$accessory_cost += $accessory->purchase_cost;
$this->accessory_cost = $accessory_cost;
}
$this->total_user_cost = ($asset_cost + $accessory_cost + $license_cost);
return $this;
}
}

View File

@@ -74,9 +74,9 @@ class AssetObserver
$tag = $asset->asset_tag;
$prefix = $settings->auto_increment_prefix;
$number = substr($tag, strlen($prefix));
// IF - auto_increment_assets is on, AND (the prefix matches the start of the tag OR there is no prefix)
// IF - auto_increment_assets is on, AND (there is no prefix OR the prefix matches the start of the tag)
// AND the rest of the string after the prefix is all digits, THEN...
if ($settings->auto_increment_assets && (strpos($tag, $prefix) === 0 || $prefix=='') && preg_match('/\d+/',$number) === 1) {
if ($settings->auto_increment_assets && ($prefix=='' || strpos($tag, $prefix) === 0) && preg_match('/\d+/',$number) === 1) {
// new way of auto-trueing-up auto_increment ID's
$next_asset_tag = intval($number, 10) + 1;
// we had to use 'intval' because the $number could be '01234' and

View File

@@ -65,6 +65,14 @@ class AssetModelPresenter extends Presenter
'title' => trans('admin/models/table.modelnumber'),
'visible' => true,
],
[
'field' => 'min_amt',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('mail.min_QTY'),
'visible' => true,
],
[
'field' => 'assets_count',
'searchable' => false,

View File

@@ -2,7 +2,10 @@
namespace App\Presenters;
use App\Helpers\CustomFieldHelper;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use DateTime;
/**
@@ -282,25 +285,21 @@ class AssetPresenter extends Presenter
// models. We only pass the fieldsets that pertain to each asset (via their model) so that we
// don't junk up the REST API with tons of custom fields that don't apply
$fields = CustomField::whereHas('fieldset', function ($query) {
$query->whereHas('models');
})->get();
//only get fieldsets that have fields
$fieldsets = CustomFieldset::where("type", Asset::class)->whereHas('fields')->get();
$ids = [];
foreach($fieldsets as $fieldset) {
if (count($fieldset->customizables()) > 0) { //only get fieldsets that are 'in use'
$ids[] = $fieldset->id;
}
}
$fields = CustomField::whereIn('id',$ids)->get(); // FIXME: d'oh! this is wrong. We just got fieldsets, above. Now we're getting fields?
// Note: We do not need to e() escape the field names here, as they are already escaped when
// they are presented in the blade view. If we escape them here, custom fields with quotes in their
// name can break the listings page. - snipe
foreach ($fields as $field) {
$layout[] = [
'field' => 'custom_fields.'.$field->db_column,
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => $field->name,
'formatter'=> 'customFieldsFormatter',
'escape' => true,
'class' => ($field->field_encrypted == '1') ? 'css-padlock' : '',
'visible' => ($field->show_in_listview == '1') ? true : false,
];
$layout[] = CustomFieldHelper::present($field);
}
$layout[] = [
@@ -548,8 +547,10 @@ class AssetPresenter extends Presenter
public function dynamicWarrantyUrl()
{
$warranty_lookup_url = $this->model->model->manufacturer->warranty_lookup_url;
$url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale,$warranty_lookup_url));
$url = (str_replace('{SERIAL}',$this->model->serial,$url));
$url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale, $warranty_lookup_url));
$url = (str_replace('{SERIAL}', urlencode($this->model->serial), $url));
$url = (str_replace('{MODEL_NAME}', urlencode($this->model->model->name), $url));
$url = (str_replace('{MODEL_NUMBER}', urlencode($this->model->model->model_number), $url));
return $url;
}

View File

@@ -21,7 +21,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'name',
'searchable' => true,
'sortable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('general.name'),
'visible' => true,
@@ -44,14 +44,14 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_fields',
'searchable' => false,
'sortable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_fields'),
'visible' => true
], [
'field' => 'support_asset_tag',
'searchable' => false,
'sortable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_asset_tag'),
'visible' => true,
@@ -59,7 +59,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_1d_barcode',
'searchable' => false,
'sortable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_1d_barcode'),
'visible' => true,
@@ -67,7 +67,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_2d_barcode',
'searchable' => false,
'sortable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_2d_barcode'),
'visible' => true,
@@ -75,7 +75,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_logo',
'searchable' => false,
'sortable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_logo'),
'visible' => true,
@@ -83,7 +83,7 @@ class LabelPresenter extends Presenter
], [
'field' => 'support_title',
'searchable' => false,
'sortable' => true,
'sortable' => false,
'switchable' => true,
'title' => trans('admin/labels/table.support_title'),
'visible' => true,

View File

@@ -2,8 +2,14 @@
namespace App\Presenters;
use App\Helpers\CustomFieldHelper;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
@@ -359,6 +365,30 @@ class UserPresenter extends Presenter
],
];
// TODO - FIXME - this is all copy-pasta'ed from the AssetPresenter! <start>
//only get fieldsets that have fields
$fieldsets = CustomFieldset::where("type", User::class)->whereHas('fields')->get();
$ids = [];
foreach($fieldsets as $fieldset) {
if (count($fieldset->customizables()) > 0) { //only get fieldsets that are 'in use'
\Log::debug("Found a fieldset! It's: ".$fieldset->id);
$ids[] = $fieldset->id;
} else {
\Log::debug("Didn't find fieldset: ".$fieldset->id);
}
}
$fields = CustomField::whereHas('fieldset', function (Builder $query) use($ids) {
$query->whereIn('custom_fieldsets.id', $ids);
})->get();
// Note: We do not need to e() escape the field names here, as they are already escaped when
// they are presented in the blade view. If we escape them here, custom fields with quotes in their
// name can break the listings page. - snipe
foreach ($fields as $field) {
\Log::debug("iterating through fields!");
$layout[] = CustomFieldHelper::present($field);
}
return json_encode($layout);
}
@@ -433,7 +463,7 @@ class UserPresenter extends Presenter
*/
public function nameUrl()
{
return (string) link_to_route('users.show', $this->fullName(), $this->id);
return (string) link_to_route('users.show', $this->getFullNameAttribute(), $this->id);
}
/**

View File

@@ -75,12 +75,7 @@ class AppServiceProvider extends ServiceProvider
// Only load rollbar if there is a rollbar key and the app is in production
if (($this->app->environment('production')) && (config('logging.channels.rollbar.access_token'))) {
$this->app->register(\Rollbar\Laravel\RollbarServiceProvider::class);
}
// Only load dusk's service provider if the app is in local or develop mode
if ($this->app->environment(['local', 'develop'])) {
$this->app->register(\Laravel\Dusk\DuskServiceProvider::class);
}
}
$this->app->singleton('ArieTimmerman\Laravel\SCIMServer\SCIMConfig', SnipeSCIMConfig::class); // this overrides the default SCIM configuration with our own

View File

@@ -87,7 +87,7 @@ class AuthServiceProvider extends ServiceProvider
]);
$this->registerPolicies();
Passport::routes();
//Passport::routes(); //this is no longer required in newer passport versions
Passport::tokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
Passport::refreshTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));
Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years')));

View File

@@ -10,14 +10,8 @@
],
"license": "AGPL-3.0-or-later",
"type": "project",
"repositories": [
{
"type": "vcs",
"url": "https://github.com/grokability/laravel-scim-server"
}
],
"require": {
"php": ">=7.4.3 <8.2",
"php": "8.0 - 8.2",
"ext-curl": "*",
"ext-fileinfo": "*",
"ext-json": "*",
@@ -29,57 +23,49 @@
"barryvdh/laravel-debugbar": "^3.6",
"barryvdh/laravel-dompdf": "^2.0",
"doctrine/cache": "^1.10",
"doctrine/common": "^2.12",
"doctrine/dbal": "^3.1",
"doctrine/inflector": "^1.3",
"doctrine/instantiator": "^1.3",
"eduardokum/laravel-mail-auto-embed": "^1.0",
"eduardokum/laravel-mail-auto-embed": "^2.0",
"enshrined/svg-sanitize": "^0.15.0",
"erusev/parsedown": "^1.7",
"facade/ignition": "^2.10",
"fideloper/proxy": "^4.3",
"fruitcake/laravel-cors": "^2.2",
"spatie/laravel-ignition": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"intervention/image": "^2.5",
"javiereguiluz/easyslugger": "^1.0",
"laravel/framework": "^8.46",
"laravel/framework": "^10.0",
"laravel/helpers": "^1.4",
"laravel/passport": "^10.1",
"laravel/passport": "^11.0",
"laravel/slack-notification-channel": "^2.3",
"laravel/socialite": "^5.6",
"laravel/tinker": "^2.6",
"laravel/ui": "^3.3",
"laravel/ui": "^4.0",
"laravelcollective/html": "^6.2",
"lcobucci/clock": "^1.2.0|^2.0.0",
"lcobucci/jwt": "^3.4.5|^4.0.4",
"league/csv": "^9.7",
"league/flysystem-aws-s3-v3": "^1.0",
"league/flysystem-cached-adapter": "^1.1",
"league/flysystem-aws-s3-v3": "^3.0",
"livewire/livewire": "^2.4",
"mediconesystems/livewire-datatables": "^0.5.0",
"neitanod/forceutf8": "^2.0",
"nesbot/carbon": "^2.32",
"nunomaduro/collision": "^5.4",
"nunomaduro/collision": "^6.1",
"onelogin/php-saml": "^3.4",
"paragonie/constant_time_encoding": "^2.3",
"paragonie/sodium_compat": "^1.19",
"phpdocumentor/reflection-docblock": "^5.1",
"phpspec/prophecy": "^1.10",
"pragmarx/google2fa-laravel": "^1.3",
"rollbar/rollbar-laravel": "^7.0",
"spatie/laravel-backup": "^6.16",
"symfony/polyfill-mbstring": "^1.22",
"rollbar/rollbar-laravel": "^8.0",
"spatie/laravel-backup": "^8.0",
"tecnickcom/tc-lib-barcode": "^1.15",
"tecnickcom/tcpdf": "^6.5",
"unicodeveloper/laravel-password": "^1.0",
"watson/validating": "^6.1"
"watson/validating": "^8.1"
},
"require-dev": {
"brianium/paratest": "^6.6",
"brianium/paratest": "^v6.4.4",
"fakerphp/faker": "^1.16",
"laravel/dusk": "^6.25",
"mockery/mockery": "^1.4",
"nunomaduro/larastan": "^1.0",
"nunomaduro/larastan": "^2.0",
"nunomaduro/phpinsights": "^2.7",
"phpunit/php-token-stream": "^3.1",
"phpunit/phpunit": "^9.0",
@@ -107,7 +93,6 @@
},
"autoload-dev": {
"classmap": [
"tests/DuskTestCase.php",
"tests/TestCase.php"
],
"psr-4": {

7110
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -289,7 +289,6 @@ return [
Intervention\Image\ImageServiceProvider::class,
Collective\Html\HtmlServiceProvider::class,
Spatie\Backup\BackupServiceProvider::class,
Fideloper\Proxy\TrustedProxyServiceProvider::class,
PragmaRX\Google2FALaravel\ServiceProvider::class,
Laravel\Passport\PassportServiceProvider::class,
Laravel\Tinker\TinkerServiceProvider::class,

View File

@@ -69,7 +69,7 @@ return [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '3306'),
'port' => env('DB_PORT', 3306),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),

View File

@@ -1,5 +1,7 @@
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| DO NOT EDIT THIS FILE DIRECTLY.
@@ -57,6 +59,12 @@ return [
*
* @link https://symfony.com/doc/current/deployment/proxies.html
*/
'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_ALL,
'headers' => Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB,
];

View File

@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v6.1.2',
'full_app_version' => 'v6.1.2 - build 10938-g32747cafd',
'build_version' => '10938',
'app_version' => 'v6.2.0-pre',
'full_app_version' => 'v6.2.0-pre - build 11391-g319cb2305',
'build_version' => '11391',
'prerelease_version' => '',
'hash_version' => 'g32747cafd',
'full_hash' => 'v6.1.2-89-g32747cafd',
'hash_version' => 'g319cb2305',
'full_hash' => 'v6.2.0-pre-451-g319cb2305',
'branch' => 'develop',
);

View File

@@ -338,4 +338,15 @@ class AssetFactory extends Factory
{
return $this->state(['requestable' => false]);
}
public function withComplicatedCustomFields()
{
return $this->state(function () {
return [
'model_id' => function () {
return AssetModel::where('name','complicated')->first() ?? AssetModel::factory()->complicated();
}
];
});
}
}

View File

@@ -3,6 +3,7 @@
namespace Database\Factories;
use App\Models\AssetModel;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use App\Models\Depreciation;
use App\Models\Manufacturer;
@@ -429,4 +430,13 @@ class AssetModelFactory extends Factory
];
});
}
public function complicated()
{
return $this->state(function () {
return [
'name' => 'Complicated fieldset'
];
})->for(CustomFieldSet::factory()->complicated(),'fieldset');
}
}

View File

@@ -26,6 +26,7 @@ class CustomFieldFactory extends Factory
'format' => '',
'element' => 'text',
'auto_add_to_fieldsets' => '0',
'type' => 'App\\Models\\Asset'
];
}
@@ -74,9 +75,28 @@ class CustomFieldFactory extends Factory
{
return $this->state(function () {
return [
'name' => 'MAC Address',
'name' => 'MAC Address EXPLICIT',
'format' => 'regex:/^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/',
];
});
}
public function plainText()
{
return $this->state(function () {
return [
'name' => 'plain_text',
];
});
}
public function date()
{
return $this->state(function () {
return [
'name' => 'date',
'format' => 'date'
];
});
}
}

View File

@@ -2,6 +2,7 @@
namespace Database\Factories;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Database\Eloquent\Factories\Factory;
@@ -43,4 +44,16 @@ class CustomFieldsetFactory extends Factory
];
});
}
public function complicated()
{
//$mac = CustomField::factory()->macAddress()->create();
return $this->state(function () {
return [
'name' => 'complicated'
];
}) ->hasAttached(CustomField::factory()->macAddress(),['required' => false, 'order' => 0],'fields')
->hasAttached(CustomField::factory()->plainText(),['required' => true,'order' => 1],'fields')
->hasAttached(CustomField::factory()->date(),['required' => false, 'order' => 2],'fields');
}
}

View File

@@ -23,8 +23,8 @@ function updateLegacyColumnName($customfield)
$name_to_db_name = CustomField::name_to_db_name($customfield->name);
//\Log::debug('Trying to rename '.$name_to_db_name." to ".$customfield->convertUnicodeDbSlug()."...\n");
if (Schema::hasColumn(CustomField::$table_name, $name_to_db_name)) {
return Schema::table(CustomField::$table_name,
if (Schema::hasColumn('assets', $name_to_db_name)) {
return Schema::table('assets',
function ($table) use ($name_to_db_name, $customfield) {
$table->renameColumn($name_to_db_name, $customfield->convertUnicodeDbSlug());
}
@@ -81,8 +81,8 @@ class FixUtf8CustomFieldColumnNames extends Migration
// "_snipeit_imei_1" becomes "_snipeit_imei"
$legacyColumnName = (string) Str::of($currentColumnName)->replaceMatches('/_(\d)+$/', '');
if (Schema::hasColumn(CustomField::$table_name, $currentColumnName)) {
Schema::table(CustomField::$table_name, function (Blueprint $table) use ($currentColumnName, $legacyColumnName) {
if (Schema::hasColumn('assets', $currentColumnName)) {
Schema::table('assets', function (Blueprint $table) use ($currentColumnName, $legacyColumnName) {
$table->renameColumn(
$currentColumnName,
$legacyColumnName

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\CustomField;
use App\Models\Asset;
class AddTypeToCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('custom_fields', function (Blueprint $table) {
//
$table->text('type')->default('App\\Models\\Asset'); // TODO this default is needed for a not-nullable column, I guess? I don't like this because it will silently 'fix' errors we should properly 'fix'
});
CustomField::query()->update(['type' => Asset::class]); // TODO - is this still necessary with that 'default'?
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('custom_fields', function (Blueprint $table) {
//
$table->dropColumn('type');
});
}
}

View File

@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\CustomFieldset;
use App\Models\Asset;
class AddTypeToCustomFieldsets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('custom_fieldsets', function (Blueprint $table) {
//
$table->text('type')->default('App\\Models\\Asset');
});
CustomFieldset::query()->update(['type' => Asset::class]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('custom_fieldsets', function (Blueprint $table) {
//
$table->dropColumn('type');
});
}
}

View File

@@ -21,11 +21,7 @@ class AddLabel2Settings extends Migration
$table->string('label2_1d_type')->default('default');
$table->string('label2_2d_type')->default('default');
$table->string('label2_2d_target')->default('hardware_id');
$table->string('label2_fields')->default(
trans('admin/hardware/form.name').'=name;'.
trans('admin/hardware/form.serial').'=serial;'.
trans('admin/hardware/form.model').'=model.name;'
);
$table->string('label2_fields')->default('name=name;serial=serial;model=model.name;');
});
}

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class GeneralizeDefaultValuesForCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::rename('models_custom_fields', 'default_values_for_custom_fields');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::rename('default_values_for_custom_fields', 'models_custom_fields');
}
}

View File

@@ -0,0 +1,43 @@
<?php
use App\Models\Asset;
use App\Models\DefaultValuesForCustomFields;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddTypeColumnToDefaultValuesForCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('default_values_for_custom_fields', function (Blueprint $table) {
$table->string('type')->nullable();
$table->renameColumn('asset_model_id','item_pivot_id'); //this one works. okay. that's someting.
//$table->text('type')->nullable(false)->change();
});
DefaultValuesForCustomFields::query()->update(['type' => Asset::class]);
Schema::table('default_values_for_custom_fields', function (Blueprint $table) {
//$table->renameColumn('asset_model_id','item_pivot_id'); //this one works. okay. that's someting.
$table->string('type')->nullable(false)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('default_values_for_custom_fields', function (Blueprint $table) {
$table->dropColumn('type');
$table->renameColumn('item_pivot_id','asset_model_id');
});
}
}

View File

@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddLastCheckinToAssets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('assets', function (Blueprint $table) {
$table->dateTime('last_checkin')->after('last_checkout')->nullable();
});
DB::statement(
"UPDATE " . DB::getTablePrefix() . "assets SET last_checkin=(SELECT MAX(" . DB::getTablePrefix() . "action_logs.action_date) FROM " . DB::getTablePrefix() . "action_logs WHERE item_type='App\\\Models\\\Asset' AND " . DB::getTablePrefix() . "action_logs.item_id=" . DB::getTablePrefix() . "assets.id AND " . DB::getTablePrefix() . "action_logs.action_type='checkin from')"
);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('assets', function (Blueprint $table) {
$table->dropColumn('last_checkin');
});
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNameOrderingToSettings extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->string('name_display_format', 10)->after('alert_threshold')->nullable()->default('first_last');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn('name_display_format');
});
}
}

View File

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

24964
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@
"vue-template-compiler": "2.4.4"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0",
"@fortawesome/fontawesome-free": "^6.4.2",
"acorn": "^8.9.0",
"acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18",
@@ -40,15 +40,15 @@
"css-loader": "^4.0.0",
"ekko-lightbox": "^5.1.1",
"imagemin": "^8.0.1",
"jquery-form-validator": "^2.3.79",
"jquery-slimscroll": "^1.3.8",
"jquery-ui": "^1.13.2",
"jquery-ui-bundle": "^1.12.1",
"jquery.iframe-transport": "^1.0.0",
"jspdf-autotable": "^3.5.30",
"less": "^4.1.2",
"less-loader": "^5.0",
"less": "^4.2.0",
"less-loader": "^6.0",
"list.js": "^1.5.0",
"morris.js": "github:morrisjs/morris.js",
"papaparse": "^4.3.3",
"select2": "4.0.13",
"sheetjs": "^2.0.0",

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

25110
public/css/dist/all.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,135 +1 @@
#signature-pad {
padding-top: 250px;
margin: auto;
}
.m-signature-pad {
position: relative;
font-size: 10px;
width: 100%;
height: 300px;
border: 1px solid #e8e8e8;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.08) inset;
border-radius: 4px;
}
.m-signature-pad:before, .m-signature-pad:after {
position: absolute;
z-index: -1;
content: "";
width: 40%;
height: 10px;
left: 20px;
bottom: 10px;
background: transparent;
-webkit-transform: skew(-3deg) rotate(-3deg);
-moz-transform: skew(-3deg) rotate(-3deg);
-ms-transform: skew(-3deg) rotate(-3deg);
-o-transform: skew(-3deg) rotate(-3deg);
transform: skew(-3deg) rotate(-3deg);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.4);
}
.m-signature-pad:after {
left: auto;
right: 20px;
-webkit-transform: skew(3deg) rotate(3deg);
-moz-transform: skew(3deg) rotate(3deg);
-ms-transform: skew(3deg) rotate(3deg);
-o-transform: skew(3deg) rotate(3deg);
transform: skew(3deg) rotate(3deg);
}
.m-signature-pad--body {
position: absolute;
top: 20px;
bottom: 60px;
border: 1px solid #f4f4f4;
background-color: white;
}
.m-signature-pad--body
canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
}
.m-signature-pad--footer {
position: absolute;
left: 20px;
right: 20px;
bottom: 20px;
height: 40px;
}
.m-signature-pad--footer
.description {
color: #C3C3C3;
text-align: center;
font-size: 1.2em;
margin-top: 1.8em;
}
.m-signature-pad--footer
.button {
position: absolute;
bottom: 0;
}
.m-signature-pad--footer
.button.clear {
left: 0;
}
.m-signature-pad--footer
.button.save {
right: 0;
}
@media screen and (max-width: 1024px) {
.m-signature-pad {
top: 0;
left: 0;
right: 0;
bottom: 0;
width: auto;
height: auto;
min-width: 250px;
min-height: 140px;
margin: 5%;
}
}
@media screen and (min-device-width: 768px) and (max-device-width: 1024px) {
.m-signature-pad {
margin: 10%;
}
}
@media screen and (max-height: 320px) {
.m-signature-pad--body {
left: 0;
right: 0;
top: 0;
bottom: 32px;
}
.m-signature-pad--footer {
left: 20px;
right: 20px;
bottom: 4px;
height: 28px;
}
.m-signature-pad--footer
.description {
font-size: 1em;
margin-top: 1em;
}
}
#signature-pad{padding-top:250px;margin:auto}.m-signature-pad{position:relative;font-size:10px;width:100%;height:300px;border:1px solid #e8e8e8;background-color:#fff;box-shadow:0 1px 4px rgba(0,0,0,.27),0 0 40px rgba(0,0,0,.08) inset;border-radius:4px}.m-signature-pad:after,.m-signature-pad:before{position:absolute;z-index:-1;content:"";width:40%;height:10px;left:20px;bottom:10px;background:0 0;-webkit-transform:skew(-3deg) rotate(-3deg);-moz-transform:skew(-3deg) rotate(-3deg);-ms-transform:skew(-3deg) rotate(-3deg);-o-transform:skew(-3deg) rotate(-3deg);transform:skew(-3deg) rotate(-3deg);box-shadow:0 8px 12px rgba(0,0,0,.4)}.m-signature-pad:after{left:auto;right:20px;-webkit-transform:skew(3deg) rotate(3deg);-moz-transform:skew(3deg) rotate(3deg);-ms-transform:skew(3deg) rotate(3deg);-o-transform:skew(3deg) rotate(3deg);transform:skew(3deg) rotate(3deg)}.m-signature-pad--body{position:absolute;top:20px;bottom:60px;border:1px solid #f4f4f4;background-color:#fff}.m-signature-pad--body canvas{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:4px;box-shadow:0 0 5px rgba(0,0,0,.02) inset}.m-signature-pad--footer{position:absolute;left:20px;right:20px;bottom:20px;height:40px}.m-signature-pad--footer .description{color:#c3c3c3;text-align:center;font-size:1.2em;margin-top:1.8em}.m-signature-pad--footer .button{position:absolute;bottom:0}.m-signature-pad--footer .button.clear{left:0}.m-signature-pad--footer .button.save{right:0}@media screen and (max-width:1024px){.m-signature-pad{top:0;left:0;right:0;bottom:0;width:auto;height:auto;min-width:250px;min-height:140px;margin:5%}}@media screen and (min-device-width:768px) and (max-device-width:1024px){.m-signature-pad{margin:10%}}@media screen and (max-height:320px){.m-signature-pad--body{left:0;right:0;top:0;bottom:32px}.m-signature-pad--footer{left:20px;right:20px;bottom:4px;height:28px}.m-signature-pad--footer .description{font-size:1em;margin-top:1em}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,219 +1 @@
/*
* Skin: Black
* ----------
*/
.skin-black .main-header .navbar {
background-color: #111;
}
.skin-black .main-header .navbar .nav > li > a {
color: #fff;
}
.skin-black .main-header .navbar .nav > li > a:hover,
.skin-black .main-header .navbar .nav > li > a:active,
.skin-black .main-header .navbar .nav > li > a:focus,
.skin-black .main-header .navbar .nav .open > a,
.skin-black .main-header .navbar .nav .open > a:hover,
.skin-black .main-header .navbar .nav .open > a:focus,
.skin-black .main-header .navbar .nav > .active > a {
background: rgba(0, 0, 0, 0.1);
color: #f6f6f6;
}
.skin-black .main-header .navbar .sidebar-toggle {
color: #fff;
}
.skin-black .main-header .navbar .sidebar-toggle:hover {
color: #f6f6f6;
background: rgba(0, 0, 0, 0.1);
}
.skin-black .main-header .navbar .sidebar-toggle {
color: #fff;
}
.skin-black .main-header .navbar .sidebar-toggle:hover {
background-color: #040404;
}
@media (max-width: 767px) {
.skin-black .main-header .navbar .dropdown-menu li.divider {
background-color: rgba(255, 255, 255, 0.1);
}
.skin-black .main-header .navbar .dropdown-menu li a {
color: #333;
}
.skin-black .main-header .navbar .dropdown-menu li a:hover {
background: #040404;
}
}
.skin-black .main-header li.user-header {
background-color: #111;
}
.skin-black .content-header {
background: transparent;
}
.skin-black .wrapper,
.skin-black .main-sidebar,
.skin-black .left-side {
background-color: #222d32;
}
.skin-black .user-panel > .info,
.skin-black .user-panel > .info > a {
color: #fff;
}
.skin-black .sidebar-menu > li.header {
color: #4b646f;
background: #1a2226;
}
.skin-black .sidebar-menu > li > a {
border-left: 3px solid transparent;
}
.skin-black .sidebar-menu > li:hover > a,
.skin-black .sidebar-menu > li.active > a {
color: #fff;
background: #1e282c;
border-left-color: #111;
}
.skin-black .sidebar-menu > li > .treeview-menu {
margin: 0 1px;
background: #2c3b41;
}
.skin-black .sidebar a {
color: #b8c7ce;
}
.skin-black .sidebar a:hover {
text-decoration: none;
}
.skin-black .treeview-menu > li > a {
color: #8aa4af;
}
.skin-black .treeview-menu > li.active > a,
.skin-black .treeview-menu > li > a:hover {
color: #fff;
}
.skin-black .sidebar-form {
border-radius: 3px;
border: 1px solid #374850;
margin: 10px 10px;
}
.skin-black .sidebar-form input[type="text"],
.skin-black .sidebar-form .btn {
box-shadow: none;
background-color: #374850;
border: 1px solid transparent;
height: 35px;
transition: all 0.3s ease-in-out;
}
.skin-black .sidebar-form input[type="text"] {
color: #666;
border-top-left-radius: 2px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 2px;
}
.skin-black .sidebar-form input[type="text"]:focus,
.skin-black .sidebar-form input[type="text"]:focus + .input-group-btn .btn {
background-color: #fff;
color: #666;
}
.skin-black .sidebar-form input[type="text"]:focus + .input-group-btn .btn {
border-left-color: #fff;
}
.skin-black .sidebar-form .btn {
color: #999;
border-top-left-radius: 0;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 0;
}
.skin-black.layout-top-nav .main-header > .logo .logo-variant {
background-color: none;
}
.btn,
.btn:hover {
color: #000;
}
.btn.btn-primary,
.btn:hover.btn-primary,
.btn .btn-primary:link,
.btn:hover .btn-primary:link {
background-color: #505156;
border-color: #FFF;
color: #FFF;
}
.btna.btn-primary:hover,
.btn:hovera.btn-primary:hover {
background-color: #111;
border-color: #1f1f21;
color: #fff;
}
.btn.btn-white:link,
.btn:hover.btn-white:link {
color: #fff;
}
.btn.btn-white:hover,
.btn:hover.btn-white:hover {
color: #fff;
}
.btn.btn-white:visited,
.btn:hover.btn-white:visited {
color: #fff;
}
a {
color: var(--link);
}
a:hover {
color: var(--hover-link);
}
a:visited {
color: var(--visited-link);
}
.text-primary {
color: #000000;
}
:root {
--button-default: #000000;
--button-primary: #000000;
--button-hover: #000000;
--header: #111;
/* Use same as Header picker */
--text-main: #BBB;
--text-sub: #9b9b9b;
--link: #black;
/* Use same as Header picker, lighten by 70% */
--visited-link: #111;
/* Use same as Header picker, lighten by 70% */
--hover-link: #999999;
/* Use same as Header picker, lighten by 70% */
--nav-link: #FFF;
/* Use same as Header picker */
--light-link: #fff;
/* Use same as Header picker */
}
a.btn-info:link,
a.btn-warning:link,
a.btn-danger:link {
color: #FFF;
}
a.btn-info:visited,
a.btn-warning:visited,
a.btn-danger:visited {
color: #FFF;
}
.btn-danger.btn-sm.disabled {
color: #FFF;
}
.far fa-life-ring {
color: var(--link);
}
.sidebar-toggle-mobile {
color: #FFF !important;
}
.skin-black .main-header .navbar .nav > li > a,
.skin-black .main-header .navbar .nav > li > a {
text-decoration: none;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #111;
}
.search-highlight,
.search-highlight:hover {
background-color: #e9d15b;
}
.skin-black .main-header .navbar{background-color:#111}.skin-black .main-header .navbar .nav>li>a{color:#fff}.skin-black .main-header .navbar .nav .open>a,.skin-black .main-header .navbar .nav .open>a:focus,.skin-black .main-header .navbar .nav .open>a:hover,.skin-black .main-header .navbar .nav>.active>a,.skin-black .main-header .navbar .nav>li>a:active,.skin-black .main-header .navbar .nav>li>a:focus,.skin-black .main-header .navbar .nav>li>a:hover,.skin-black .main-header .navbar .sidebar-toggle:hover{background:rgba(0,0,0,.1);color:#f6f6f6}.skin-black .main-header .navbar .sidebar-toggle{color:#fff}.skin-black .main-header .navbar .sidebar-toggle:hover{background-color:#040404}@media (max-width:767px){.skin-black .main-header .navbar .dropdown-menu li.divider{background-color:hsla(0,0%,100%,.1)}.skin-black .main-header .navbar .dropdown-menu li a{color:#333}.skin-black .main-header .navbar .dropdown-menu li a:hover{background:#040404}}.skin-black .main-header li.user-header{background-color:#111}.skin-black .content-header{background:transparent}.skin-black .left-side,.skin-black .main-sidebar,.skin-black .wrapper{background-color:#222d32}.skin-black .user-panel>.info,.skin-black .user-panel>.info>a{color:#fff}.skin-black .sidebar-menu>li.header{background:#1a2226;color:#4b646f}.skin-black .sidebar-menu>li>a{border-left:3px solid transparent}.skin-black .sidebar-menu>li.active>a,.skin-black .sidebar-menu>li:hover>a{background:#1e282c;border-left-color:#111;color:#fff}.skin-black .sidebar-menu>li>.treeview-menu{background:#2c3b41;margin:0 1px}.skin-black .sidebar a{color:#b8c7ce}.skin-black .sidebar a:hover{text-decoration:none}.skin-black .treeview-menu>li>a{color:#8aa4af}.skin-black .treeview-menu>li.active>a,.skin-black .treeview-menu>li>a:hover{color:#fff}.skin-black .sidebar-form{border:1px solid #374850;border-radius:3px;margin:10px}.skin-black .sidebar-form .btn,.skin-black .sidebar-form input[type=text]{background-color:#374850;border:1px solid transparent;box-shadow:none;height:35px;transition:all .3s ease-in-out}.skin-black .sidebar-form input[type=text]{border-bottom-left-radius:2px;border-bottom-right-radius:0;border-top-left-radius:2px;border-top-right-radius:0;color:#666}.skin-black .sidebar-form input[type=text]:focus,.skin-black .sidebar-form input[type=text]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black .sidebar-form input[type=text]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black .sidebar-form .btn{border-bottom-left-radius:0;border-bottom-right-radius:2px;border-top-left-radius:0;border-top-right-radius:2px;color:#999}.skin-black.layout-top-nav .main-header>.logo .logo-variant{background-color:none}.btn,.btn:hover{color:#000}.btn .btn-primary:link,.btn.btn-primary,.btn:hover .btn-primary:link,.btn:hover.btn-primary{background-color:#505156;border-color:#fff;color:#fff}.btn:hovera.btn-primary:hover,.btna.btn-primary:hover{background-color:#111;border-color:#1f1f21;color:#fff}.btn.btn-white:hover,.btn.btn-white:link,.btn.btn-white:visited,.btn:hover.btn-white:hover,.btn:hover.btn-white:link,.btn:hover.btn-white:visited{color:#fff}a{color:var(--link)}a:hover{color:var(--hover-link)}a:visited{color:var(--visited-link)}.text-primary{color:#000}:root{--button-default:#000;--button-primary:#000;--button-hover:#000;--header:#111;--text-main:#bbb;--text-sub:#9b9b9b;--link:#black;--visited-link:#111;--hover-link:#999;--nav-link:#fff;--light-link:#fff}.btn-danger.btn-sm.disabled,a.btn-danger:link,a.btn-danger:visited,a.btn-info:link,a.btn-info:visited,a.btn-warning:link,a.btn-warning:visited{color:#fff}.far fa-life-ring{color:var(--link)}.sidebar-toggle-mobile{color:#fff!important}.skin-black .main-header .navbar .nav>li>a{text-decoration:none}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#111}.search-highlight,.search-highlight:hover{background-color:#e9d15b}

View File

@@ -1,219 +1 @@
/*
* Skin: Black
* ----------
*/
.skin-black .main-header .navbar {
background-color: #111;
}
.skin-black .main-header .navbar .nav > li > a {
color: #fff;
}
.skin-black .main-header .navbar .nav > li > a:hover,
.skin-black .main-header .navbar .nav > li > a:active,
.skin-black .main-header .navbar .nav > li > a:focus,
.skin-black .main-header .navbar .nav .open > a,
.skin-black .main-header .navbar .nav .open > a:hover,
.skin-black .main-header .navbar .nav .open > a:focus,
.skin-black .main-header .navbar .nav > .active > a {
background: rgba(0, 0, 0, 0.1);
color: #f6f6f6;
}
.skin-black .main-header .navbar .sidebar-toggle {
color: #fff;
}
.skin-black .main-header .navbar .sidebar-toggle:hover {
color: #f6f6f6;
background: rgba(0, 0, 0, 0.1);
}
.skin-black .main-header .navbar .sidebar-toggle {
color: #fff;
}
.skin-black .main-header .navbar .sidebar-toggle:hover {
background-color: #040404;
}
@media (max-width: 767px) {
.skin-black .main-header .navbar .dropdown-menu li.divider {
background-color: rgba(255, 255, 255, 0.1);
}
.skin-black .main-header .navbar .dropdown-menu li a {
color: #333;
}
.skin-black .main-header .navbar .dropdown-menu li a:hover {
background: #040404;
}
}
.skin-black .main-header li.user-header {
background-color: #111;
}
.skin-black .content-header {
background: transparent;
}
.skin-black .wrapper,
.skin-black .main-sidebar,
.skin-black .left-side {
background-color: #222d32;
}
.skin-black .user-panel > .info,
.skin-black .user-panel > .info > a {
color: #fff;
}
.skin-black .sidebar-menu > li.header {
color: #4b646f;
background: #1a2226;
}
.skin-black .sidebar-menu > li > a {
border-left: 3px solid transparent;
}
.skin-black .sidebar-menu > li:hover > a,
.skin-black .sidebar-menu > li.active > a {
color: #fff;
background: #1e282c;
border-left-color: #111;
}
.skin-black .sidebar-menu > li > .treeview-menu {
margin: 0 1px;
background: #2c3b41;
}
.skin-black .sidebar a {
color: #b8c7ce;
}
.skin-black .sidebar a:hover {
text-decoration: none;
}
.skin-black .treeview-menu > li > a {
color: #8aa4af;
}
.skin-black .treeview-menu > li.active > a,
.skin-black .treeview-menu > li > a:hover {
color: #fff;
}
.skin-black .sidebar-form {
border-radius: 3px;
border: 1px solid #374850;
margin: 10px 10px;
}
.skin-black .sidebar-form input[type="text"],
.skin-black .sidebar-form .btn {
box-shadow: none;
background-color: #374850;
border: 1px solid transparent;
height: 35px;
transition: all 0.3s ease-in-out;
}
.skin-black .sidebar-form input[type="text"] {
color: #666;
border-top-left-radius: 2px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 2px;
}
.skin-black .sidebar-form input[type="text"]:focus,
.skin-black .sidebar-form input[type="text"]:focus + .input-group-btn .btn {
background-color: #fff;
color: #666;
}
.skin-black .sidebar-form input[type="text"]:focus + .input-group-btn .btn {
border-left-color: #fff;
}
.skin-black .sidebar-form .btn {
color: #999;
border-top-left-radius: 0;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
border-bottom-left-radius: 0;
}
.skin-black.layout-top-nav .main-header > .logo .logo-variant {
background-color: none;
}
.btn,
.btn:hover {
color: #000;
}
.btn.btn-primary,
.btn:hover.btn-primary,
.btn .btn-primary:link,
.btn:hover .btn-primary:link {
background-color: #505156;
border-color: #FFF;
color: #FFF;
}
.btna.btn-primary:hover,
.btn:hovera.btn-primary:hover {
background-color: #111;
border-color: #1f1f21;
color: #fff;
}
.btn.btn-white:link,
.btn:hover.btn-white:link {
color: #fff;
}
.btn.btn-white:hover,
.btn:hover.btn-white:hover {
color: #fff;
}
.btn.btn-white:visited,
.btn:hover.btn-white:visited {
color: #fff;
}
a {
color: var(--link);
}
a:hover {
color: var(--hover-link);
}
a:visited {
color: var(--visited-link);
}
.text-primary {
color: #000000;
}
:root {
--button-default: #000000;
--button-primary: #000000;
--button-hover: #000000;
--header: #111;
/* Use same as Header picker */
--text-main: #BBB;
--text-sub: #9b9b9b;
--link: #black;
/* Use same as Header picker, lighten by 70% */
--visited-link: #111;
/* Use same as Header picker, lighten by 70% */
--hover-link: #999999;
/* Use same as Header picker, lighten by 70% */
--nav-link: #FFF;
/* Use same as Header picker */
--light-link: #fff;
/* Use same as Header picker */
}
a.btn-info:link,
a.btn-warning:link,
a.btn-danger:link {
color: #FFF;
}
a.btn-info:visited,
a.btn-warning:visited,
a.btn-danger:visited {
color: #FFF;
}
.btn-danger.btn-sm.disabled {
color: #FFF;
}
.far fa-life-ring {
color: var(--link);
}
.sidebar-toggle-mobile {
color: #FFF !important;
}
.skin-black .main-header .navbar .nav > li > a,
.skin-black .main-header .navbar .nav > li > a {
text-decoration: none;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #111;
}
.search-highlight,
.search-highlight:hover {
background-color: #e9d15b;
}
.skin-black .main-header .navbar{background-color:#111}.skin-black .main-header .navbar .nav>li>a{color:#fff}.skin-black .main-header .navbar .nav .open>a,.skin-black .main-header .navbar .nav .open>a:focus,.skin-black .main-header .navbar .nav .open>a:hover,.skin-black .main-header .navbar .nav>.active>a,.skin-black .main-header .navbar .nav>li>a:active,.skin-black .main-header .navbar .nav>li>a:focus,.skin-black .main-header .navbar .nav>li>a:hover,.skin-black .main-header .navbar .sidebar-toggle:hover{background:rgba(0,0,0,.1);color:#f6f6f6}.skin-black .main-header .navbar .sidebar-toggle{color:#fff}.skin-black .main-header .navbar .sidebar-toggle:hover{background-color:#040404}@media (max-width:767px){.skin-black .main-header .navbar .dropdown-menu li.divider{background-color:hsla(0,0%,100%,.1)}.skin-black .main-header .navbar .dropdown-menu li a{color:#333}.skin-black .main-header .navbar .dropdown-menu li a:hover{background:#040404}}.skin-black .main-header li.user-header{background-color:#111}.skin-black .content-header{background:0 0}.skin-black .left-side,.skin-black .main-sidebar,.skin-black .wrapper{background-color:#222d32}.skin-black .user-panel>.info,.skin-black .user-panel>.info>a{color:#fff}.skin-black .sidebar-menu>li.header{background:#1a2226;color:#4b646f}.skin-black .sidebar-menu>li>a{border-left:3px solid transparent}.skin-black .sidebar-menu>li.active>a,.skin-black .sidebar-menu>li:hover>a{background:#1e282c;border-left-color:#111;color:#fff}.skin-black .sidebar-menu>li>.treeview-menu{background:#2c3b41;margin:0 1px}.skin-black .sidebar a{color:#b8c7ce}.skin-black .sidebar a:hover{text-decoration:none}.skin-black .treeview-menu>li>a{color:#8aa4af}.skin-black .treeview-menu>li.active>a,.skin-black .treeview-menu>li>a:hover{color:#fff}.skin-black .sidebar-form{border:1px solid #374850;border-radius:3px;margin:10px}.skin-black .sidebar-form .btn,.skin-black .sidebar-form input[type=text]{background-color:#374850;border:1px solid transparent;box-shadow:none;height:35px;transition:all .3s ease-in-out}.skin-black .sidebar-form input[type=text]{border-bottom-left-radius:2px;border-bottom-right-radius:0;border-top-left-radius:2px;border-top-right-radius:0;color:#666}.skin-black .sidebar-form input[type=text]:focus,.skin-black .sidebar-form input[type=text]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black .sidebar-form input[type=text]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black .sidebar-form .btn{border-bottom-left-radius:0;border-bottom-right-radius:2px;border-top-left-radius:0;border-top-right-radius:2px;color:#999}.skin-black.layout-top-nav .main-header>.logo .logo-variant{background-color:none}.btn,.btn:hover{color:#000}.btn .btn-primary:link,.btn.btn-primary,.btn:hover .btn-primary:link,.btn:hover.btn-primary{background-color:#505156;border-color:#fff;color:#fff}.btn:hovera.btn-primary:hover,.btna.btn-primary:hover{background-color:#111;border-color:#1f1f21;color:#fff}.btn.btn-white:hover,.btn.btn-white:link,.btn.btn-white:visited,.btn:hover.btn-white:hover,.btn:hover.btn-white:link,.btn:hover.btn-white:visited{color:#fff}a{color:var(--link)}a:hover{color:var(--hover-link)}a:visited{color:var(--visited-link)}.text-primary{color:#000}:root{--button-default:#000;--button-primary:#000;--button-hover:#000;--header:#111;--text-main:#bbb;--text-sub:#9b9b9b;--link:#black;--visited-link:#111;--hover-link:#999;--nav-link:#fff;--light-link:#fff}.btn-danger.btn-sm.disabled,a.btn-danger:link,a.btn-danger:visited,a.btn-info:link,a.btn-info:visited,a.btn-warning:link,a.btn-warning:visited{color:#fff}.far fa-life-ring{color:var(--link)}.sidebar-toggle-mobile{color:#fff!important}.skin-black .main-header .navbar .nav>li>a{text-decoration:none}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#111}.search-highlight,.search-highlight:hover{background-color:#e9d15b}

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