Compare commits

..

93 Commits

Author SHA1 Message Date
snipe
53330514ac Updated field name
Signed-off-by: snipe <snipe@snipe.net>
2024-11-02 15:09:03 +00:00
snipe
ba226d9ba3 Added undeployable?
Signed-off-by: snipe <snipe@snipe.net>
2024-10-31 11:04:43 +00:00
snipe
95d136284d Few more fixes
Signed-off-by: snipe <snipe@snipe.net>
2024-10-28 22:02:48 +00:00
snipe
f5aea7b0d5 Changed status label to use status_type instead of booleans
Signed-off-by: snipe <snipe@snipe.net>
2024-10-28 11:37:17 +00:00
snipe
dfc63641dc Merge pull request #15550 from uberbrady/fix_multi_create_partial_failure
Fix multi create partial failure (fixes: [RB-18591])
2024-10-24 11:40:50 +01:00
snipe
07a51ec8b1 Merge pull request #15712 from Godmartinz/fix_import-progress-bar
Upgraded `livewire v3.5.9 => v3.5.12`
2024-10-24 10:03:10 +01:00
snipe
e0ec6795b5 Use crucial for seeded data
Signed-off-by: snipe <snipe@snipe.net>
2024-10-24 01:27:50 +01:00
snipe
5509d756b7 Merge pull request #15722 from Godmartinz/fix_component_factory
Fixed Component Factory: use manufacturer factory for `manufactuer_id`
2024-10-24 01:12:11 +01:00
Godfrey M
b16a978f1b uses manufacturer factory for manufactuer_id 2024-10-23 16:25:51 -07:00
snipe
55ba6279a4 Use trans_choice on alert menu
Signed-off-by: snipe <snipe@snipe.net>
2024-10-23 19:14:17 +01:00
snipe
a9eea830e3 Added manufacturer and model number to component seeders
Signed-off-by: snipe <snipe@snipe.net>
2024-10-23 19:05:34 +01:00
snipe
af564935d5 Merge pull request #15720 from snipe/15695_adds_manufacturer_and_model_number_to_components
Fixed #15695 - Added manufacturer and model_number to components
2024-10-23 17:56:04 +01:00
snipe
3ee76be7e3 Added manufacturer and model_number to components
Signed-off-by: snipe <snipe@snipe.net>
2024-10-23 17:50:22 +01:00
snipe
d58f87862c Merge pull request #15719 from snipe/#15717_adds_qty_to_consumable
Fixed #15717 - Added ability to checkout consumables in variable qty via API
2024-10-23 15:09:50 +01:00
snipe
0b6859c491 Added ability to checkout consumables in variable qty
Signed-off-by: snipe <snipe@snipe.net>
2024-10-23 15:05:35 +01:00
Godfrey M
727c0e458c remove translation 2024-10-22 14:34:39 -07:00
Godfrey M
870dc747db oops 2024-10-22 14:27:32 -07:00
Godfrey M
0fb3d83fac revert controller change 2024-10-22 14:26:54 -07:00
Godfrey M
0d59ccd6a6 upgraded livewire v3.5.9 => v3.5.12 2024-10-22 14:23:26 -07:00
snipe
5da3ce3564 Merge pull request #15711 from marcusmoore/fixes/custom-fieldset-checkboxes
Fixed custom field checkboxes on asset edit page
2024-10-22 22:10:20 +01:00
snipe
c3bbca30ad Merge pull request #15710 from snipe/fixes_lightbox_for_avif
Fixes #15701 - load avif files properly in lightbox
2024-10-22 22:07:50 +01:00
snipe
37f14fff3b Fixed typo
Signed-off-by: snipe <snipe@snipe.net>
2024-10-22 22:07:28 +01:00
snipe
eb6c51fabd Fixes #15701 - load avif files properly in lightbox
Signed-off-by: snipe <snipe@snipe.net>
2024-10-22 22:04:57 +01:00
Marcus Moore
5ecd2b6293 Default checkbox elements to an empty array 2024-10-22 14:04:05 -07:00
snipe
dccb788a88 Merge pull request #15691 from marcusmoore/fixes/get-id-for-current-user
Updated `Company::getIdForCurrentUser()` to return null in certain scenarios
2024-10-22 17:44:30 +01:00
Marcus Moore
d10fe77ee7 Merge branch 'develop' into fixes/get-id-for-current-user 2024-10-22 09:38:31 -07:00
snipe
5e1d792bba Merge pull request #15687 from NebelKreis/fix/dashboard-title-casing
Fixed #15686: Corrected capitalization for dashboard section titles by removing strtolower()
2024-10-22 16:52:28 +01:00
snipe
9cf71976f6 Fixed #15706 - Removed purchase order number from asset import
Signed-off-by: snipe <snipe@snipe.net>
2024-10-22 16:51:42 +01:00
snipe
15745d9737 Merge pull request #15566 from Godmartinz/status-label-error-message
Fixed Status Labels Error Message
2024-10-22 16:39:44 +01:00
snipe
bd97955b9e Bumped hash
Signed-off-by: snipe <snipe@snipe.net>
2024-10-22 16:38:46 +01:00
snipe
252d99421c Merge pull request #15689 from snipe/better_handle_inline_files
Better handle inline files in file listing
2024-10-22 16:26:48 +01:00
snipe
5767a98ad8 Merge pull request #15649 from bryanlopezinc/ImproveImporting
Improve import performance
2024-10-22 16:26:23 +01:00
snipe
0c820cbc0d Merge pull request #15598 from spencerrlongg/bug/custom_field_validation_issue
Custom Field Existence Validation Issue
2024-10-22 15:56:12 +01:00
snipe
db81701621 Merge branch 'develop' into better_handle_inline_files 2024-10-22 15:44:24 +01:00
snipe
a05c33febf Squashed commit of the following:
commit 147fcfb8eb
Merge: 58a3d09b5 fdcc17ca2
Author: snipe <snipe@snipe.net>
Date:   Tue Oct 22 15:12:55 2024 +0100

    Merge pull request #15676 from Toreg87/fixes/api_create_user_fmcs

    Fix user creation with FullMultipleCompanySupport enabled over API

commit 58a3d09b5f
Merge: 30a06a594 867fa2f36
Author: snipe <snipe@snipe.net>
Date:   Tue Oct 22 14:55:42 2024 +0100

    Merge pull request #15703 from marcusmoore/bug/sc-27188

    Linked accessory files in activity report

commit 30a06a5942
Merge: 6c6af78e0 ce3086317
Author: snipe <snipe@snipe.net>
Date:   Tue Oct 22 11:47:06 2024 +0100

    Merge pull request #15693 from marcusmoore/chore/remove-parallel-testing

    Removed brianium/paratest

commit 6c6af78e08
Merge: 9b06bbb6c 3f79fd7ea
Author: snipe <snipe@snipe.net>
Date:   Tue Oct 22 11:46:04 2024 +0100

    Merge pull request #15705 from marcusmoore/tests/icon-component-test

    Added test to ensure icon component does not end in newline

commit 3f79fd7ea7
Author: Marcus Moore <contact@marcusmoore.io>
Date:   Mon Oct 21 17:07:40 2024 -0700

    Add test to ensure icon component does not end in newline

commit 9b06bbb6c3
Merge: 46ad1d072 d7f70146f
Author: snipe <snipe@snipe.net>
Date:   Mon Oct 21 22:38:26 2024 +0100

    Merge pull request #15704 from marcusmoore/bug/remove-extra-icon

    Removed second icon in accessory file list

commit ce30863177
Author: Marcus Moore <contact@marcusmoore.io>
Date:   Mon Oct 21 13:57:04 2024 -0700

    Remove brianium/paratest dependency

commit d7f70146f4
Author: Marcus Moore <contact@marcusmoore.io>
Date:   Mon Oct 21 13:48:25 2024 -0700

    Remove extra icon in accessory file upload list

commit 867fa2f36e
Author: Marcus Moore <contact@marcusmoore.io>
Date:   Mon Oct 21 12:40:24 2024 -0700

    Display file in activity report for accessories

commit 0933a2d4ea
Author: Marcus Moore <contact@marcusmoore.io>
Date:   Thu Oct 17 18:01:48 2024 -0700

    Remove --parallel flag

commit 46ad1d072f
Merge: bcb4bd9eb 3cf746d7d
Author: snipe <snipe@snipe.net>
Date:   Thu Oct 17 15:29:47 2024 +0100

    Merge pull request #15680 from uberbrady/bulk_checkout_to_bulk_actions

    Bulk checkout to bulk actions

commit bcb4bd9eb4
Merge: 250037540 f50ccbcc4
Author: snipe <snipe@snipe.net>
Date:   Thu Oct 17 10:20:13 2024 +0100

    Merge pull request #15683 from Toreg87/fixes/outdated_comment

    Fix outdated comment in CompanyableTrait

commit f50ccbcc49
Author: Tobias Regnery <tobias.regnery@gmail.com>
Date:   Thu Oct 17 11:07:28 2024 +0200

    Fix outdated comment in CompanyableTrait

    As of commit 5800e8d the user model uses CompanyableTrait so remove this clearly outdated comment

commit 3cf746d7df
Author: Brady Wetherington <bwetherington@grokability.com>
Date:   Wed Oct 16 23:13:32 2024 +0100

    Rework the bulk checkout to not change how all checkouts work

commit 6b7af802af
Author: Brady Wetherington <bwetherington@grokability.com>
Date:   Thu Oct 10 13:28:23 2024 +0100

    Add 'bulk checkout' as one of the bulk actions in the bulk actions toolbar

commit fdcc17ca2c
Author: Tobias Regnery <tobias.regnery@gmail.com>
Date:   Wed Oct 16 11:18:24 2024 +0200

    Fix user creation with FullMultipleCompanySupport enabled over API

    It is currently possible as a non-superuser to create a new user or patch an existing user with arbitrary company over the API if FullMultipleCompanySupport is enabled.
    Altough a highly unlikely scenario as the user needs permission to create API keys and new users, it is a bug that should get fixed.

    Add a call to getIdForCurrentUser() to normalize the company_id if FullMultipleCompanySupport is enabled.

Signed-off-by: snipe <snipe@snipe.net>
2024-10-22 15:43:19 +01:00
snipe
147fcfb8eb Merge pull request #15676 from Toreg87/fixes/api_create_user_fmcs
Fix user creation with FullMultipleCompanySupport enabled over API
2024-10-22 15:12:55 +01:00
snipe
58a3d09b5f Merge pull request #15703 from marcusmoore/bug/sc-27188
Linked accessory files in activity report
2024-10-22 14:55:42 +01:00
snipe
30a06a5942 Merge pull request #15693 from marcusmoore/chore/remove-parallel-testing
Removed brianium/paratest
2024-10-22 11:47:06 +01:00
snipe
6c6af78e08 Merge pull request #15705 from marcusmoore/tests/icon-component-test
Added test to ensure icon component does not end in newline
2024-10-22 11:46:04 +01:00
Marcus Moore
3f79fd7ea7 Add test to ensure icon component does not end in newline 2024-10-21 17:07:40 -07:00
snipe
9b06bbb6c3 Merge pull request #15704 from marcusmoore/bug/remove-extra-icon
Removed second icon in accessory file list
2024-10-21 22:38:26 +01:00
Marcus Moore
ce30863177 Remove brianium/paratest dependency 2024-10-21 13:57:04 -07:00
Marcus Moore
d7f70146f4 Remove extra icon in accessory file upload list 2024-10-21 13:48:25 -07:00
Marcus Moore
867fa2f36e Display file in activity report for accessories 2024-10-21 12:40:24 -07:00
Marcus Moore
e1882ee6d2 Add comment 2024-10-21 12:21:45 -07:00
Marcus Moore
7eee239378 use is_numeric instead of is_int 2024-10-21 12:20:28 -07:00
Marcus Moore
4188849ae1 Add failing test case 2024-10-21 12:19:48 -07:00
snipe
787e651778 Fixed todos with log message
Signed-off-by: snipe <snipe@snipe.net>
2024-10-21 16:52:21 +01:00
snipe
ef9b6e3b07 Code cleanup
Signed-off-by: snipe <snipe@snipe.net>
2024-10-21 16:34:54 +01:00
snipe
06c599cc17 Added method to show or download file
Signed-off-by: snipe <snipe@snipe.net>
2024-10-21 16:34:03 +01:00
snipe
6105323877 Use plural class name for src
Signed-off-by: snipe <snipe@snipe.net>
2024-10-21 14:11:20 +01:00
Marcus Moore
0933a2d4ea Remove --parallel flag 2024-10-17 18:01:48 -07:00
Marcus Moore
a8d853c44a Remove focus group tags 2024-10-17 15:26:27 -07:00
Marcus Moore
7e1b47708e Fix failing test ensuring company id is an integer 2024-10-17 15:18:41 -07:00
Marcus Moore
979e4502ff Have getIdForCurrentUser method return null if FMCS enabled, user is not super admin, and does not have company 2024-10-17 15:14:39 -07:00
Marcus Moore
99dd51a965 Improve name 2024-10-17 14:53:18 -07:00
Marcus Moore
15c2169477 Scaffold additional tests 2024-10-17 14:31:05 -07:00
Marcus Moore
50fa6ce335 Scaffold tests 2024-10-17 14:12:22 -07:00
Marcus Moore
159a1d3f43 Be more explicit 2024-10-17 11:48:55 -07:00
snipe
d1149730be Apply blade component to files views
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 16:31:17 +01:00
snipe
46ad1d072f Merge pull request #15680 from uberbrady/bulk_checkout_to_bulk_actions
Bulk checkout to bulk actions
2024-10-17 15:29:47 +01:00
NebelKreis
780ed91a10 Fix: Removed strtolower() from dashboard titles
This fix ensures the correct capitalization in different languages.
2024-10-17 16:09:25 +02:00
snipe
bcb4bd9eb4 Merge pull request #15683 from Toreg87/fixes/outdated_comment
Fix outdated comment in CompanyableTrait
2024-10-17 10:20:13 +01:00
Tobias Regnery
f50ccbcc49 Fix outdated comment in CompanyableTrait
As of commit 5800e8d the user model uses CompanyableTrait so remove this clearly outdated comment
2024-10-17 11:07:28 +02:00
snipe
0e9b3c9119 Check for existence before trying to get the icon
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:27:39 +01:00
snipe
4933aa5784 Add StorageHelper to app config
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:27:04 +01:00
snipe
d67addc69e Removed filetype column - it’s dumb
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:21:43 +01:00
snipe
02c80ff18a Added comment
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:18:40 +01:00
snipe
c01190fac2 Conditionally add content-type
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:18:34 +01:00
snipe
017884f843 Added checks and filetype display
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:09:09 +01:00
snipe
c49921f50f Removed unused (maybe?) API endpoint
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:08:54 +01:00
snipe
c49abb6aea Refactor the UserFilesController show method for simpler inlining
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:08:38 +01:00
snipe
ccd2019448 Removed unusded use statements
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:08:04 +01:00
snipe
96191a5e93 Added method to decide if the file should be inlinable
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:07:54 +01:00
snipe
c56affd663 Added SVG icon
Signed-off-by: snipe <snipe@snipe.net>
2024-10-17 00:07:37 +01:00
Brady Wetherington
3cf746d7df Rework the bulk checkout to not change how all checkouts work 2024-10-16 23:13:32 +01:00
Brady Wetherington
6b7af802af Add 'bulk checkout' as one of the bulk actions in the bulk actions toolbar 2024-10-16 22:02:45 +01:00
Marcus Moore
604a964462 Improve scenario descriptions 2024-10-16 11:52:24 -07:00
Marcus Moore
2f72c66614 Add additional case 2024-10-16 11:30:06 -07:00
Tobias Regnery
fdcc17ca2c Fix user creation with FullMultipleCompanySupport enabled over API
It is currently possible as a non-superuser to create a new user or patch an existing user with arbitrary company over the API if FullMultipleCompanySupport is enabled.
Altough a highly unlikely scenario as the user needs permission to create API keys and new users, it is a bug that should get fixed.

Add a call to getIdForCurrentUser() to normalize the company_id if FullMultipleCompanySupport is enabled.
2024-10-16 11:47:18 +02:00
Marcus Moore
cba1a56040 Improve readability? 2024-10-15 17:38:11 -07:00
Marcus Moore
d9afde4610 Write failing test 2024-10-15 17:00:22 -07:00
Marcus Moore
42095c0167 Add reference link 2024-10-15 13:02:22 -07:00
snipe
2500375400 Merge pull request #15672 from uberbrady/ldap_location_fixes
Clean up how we use the '$location' in LDAP sync command
2024-10-15 17:34:35 +01:00
Brady Wetherington
e4e1d0d50a Clean up how we use the '$location' in LDAP sync command 2024-10-15 17:26:31 +01:00
snipe
16c8264e76 Merge pull request #15671 from snipe/bug/sc-27147
Bug/sc 27147
2024-10-15 16:42:34 +01:00
snipe
0ae9ce0aa9 Cannot sort by updated at on Users [sc-27147]
Signed-off-by: snipe <snipe@snipe.net>
2024-10-15 16:41:45 +01:00
snipe
50b8f180b3 More logical grouping in allow_columns
Signed-off-by: snipe <snipe@snipe.net>
2024-10-15 16:41:39 +01:00
bryanlopezinc
524a442724 Improved import performance 2024-10-10 23:32:07 +01:00
spencerrlongg
3153bbb13f dumb fix 2024-10-01 17:04:18 -05:00
Godfrey M
3f74ff25d2 fixed error message 2024-09-25 16:19:09 -07:00
Brady Wetherington
b6340532d7 Improve the error and success messages and linking 2024-09-24 17:15:39 +01:00
Brady Wetherington
c71411465a First pass at better-handling those annoying Rollbars we keep getting 2024-09-24 15:17:35 +01:00
107 changed files with 1430 additions and 4028 deletions

View File

@@ -76,4 +76,4 @@ jobs:
DB_DATABASE: snipeit
DB_PORT: ${{ job.services.mysql.ports[3306] }}
DB_USERNAME: root
run: php artisan test --parallel
run: php artisan test

View File

@@ -74,4 +74,4 @@ jobs:
DB_PORT: ${{ job.services.postgresql.ports[5432] }}
DB_USERNAME: snipeit
DB_PASSWORD: password
run: php artisan test --parallel
run: php artisan test

View File

@@ -58,4 +58,4 @@ jobs:
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: sqlite_testing
run: php artisan test --parallel
run: php artisan test

View File

@@ -137,23 +137,24 @@ class LdapSync extends Command
}
/* Determine which location to assign users to by default. */
$location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose
$default_location = null;
if ($this->option('location') != '') {
if ($location = Location::where('name', '=', $this->option('location'))->first()) {
if ($default_location = Location::where('name', '=', $this->option('location'))->first()) {
Log::debug('Location name ' . $this->option('location') . ' passed');
Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')');
}
} elseif ($this->option('location_id')) {
//TODO - figure out how or why this is an array?
foreach($this->option('location_id') as $location_id) {
if ($location = Location::where('id', '=', $location_id)->first()) {
if ($default_location = Location::where('id', '=', $location_id)->first()) {
Log::debug('Location ID ' . $location_id . ' passed');
Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')');
Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')');
}
}
}
if (! isset($location)) {
if (!isset($default_location)) {
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}
@@ -229,43 +230,44 @@ class LdapSync extends Command
for ($i = 0; $i < $results['count']; $i++) {
$item = [];
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? '';
$item['email'] = $results[$i][$ldap_map["email"]][0] ?? '';
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
$item['location_id'] = $results[$i]['location_id'] ?? '';
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? '';
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
$item = [];
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? '';
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? '';
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? '';
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? '';
$item['email'] = $results[$i][$ldap_map["email"]][0] ?? '';
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? '';
$item['location_id'] = $results[$i]['location_id'] ?? '';
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? '';
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? '';
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? '';
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? '';
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? '';
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? '';
$location = $default_location; //initially, set '$location' to the default_location (which may just be `null`)
// ONLY if you are using the "ldap_location" option *AND* you have an actual result
if ($ldap_map["location"] && $item['location']) {
$location = Location::firstOrCreate([
'name' => $item['location'],
]);
}
$department = Department::firstOrCreate([
'name' => $item['department'],
// ONLY if you are using the "ldap_location" option *AND* you have an actual result
if ($ldap_map["location"] && $item['location']) {
$location = Location::firstOrCreate([
'name' => $item['location'],
]);
}
$department = Department::firstOrCreate([
'name' => $item['department'],
]);
$user = User::where('username', $item['username'])->first();
if ($user) {
// Updating an existing user.
$item['createorupdate'] = 'updated';
} else {
// Creating a new user.
$user = new User;
$user->password = $user->noPassword();
$user->locale = app()->getLocale();
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
$item['createorupdate'] = 'created';
}
$user = User::where('username', $item['username'])->first();
if ($user) {
// Updating an existing user.
$item['createorupdate'] = 'updated';
} else {
// Creating a new user.
$user = new User;
$user->password = $user->noPassword();
$user->locale = app()->getLocale();
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
$item['createorupdate'] = 'created';
}
//If a sync option is not filled in on the LDAP settings don't populate the user field
if($ldap_map["username"] != null){
@@ -296,7 +298,7 @@ class LdapSync extends Command
$user->department_id = $department->id;
}
if($ldap_map["location"] != null){
$user->location_id = $location ? $location->id : null;
$user->location_id = $location?->id;
}
if($ldap_map["manager"] != null){
@@ -341,38 +343,38 @@ class LdapSync extends Command
}
}
// Sync activated state for Active Directory.
if ( !empty($ldap_map["active_flag"])) { // IF we have an 'active' flag set....
// ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them.
// (Specifically, we don't handle a value of '0.0' correctly)
$raw_value = @$results[$i][$ldap_map["active_flag"]][0];
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$boolean_cast = (bool)$raw_value;
// Sync activated state for Active Directory.
if (!empty($ldap_map["active_flag"])) { // IF we have an 'active' flag set....
// ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them.
// (Specifically, we don't handle a value of '0.0' correctly)
$raw_value = @$results[$i][$ldap_map["active_flag"]][0];
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$boolean_cast = (bool) $raw_value;
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
} elseif (array_key_exists('useraccountcontrol', $results[$i]) ) {
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
// ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in
} elseif (array_key_exists('useraccountcontrol', $results[$i])) {
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
// ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in
/* The following is _probably_ the correct logic, but we can't use it because
some users may have been dependent upon the previous behavior, and this
could cause additional access to be available to users they don't want
to allow to log in.
/* The following is _probably_ the correct logic, but we can't use it because
some users may have been dependent upon the previous behavior, and this
could cause additional access to be available to users they don't want
to allow to log in.
$useraccountcontrol = $results[$i]['useraccountcontrol'][0];
if(
// based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties
($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT
!($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE
!($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT
) {
$user->activated = 1;
} else {
$user->activated = 0;
} */
$enabled_accounts = [
$useraccountcontrol = $results[$i]['useraccountcontrol'][0];
if(
// based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties
($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT
!($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE
!($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT
) {
$user->activated = 1;
} else {
$user->activated = 0;
} */
$enabled_accounts = [
'512', // 0x200 NORMAL_ACCOUNT
'544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD
'66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD
@@ -385,44 +387,47 @@ class LdapSync extends Command
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
'1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED,
];
$user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;
];
$user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0;
// If we're not using AD, and there isn't an activated flag set, activate all users
} /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active.
already-existing accounts will be however the administrator has set them */
} /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active.
already-existing accounts will be however the administrator has set them */
if ($item['ldap_location_override'] == true) {
$user->location_id = $item['location_id'];
} elseif ((isset($location)) && (! empty($location))) {
if ((is_array($location)) && (array_key_exists('id', $location))) {
$user->location_id = $location['id'];
} elseif (is_object($location)) {
$user->location_id = $location->id;
}
if ($item['ldap_location_override'] == true) {
$user->location_id = $item['location_id'];
} elseif ((isset($location)) && (!empty($location))) {
if ((is_array($location)) && (array_key_exists('id', $location))) {
$user->location_id = $location['id'];
} elseif (is_object($location)) {
$user->location_id = $location->id; //THIS is the magic line, this should do it.
}
$location = null;
$user->ldap_import = 1;
}
// TODO - should we be NULLING locations if $location is really `null`, and that's what we came up with?
// will that conflict with any overriding setting that the user set? Like, if they moved someone from
// the 'null' location to somewhere, we wouldn't want to try to override that, right?
$location = null;
$user->ldap_import = 1;
$errors = '';
$errors = '';
if ($user->save()) {
$item['note'] = $item['createorupdate'];
$item['status'] = 'success';
if ( $item['createorupdate'] === 'created' && $ldap_default_group) {
$user->groups()->attach($ldap_default_group);
}
} else {
foreach ($user->getErrors()->getMessages() as $key => $err) {
$errors .= $err[0];
}
$item['note'] = $errors;
$item['status'] = 'error';
if ($user->save()) {
$item['note'] = $item['createorupdate'];
$item['status'] = 'success';
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
$user->groups()->attach($ldap_default_group);
}
array_push($summary, $item);
} else {
foreach ($user->getErrors()->getMessages() as $key => $err) {
$errors .= $err[0];
}
$item['note'] = $errors;
$item['status'] = 'error';
}
array_push($summary, $item);
}
if ($this->option('summary')) {

View File

@@ -6,6 +6,7 @@ use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Helper\ProgressIndicator;
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
@@ -29,6 +30,11 @@ class ObjectImportCommand extends Command
*/
protected $description = 'Import Items from CSV';
/**
* The progress indicator instance.
*/
protected ProgressIndicator $progressIndicator;
/**
* Create a new command instance.
*
@@ -39,8 +45,6 @@ class ObjectImportCommand extends Command
parent::__construct();
}
private $bar;
/**
* Execute the console command.
*
@@ -48,6 +52,8 @@ class ObjectImportCommand extends Command
*/
public function handle()
{
$this->progressIndicator = new ProgressIndicator($this->output);
$filename = $this->argument('filename');
$class = title_case($this->option('item-type'));
$classString = "App\\Importer\\{$class}Importer";
@@ -61,46 +67,25 @@ class ObjectImportCommand extends Command
// This $logFile/useFiles() bit is currently broken, so commenting it out for now
// $logFile = $this->option('logfile');
// Log::useFiles($logFile);
$this->comment('======= Importing Items from '.$filename.' =========');
$this->progressIndicator->start('======= Importing Items from '.$filename.' =========');
$importer->import();
$this->bar = null;
if (! empty($this->errors)) {
$this->comment('The following Errors were encountered.');
foreach ($this->errors as $asset => $error) {
$this->comment('Error: Item: '.$asset.' failed validation: '.json_encode($error));
}
} else {
$this->comment('All Items imported successfully!');
}
$this->comment('');
$this->progressIndicator->finish('Import finished.');
}
public function errorCallback($item, $field, $errorString)
public function errorCallback($item, $field, $error)
{
$this->errors[$item->name][$field] = $errorString;
$this->output->write("\x0D\x1B[2K");
$this->warn('Error: Item: '.$item->name.' failed validation: '.json_encode($error));
}
public function progress($count)
public function progress($importedItemsCount)
{
if (! $this->bar) {
$this->bar = $this->output->createProgressBar($count);
}
static $index = 0;
$index++;
if ($index < $count) {
$this->bar->advance();
} else {
$this->bar->finish();
}
$this->progressIndicator->advance();
}
// Tracks the current item for error messages
private $updating;
// An array of errors encountered while parsing
private $errors;
/**
* Log a message to file, configurable by the --log-file parameter.
* If a warning message is passed, we'll spit it to the console as well.

View File

@@ -553,7 +553,7 @@ class Helper
*/
public static function statusLabelList()
{
$statuslabel_list = ['' => trans('general.select_statuslabel')] + Statuslabel::orderBy('default_label', 'desc')->orderBy('name', 'asc')->orderBy('deployable', 'desc')
$statuslabel_list = ['' => trans('general.select_statuslabel')] + Statuslabel::orderBy('default_label', 'desc')->orderBy('name', 'asc')->orderBy('status_type', 'desc')
->pluck('name', 'id')->toArray();
return $statuslabel_list;
@@ -572,9 +572,9 @@ class Helper
*/
public static function deployableStatusLabelList()
{
$statuslabel_list = Statuslabel::where('deployable', '=', '1')->orderBy('default_label', 'desc')
$statuslabel_list = Statuslabel::where('status_type', 'deployable')->orderBy('default_label', 'desc')
->orderBy('name', 'asc')
->orderBy('deployable', 'desc')
->orderBy('status_type', 'desc')
->pluck('name', 'id')->toArray();
return $statuslabel_list;
@@ -1123,6 +1123,7 @@ class Helper
'png' => 'far fa-image',
'webp' => 'far fa-image',
'avif' => 'far fa-image',
'svg' => 'fas fa-vector-square',
// word
'doc' => 'far fa-file-word',
'docx' => 'far fa-file-word',
@@ -1135,7 +1136,7 @@ class Helper
//Text
'txt' => 'far fa-file-alt',
'rtf' => 'far fa-file-alt',
'xml' => 'far fa-file-alt',
'xml' => 'fas fa-code',
// Misc
'pdf' => 'far fa-file-pdf',
'lic' => 'far fa-save',
@@ -1148,41 +1149,7 @@ class Helper
return 'far fa-file';
}
public static function show_file_inline($filename)
{
$extension = substr(strrchr($filename, '.'), 1);
if ($extension) {
switch ($extension) {
case 'jpg':
case 'jpeg':
case 'gif':
case 'png':
case 'webp':
case 'avif':
return true;
break;
default:
return false;
}
}
return false;
}
/**
* Generate a random encrypted password.
*
* @author Wes Hulette <jwhulette@gmail.com>
*
* @since 5.0.0
*
* @return string
*/
public static function generateEncyrptedPassword(): string
{
return bcrypt(self::generateUnencryptedPassword());
}
/**
* Get a random unencrypted password.

View File

@@ -7,6 +7,7 @@ use Illuminate\Http\Response;
use Illuminate\Http\RedirectResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
class StorageHelper
{
public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse
@@ -25,4 +26,64 @@ class StorageHelper
return Storage::disk($disk)->download($filename);
}
}
/**
* This determines the file types that should be allowed inline and checks their fileinfo extension
* to determine that they are safe to display inline.
*
* @author <A. Gianotto> [<snipe@snipe.net]>
* @since v7.0.14
* @param $file_with_path
* @return bool
*/
public static function allowSafeInline($file_with_path) {
$allowed_inline = [
'pdf',
'svg',
'jpg',
'gif',
'svg',
'avif',
'webp',
'png',
];
// The file exists and is allowed to be displayed inline
if (Storage::exists($file_with_path) && (in_array(pathinfo($file_with_path, PATHINFO_EXTENSION), $allowed_inline))) {
return true;
}
return false;
}
/**
* Decide whether to show the file inline or download it.
*/
public static function showOrDownloadFile($file, $filename) {
$headers = [];
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
// This is NOT allowed as inline - force it to be displayed as text in the browser
if (self::allowSafeInline($file) != true) {
$headers = array_merge($headers, ['Content-Type' => 'text/plain']);
}
}
// Everything else seems okay, but the file doesn't exist on the server.
if (Storage::missing($file)) {
throw new FileNotFoundException();
}
return Storage::download($file, $filename, $headers);
}
}

View File

@@ -106,50 +106,29 @@ class AccessoriesFilesController extends Controller
* @param int $accessoryId
* @param int $fileId
*/
public function show($accessoryId = null, $fileId = null, $download = true) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse
{
Log::debug('Private filesystem is: '.config('filesystems.default'));
$accessory = Accessory::find($accessoryId);
// the accessory is valid
if (isset($accessory->id)) {
if ($accessory = Accessory::find($accessoryId)) {
$this->authorize('view', $accessory);
$this->authorize('accessories.files', $accessory);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found'));
}
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) {
$file = 'private_uploads/accessories/'.$log->filename;
$file = 'private_uploads/accessories/'.$log->filename;
if (Storage::missing($file)) {
Log::debug('FILE DOES NOT EXISTS for '.$file);
Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->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);
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found'));
}
}
return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
return redirect()->route('accessories.index')->with('error', trans('general.file_not_found'));
}
}

View File

@@ -61,7 +61,7 @@ class AssetsController extends Controller
if ($action == 'audit') {
$action = 'audits';
}
$filter_non_deprecable_assets = false;
$filter_non_depreciable_assets = false;
/**
* This looks MAD janky (and it is), but the AssetsController@index does a LOT of heavy lifting throughout the
@@ -75,7 +75,7 @@ class AssetsController extends Controller
* which would have been far worse of a mess. *sad face* - snipe (Sept 1, 2021)
*/
if (Route::currentRouteName()=='api.depreciation-report.index') {
$filter_non_deprecable_assets = true;
$filter_non_depreciable_assets = true;
$transformer = 'App\Http\Transformers\DepreciationReportTransformer';
$this->authorize('reports.view');
} else {
@@ -130,9 +130,9 @@ class AssetsController extends Controller
'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
if ($filter_non_deprecable_assets) {
$non_deprecable_models = AssetModel::select('id')->whereNotNull('depreciation_id')->get();
$assets->InModelList($non_deprecable_models->toArray());
if ($filter_non_depreciable_assets) {
$non_depreciable_models = AssetModel::select('id')->whereNotNull('depreciation_id')->get();
$assets->InModelList($non_depreciable_models->toArray());
}
@@ -206,18 +206,14 @@ class AssetsController extends Controller
case 'Pending':
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 0)
->where('status_alias.pending', '=', 1)
->where('status_alias.archived', '=', 0);
->where('status_alias.status_type', '=', 'pending');
});
break;
case 'RTD':
$assets->whereNull('assets.assigned_to')
->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 1)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 0);
->where('status_alias.status_type', '=', 'deployable');
});
break;
case 'Undeployable':
@@ -226,20 +222,15 @@ class AssetsController extends Controller
case 'Archived':
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 0)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 1);
->where('status_alias.status_type', '=', 'archived');
});
break;
case 'Requestable':
$assets->where('assets.requestable', '=', 1)
->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.deployable', '=', 1)
->where('status_alias.pending', '=', 0)
->where('status_alias.archived', '=', 0);
->where('status_alias.status_type', '=', 'deployable');
});
break;
case 'Deployed':
// more sad, horrible workarounds for laravel bugs when doing full text searches
@@ -256,7 +247,7 @@ class AssetsController extends Controller
// terrible workaround for complex-query Laravel bug in fulltext
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id')
->where('status_alias.archived', '=', 0);
->where('status_alias.status_type', '!=', 'archived');
});
// If there is a status ID, don't take show_archived_in_list into consideration
@@ -265,6 +256,7 @@ class AssetsController extends Controller
$join->on('status_alias.id', '=', 'assets.status_id');
});
}
break;
}
@@ -574,8 +566,8 @@ class AssetsController extends Controller
}
if ($asset->assetstatus->getStatuslabelType() == 'pending') {
$asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
if ($asset->assetstatus->status_label == 'pending') {
$asset->use_text .= '('.$asset->assetstatus->status_label.')';
}
$asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null;

View File

@@ -38,6 +38,7 @@ class ComponentsController extends Controller
'name',
'min_amt',
'order_number',
'model_number',
'serial',
'purchase_date',
'purchase_cost',
@@ -47,7 +48,7 @@ class ComponentsController extends Controller
];
$components = Component::select('components.*')
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser');
->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer');
if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search'));
@@ -69,6 +70,14 @@ class ComponentsController extends Controller
$components->where('supplier_id', '=', $request->input('supplier_id'));
}
if ($request->filled('manufacturer_id')) {
$components->where('manufacturer_id', '=', $request->input('manufacturer_id'));
}
if ($request->filled('model_number')) {
$components->where('model_number', '=', $request->input('model_number'));
}
if ($request->filled('location_id')) {
$components->where('location_id', '=', $request->input('location_id'));
}
@@ -98,6 +107,9 @@ class ComponentsController extends Controller
case 'supplier':
$components = $components->OrderSupplier($order);
break;
case 'manufacturer':
$components = $components->OrderManufacturer($order);
break;
case 'created_by':
$components = $components->OrderByCreatedBy($order);
break;

View File

@@ -258,6 +258,8 @@ class ConsumablesController extends Controller
$this->authorize('checkout', $consumable);
$consumable->checkout_qty = $request->input('checkout_qty', 1);
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable')));
@@ -268,6 +270,12 @@ class ConsumablesController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')])));
}
// Make sure there is at least one available to checkout
if ($consumable->numRemaining() <= 0 || $consumable->checkout_qty > $consumable->numRemaining()) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable', ['requested' => $consumable->checkout_qty, 'remaining' => $consumable->numRemaining() ])));
}
// Check if the user exists - @TODO: this should probably be handled via validation, not here??
if (!$user = User::find($request->input('assigned_to'))) {
@@ -278,7 +286,8 @@ class ConsumablesController extends Controller
// Update the consumable data
$consumable->assigned_to = $request->input('assigned_to');
$consumable->users()->attach($consumable->id,
for ($i = 0; $i < $consumable->checkout_qty; $i++) {
$consumable->users()->attach($consumable->id,
[
'consumable_id' => $consumable->id,
'created_by' => $user->id,
@@ -286,6 +295,8 @@ class ConsumablesController extends Controller
'note' => $request->input('note'),
]
);
}
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));

View File

@@ -60,7 +60,8 @@ class ManufacturersController extends Controller
->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
->withCount('consumables as consumables_count')
->withCount('accessories as accessories_count');
->withCount('accessories as accessories_count')
->withCount('components as components_count');
if ($request->input('deleted') == 'true') {
$manufacturers->onlyTrashed();

View File

@@ -32,7 +32,8 @@ class StatuslabelsController extends Controller
'assets_count',
'color',
'notes',
'default_label'
'default_label',
'status_type',
];
$statuslabels = Statuslabel::with('adminuser')->withCount('assets as assets_count');
@@ -49,13 +50,12 @@ class StatuslabelsController extends Controller
// if a status_type is passed, filter by that
if ($request->filled('status_type')) {
if (strtolower($request->input('status_type')) == 'pending') {
$statuslabels = $statuslabels->Pending();
} elseif (strtolower($request->input('status_type')) == 'archived') {
$statuslabels = $statuslabels->Archived();
} elseif (strtolower($request->input('status_type')) == 'deployable') {
$statuslabels = $statuslabels->Deployable();
$statuslabels->where('status_type', '=', 'pending');
} elseif (strtolower($request->input('status_type')) == 'archived') $statuslabels->where('status_type', '=', 'archived');
elseif (strtolower($request->input('status_type')) == 'deployable') {
$statuslabels->where('status_type', '=', 'deployable');
} elseif (strtolower($request->input('status_type')) == 'undeployable') {
$statuslabels = $statuslabels->Undeployable();
$statuslabels->whereNot('status_type', 'deployable');
}
}
@@ -92,19 +92,11 @@ class StatuslabelsController extends Controller
public function store(Request $request) : JsonResponse
{
$this->authorize('create', Statuslabel::class);
$request->except('deployable', 'pending', 'archived');
if (! $request->filled('type')) {
return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']]), 500);
}
$statuslabel = new Statuslabel;
$statuslabel->fill($request->all());
$statusType = Statuslabel::getStatuslabelTypesForDB($request->input('type'));
$statuslabel->deployable = $statusType['deployable'];
$statuslabel->pending = $statusType['pending'];
$statuslabel->archived = $statusType['archived'];
$statuslabel->status_type = $request->input('status_type');
$statuslabel->color = $request->input('color');
$statuslabel->show_in_nav = $request->input('show_in_nav', 0);
$statuslabel->default_label = $request->input('default_label', 0);
@@ -145,20 +137,12 @@ class StatuslabelsController extends Controller
{
$this->authorize('update', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id);
$request->except('deployable', 'pending', 'archived');
if (! $request->filled('type')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Status label type is required.'));
}
$statuslabel->fill($request->all());
$statusType = Statuslabel::getStatuslabelTypesForDB($request->input('type'));
$statuslabel->deployable = $statusType['deployable'];
$statuslabel->pending = $statusType['pending'];
$statuslabel->archived = $statusType['archived'];
$statuslabel->status_type = $request->input('status_type');
$statuslabel->color = $request->input('color');
$statuslabel->show_in_nav = $request->input('show_in_nav', 0);
$statuslabel->default_label = $request->input('default_label', 0);
@@ -206,7 +190,7 @@ class StatuslabelsController extends Controller
$this->authorize('view', Statuslabel::class);
if (Setting::getSettings()->show_archived_in_list == 0 ) {
$statuslabels = Statuslabel::withCount('assets')->where('archived','0')->get();
$statuslabels = Statuslabel::withCount('assets')->whereNot('status_type','archived')->get();
} else {
$statuslabels = Statuslabel::withCount('assets')->get();
}
@@ -300,7 +284,7 @@ class StatuslabelsController extends Controller
public function checkIfDeployable($id) : string
{
$statuslabel = Statuslabel::findOrFail($id);
if ($statuslabel->getStatuslabelType() == 'deployable') {
if ($statuslabel->status_type == 'deployable') {
return '1';
}
@@ -318,22 +302,22 @@ class StatuslabelsController extends Controller
{
$this->authorize('view.selectlists');
$statuslabels = Statuslabel::orderBy('default_label', 'desc')->orderBy('name', 'asc')->orderBy('deployable', 'desc');
$statuslabels = Statuslabel::orderBy('default_label', 'desc')->orderBy('name', 'asc')->orderBy('status_type', 'desc');
if ($request->filled('search')) {
$statuslabels = $statuslabels->where('name', 'LIKE', '%'.$request->get('search').'%');
}
if ($request->filled('deployable')) {
$statuslabels = $statuslabels->where('deployable', '=', '1');
$statuslabels = $statuslabels->where('status_type', '=', 'deployable');
}
if ($request->filled('pending')) {
$statuslabels = $statuslabels->where('pending', '=', '1');
$statuslabels = $statuslabels->where('status_type', '=', 'pending');
}
if ($request->filled('archived')) {
$statuslabels = $statuslabels->where('archived', '=', '1');
$statuslabels = $statuslabels->where('status_type', '=', 'archived');
}
$statuslabels = $statuslabels->orderBy('name', 'ASC')->paginate(50);

View File

@@ -14,6 +14,7 @@ use App\Http\Transformers\UsersTransformer;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Accessory;
use App\Models\Company;
use App\Models\Consumable;
use App\Models\License;
use App\Models\User;
@@ -42,13 +43,14 @@ class UsersController extends Controller
$users = User::select([
'users.activated',
'users.created_by',
'users.address',
'users.avatar',
'users.city',
'users.company_id',
'users.country',
'users.created_by',
'users.created_at',
'users.updated_at',
'users.deleted_at',
'users.department_id',
'users.email',
@@ -67,7 +69,6 @@ class UsersController extends Controller
'users.state',
'users.two_factor_enrolled',
'users.two_factor_optin',
'users.updated_at',
'users.username',
'users.zip',
'users.remote',
@@ -255,6 +256,7 @@ class UsersController extends Controller
'groups',
'activated',
'created_at',
'updated_at',
'two_factor_enrolled',
'two_factor_optin',
'last_login',
@@ -370,6 +372,7 @@ class UsersController extends Controller
$user = new User;
$user->fill($request->all());
$user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$user->created_by = auth()->id();
if ($request->has('permissions')) {
@@ -451,6 +454,10 @@ class UsersController extends Controller
$user->fill($request->all());
if ($request->filled('company_id')) {
$user->company_id = Company::getIdForCurrentUser($request->input('company_id'));
}
if ($user->id == $request->input('manager_id')) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
}

View File

@@ -61,43 +61,30 @@ class AssetFilesController extends Controller
*/
public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse
{
$asset = Asset::find($assetId);
// the asset is valid
if (isset($asset->id)) {
if ($asset = Asset::find($assetId)) {
$this->authorize('view', $asset);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
$file = 'private_uploads/assets/'.$log->filename;
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.file_not_found'));
}
}
$file = 'private_uploads/assets/'.$log->filename;
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
if (! Storage::exists($file)) {
return response('File '.$file.' not found on server', 404)
->header('Content-Type', 'text/plain');
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.log_record_not_found'));
}
// Prepare the error message
$error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]);
// Redirect to the hardware management page
return redirect()->route('hardware.index')->with('error', $error);
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
/**

View File

@@ -17,7 +17,6 @@ use App\Models\Location;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\View\Label;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
@@ -112,8 +111,10 @@ class AssetsController extends Controller
$settings = Setting::getSettings();
$success = false;
$successes = [];
$failures = [];
$serials = $request->input('serials');
$asset = null;
for ($a = 1; $a <= count($asset_tags); $a++) {
$asset = new Asset();
@@ -200,20 +201,35 @@ class AssetsController extends Controller
$asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location);
}
$success = true;
$successes[] = "<a href='" . route('hardware.show', ['hardware' => $asset->id]) . "' style='color: white;'>" . e($asset->asset_tag) . "</a>";
} else {
$failures[] = join(",", $asset->getErrors()->all());
}
}
session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]);
if ($success) {
if ($successes) {
if ($failures) {
//some succeeded, some failed
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) //FIXME - not tested
->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]))
->with('warning', trans_choice('admin/hardware/message.create.partial_failure', $failures, ['failures' => join("; ", $failures)]));
} else {
if (count($successes) == 1) {
//the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed
//and re-translated
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
} else {
//multi-success
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)]));
}
}
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))
->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)]));
}
return redirect()->back()->withInput()->withErrors($asset->getErrors());
@@ -336,7 +352,7 @@ class AssetsController extends Controller
$status = Statuslabel::find($request->input('status_id'));
// This is a non-deployable status label - we should check the asset back in.
if (($status && $status->getStatuslabelType() != 'deployable') && ($target = $asset->assignedTo)) {
if (($status && $status->status_type != 'deployable') && ($target = $asset->assignedTo)) {
$originalValues = $asset->getRawOriginal();
$asset->assigned_to = null;

View File

@@ -52,6 +52,10 @@ class BulkAssetsController extends Controller
}
$asset_ids = $request->input('ids');
if ($request->input('bulk_actions') === 'checkout') {
$request->session()->flashInput(['selected_assets' => $asset_ids]);
return redirect()->route('hardware.bulkcheckout.show');
}
// Figure out where we need to send the user after the update is complete, and store that in the session
$bulk_back_url = request()->headers->get('referer');
@@ -571,31 +575,34 @@ class BulkAssetsController extends Controller
}
$errors = [];
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids, $request) {
DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $asset_ids, $request) { //NOTE: $errors is passsed by reference!
foreach ($asset_ids as $asset_id) {
$asset = Asset::findOrFail($asset_id);
$this->authorize('checkout', $asset);
$error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
$checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null);
//TODO - I think this logic is duplicated in the checkOut method?
if ($target->location_id != '') {
$asset->location_id = $target->location_id;
$asset->unsetEventDispatcher();
$asset->save();
// TODO - I don't know why this is being saved without events
$asset::withoutEvents(function () use ($asset) {
$asset->save();
});
}
if ($error) {
array_merge_recursive($errors, $asset->getErrors()->toArray());
if (!$checkout_success) {
$errors = array_merge_recursive($errors, $asset->getErrors()->toArray());
}
}
});
if (! $errors) {
// Redirect to the new asset page
return redirect()->to('hardware')->with('success', trans('admin/hardware/message.checkout.success'));
return redirect()->to('hardware')->with('success', trans_choice('admin/hardware/message.multi-checkout.success', $asset_ids));
}
// Redirect to the asset management page with error
return redirect()->route('hardware.bulkcheckout.show')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors);
return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors);
} catch (ModelNotFoundException $e) {
return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors());
}

View File

@@ -73,6 +73,8 @@ class ComponentsController extends Controller
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->supplier_id = $request->input('supplier_id');
$component->manufacturer_id = $request->input('manufacturer_id');
$component->model_number = $request->input('model_number');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null);
@@ -150,6 +152,8 @@ class ComponentsController extends Controller
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->supplier_id = $request->input('supplier_id');
$component->manufacturer_id = $request->input('manufacturer_id');
$component->model_number = $request->input('model_number');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number');

View File

@@ -112,40 +112,25 @@ class ComponentsFilesController extends Controller
public function show($componentId = null, $fileId = null)
{
Log::debug('Private filesystem is: '.config('filesystems.default'));
$component = Component::find($componentId);
// the component is valid
if (isset($component->id)) {
if ($component = Component::find($componentId)) {
$this->authorize('view', $component);
$this->authorize('components.files', $component);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) {
$file = 'private_uploads/components/'.$log->filename;
$file = 'private_uploads/components/'.$log->filename;
if (Storage::missing($file)) {
Log::debug('FILE DOES NOT EXISTS for '.$file);
Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->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);
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found'));
}
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
}
}
return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));

View File

@@ -70,7 +70,7 @@ class ConsumableCheckoutController extends Controller
$this->authorize('checkout', $consumable);
// If the quantity is not present in the request or is not a positive integer, set it to 1
$quantity = $request->input('qty');
$quantity = $request->input('checkout_qty');
if (!isset($quantity) || !ctype_digit((string)$quantity) || $quantity <= 0) {
$quantity = 1;
}
@@ -92,7 +92,7 @@ class ConsumableCheckoutController extends Controller
// Update the consumable data
$consumable->assigned_to = e($request->input('assigned_to'));
for($i = 0; $i < $quantity; $i++){
for ($i = 0; $i < $quantity; $i++){
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
'created_by' => $admin_user->id,
@@ -100,6 +100,8 @@ class ConsumableCheckoutController extends Controller
'note' => $request->input('note'),
]);
}
$consumable->checkout_qty = $quantity;
event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note')));
$request->request->add(['checkout_to_type' => 'user']);

View File

@@ -104,7 +104,6 @@ class ConsumablesFilesController extends Controller
* @since [v1.4]
* @param int $consumableId
* @param int $fileId
* @return \Symfony\Consumable\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($consumableId = null, $fileId = null)
@@ -116,36 +115,18 @@ class ConsumablesFilesController extends Controller
$this->authorize('view', $consumable);
$this->authorize('consumables.files', $consumable);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) {
$file = 'private_uploads/consumables/'.$log->filename;
$file = 'private_uploads/consumables/'.$log->filename;
if (Storage::missing($file)) {
Log::debug('FILE DOES NOT EXISTS for '.$file);
Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->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);
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found'));
}
}
// The log record doesn't exist somehow
return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));

View File

@@ -112,37 +112,19 @@ class LicenseFilesController extends Controller
$this->authorize('view', $license);
$this->authorize('licenses.files', $license);
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
$file = 'private_uploads/licenses/'.$log->filename;
if (Storage::missing($file)) {
Log::debug('NOT EXISTS for '.$file);
Log::debug('NOT EXISTS URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->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);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) {
$file = 'private_uploads/licenses/'.$log->filename;
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found'));
}
}
// The log record doesn't exist somehow
return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found'));
}
return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId]));

View File

@@ -40,7 +40,7 @@ class ModalController extends Controller
$view = view("modals.${type}");
if ($type == "statuslabel") {
$view->with('statuslabel_types', Helper::statusTypeList());
$view->with('status_types', Helper::statusTypeList());
}
if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) {
$view->with('kitId', $itemId);

View File

@@ -47,7 +47,7 @@ class StatuslabelsController extends Controller
return view('statuslabels/edit')
->with('item', new Statuslabel)
->with('statuslabel_types', Helper::statusTypeList());
->with('status_types', Helper::statusTypeList());
}
/**
@@ -61,19 +61,11 @@ class StatuslabelsController extends Controller
// create a new model instance
$statusLabel = new Statuslabel();
if ($request->missing('statuslabel_types')) {
return redirect()->back()->withInput()->withErrors(['statuslabel_types' => trans('validation.statuslabel_type')]);
}
$statusType = Statuslabel::getStatuslabelTypesForDB($request->input('statuslabel_types'));
// Save the Statuslabel data
$statusLabel->name = $request->input('name');
$statusLabel->created_by = auth()->id();
$statusLabel->notes = $request->input('notes');
$statusLabel->deployable = $statusType['deployable'];
$statusLabel->pending = $statusType['pending'];
$statusLabel->archived = $statusType['archived'];
$statusLabel->status_type = $request->input('status_type');
$statusLabel->color = $request->input('color');
$statusLabel->show_in_nav = $request->input('show_in_nav', 0);
$statusLabel->default_label = $request->input('default_label', 0);
@@ -100,11 +92,7 @@ class StatuslabelsController extends Controller
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
$use_statuslabel_type = $item->getStatuslabelType();
$statuslabel_types = ['' => trans('admin/hardware/form.select_statustype')] + ['undeployable' => trans('admin/hardware/general.undeployable')] + ['pending' => trans('admin/hardware/general.pending')] + ['archived' => trans('admin/hardware/general.archived')] + ['deployable' => trans('admin/hardware/general.deployable')];
return view('statuslabels/edit', compact('item', 'statuslabel_types'))->with('use_statuslabel_type', $use_statuslabel_type);
return view('statuslabels/edit', compact('item'))->with('status_types', Helper::statusTypeList());;
}
/**
@@ -121,17 +109,10 @@ class StatuslabelsController extends Controller
return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist'));
}
if (! $request->filled('statuslabel_types')) {
return redirect()->back()->withInput()->withErrors(['statuslabel_types' => trans('validation.statuslabel_type')]);
}
// Update the Statuslabel data
$statustype = Statuslabel::getStatuslabelTypesForDB($request->input('statuslabel_types'));
$statuslabel->name = $request->input('name');
$statuslabel->notes = $request->input('notes');
$statuslabel->deployable = $statustype['deployable'];
$statuslabel->pending = $statustype['pending'];
$statuslabel->archived = $statustype['archived'];
$statuslabel->status_type = $request->input('status_type');
$statuslabel->color = $request->input('color');
$statuslabel->show_in_nav = $request->input('show_in_nav', 0);
$statuslabel->default_label = $request->input('default_label', 0);

View File

@@ -7,9 +7,6 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Models\Actionlog;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Illuminate\Support\Facades\Storage;
@@ -116,31 +113,30 @@ class UserFilesController extends Controller
public function show($userId = null, $fileId = null)
{
if (empty($fileId)) {
return redirect()->route('users.show')->with('error', 'Invalid file request');
}
$user = User::find($userId);
// the license is valid
if (isset($user->id)) {
if ($user = User::find($userId)) {
$this->authorize('view', $user);
if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) {
$file = 'private_uploads/users/'.$log->filename;
// Display the file inline
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers);
try {
return StorageHelper::showOrDownloadFile($file, $log->filename);
} catch (\Exception $e) {
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found'));
}
return Storage::download('private_uploads/users/'.$log->filename);
}
return redirect()->route('users.index')->with('error', trans('admin/users/message.log_record_not_found'));
// The log record doesn't exist somehow
return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found'));
return redirect()->back()->with('error', trans('general.file_not_found'));
}
// Redirect to the user management page if the user doesn't exist

View File

@@ -26,11 +26,19 @@ class StoreAssetRequest extends ImageUploadRequest
public function prepareForValidation(): void
{
// Guard against users passing in an array for company_id instead of an integer.
// If the company_id is not an integer then we simply use what was
// provided to be caught by model level validation later.
// The use of is_numeric accounts for 1 and '1'.
$idForCurrentUser = is_numeric($this->company_id)
? Company::getIdForCurrentUser($this->company_id)
: $this->company_id;
$this->parseLastAuditDate();
$this->merge([
'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(),
'company_id' => Company::getIdForCurrentUser($this->company_id),
'company_id' => $idForCurrentUser,
'assigned_to' => $assigned_to ?? null,
]);
}

View File

@@ -23,7 +23,7 @@ trait MayContainCustomFields
return str_starts_with($attributes, '_snipeit_');
});
// if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag
if (count($request_fields) > 0) {
if (count($request_fields) > 0 && $validator->errors()->isEmpty()) {
$request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column'))
->each(function ($request_field_name) use ($request_fields, $validator) {
if (CustomField::where('db_column', $request_field_name)->exists()) {

View File

@@ -141,6 +141,8 @@ class ActionlogsTransformer
if ($actionlog->item) {
if ($actionlog->itemType() == 'asset') {
$file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'accessory') {
$file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'license') {
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
} elseif ($actionlog->itemType() == 'user') {
@@ -158,7 +160,6 @@ class ActionlogsTransformer
[
'url' => $file_url,
'filename' => $actionlog->filename,
'inlineable' => (bool) Helper::show_file_inline($actionlog->filename),
] : null,
'item' => ($actionlog->item) ? [
@@ -346,4 +347,4 @@ class ActionlogsTransformer
}
}

View File

@@ -39,7 +39,7 @@ class AssetMaintenancesTransformer
'status_label' => ($assetmaintenance->asset->assetstatus) ? [
'id' => (int) $assetmaintenance->asset->assetstatus->id,
'name'=> e($assetmaintenance->asset->assetstatus->name),
'status_type'=> e($assetmaintenance->asset->assetstatus->getStatuslabelType()),
'status_type'=> e($assetmaintenance->asset->assetstatus->status_type),
'status_meta' => e($assetmaintenance->asset->present()->statusMeta),
] : null,
'company' => (($assetmaintenance->asset) && ($assetmaintenance->asset->company)) ? [

View File

@@ -8,7 +8,6 @@ use App\Models\Setting;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
class AssetsTransformer
{
@@ -45,8 +44,8 @@ class AssetsTransformer
'status_label' => ($asset->assetstatus) ? [
'id' => (int) $asset->assetstatus->id,
'name'=> e($asset->assetstatus->name),
'status_type'=> e($asset->assetstatus->getStatuslabelType()),
'status_meta' => e($asset->present()->statusMeta),
'status_type'=> e($asset->assetstatus->status_type),
'status_meta' => e($asset->assetstatus->status_type),
] : null,
'category' => (($asset->model) && ($asset->model->category)) ? [
'id' => (int) $asset->model->category->id,

View File

@@ -38,6 +38,8 @@ class ComponentsTransformer
'name' => e($component->category->name),
] : null,
'supplier' => ($component->supplier) ? ['id' => $component->supplier->id, 'name'=> e($component->supplier->name)] : null,
'manufacturer' => ($component->manufacturer) ? ['id' => $component->manufacturer->id, 'name'=> e($component->manufacturer->name)] : null,
'model_number' => ($component->model_number) ? e($component->model_number) : null,
'order_number' => e($component->order_number),
'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost),

View File

@@ -36,6 +36,7 @@ class ManufacturersTransformer
'licenses_count' => (int) $manufacturer->licenses_count,
'consumables_count' => (int) $manufacturer->consumables_count,
'accessories_count' => (int) $manufacturer->accessories_count,
'components_count' => (int) $manufacturer->components_count,
'created_by' => ($manufacturer->adminuser) ? [
'id' => (int) $manufacturer->adminuser->id,
'name'=> e($manufacturer->adminuser->present()->fullName()),

View File

@@ -23,9 +23,9 @@ class StatuslabelsTransformer
{
$array = [
'id' => (int) $statuslabel->id,
'reorder_icon' => '<i class="fas fa-ellipsis-v"></i>',
'name' => e($statuslabel->name),
'type' => $statuslabel->getStatuslabelType(),
'type' => $statuslabel->status_type, // legacy - to be removed in later versions
'status_type' => $statuslabel->status_type,
'color' => ($statuslabel->color) ? e($statuslabel->color) : null,
'show_in_nav' => ($statuslabel->show_in_nav == '1') ? true : false,
'default_label' => ($statuslabel->default_label == '1') ? true : false,

View File

@@ -18,8 +18,8 @@ class AssetImporter extends ItemImporter
$this->defaultStatusLabelId = Statuslabel::first()->id;
if (!is_null(Statuslabel::deployable()->first())) {
$this->defaultStatusLabelId = Statuslabel::deployable()->first()->id;
if (!is_null(Statuslabel::where('status_type', 'deployable')->first())) {
$this->defaultStatusLabelId = Statuslabel::where('status_type', 'deployable')->first()->id;
}
}

View File

@@ -21,7 +21,6 @@ abstract class Importer
* Id of User performing import
* @var
*/
protected $created_by;
/**
* Are we updating items in the import
@@ -149,17 +148,23 @@ abstract class Importer
{
$headerRow = $this->csv->fetchOne();
$this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record
$results = $this->normalizeInputArray($this->csv->getRecords($headerRow));
$this->populateCustomFields($headerRow);
DB::transaction(function () use (&$results) {
DB::transaction(function () use ($headerRow) {
$importedItemsCount = 0;
Model::unguard();
$resultsCount = count($results);
foreach ($results as $row) {
foreach ($this->csv->getRecords($headerRow) as $row) {
//Lowercase header values to ensure we're comparing values properly.
$row = array_change_key_case($row, CASE_LOWER);
$this->handle($row);
$importedItemsCount++;
if ($this->progressCallback) {
call_user_func($this->progressCallback, $resultsCount);
call_user_func($this->progressCallback, $importedItemsCount);
}
$this->log('------------- Action Summary ----------------');
@@ -237,22 +242,6 @@ abstract class Importer
return $key;
}
/**
* Used to lowercase header values to ensure we're comparing values properly.
*
* @param $results
* @return array
*/
public function normalizeInputArray($results)
{
$newArray = [];
foreach ($results as $index => $arrayToNormalize) {
$newArray[$index] = array_change_key_case($arrayToNormalize);
}
return $newArray;
}
/**
* Figure out the fieldname of the custom field
*

View File

@@ -81,6 +81,12 @@ class CustomFieldSetDefaultValuesForModel extends Component
{
$this->fields->each(function ($field) {
$this->selectedValues[$field->db_column] = $this->getSelectedValueForField($field);
// if the element is a checkbox and the value was just sent to null, make it
// an array since Livewire can't bind to non-array values for checkboxes.
if ($field->element === 'checkbox' && is_null($this->selectedValues[$field->db_column])) {
$this->selectedValues[$field->db_column] = [];
}
});
}

View File

@@ -196,7 +196,6 @@ class Importer extends Component
'supplier' => trans('general.supplier'),
'purchase_cost' => trans('general.purchase_cost'),
'purchase_date' => trans('general.purchase_date'),
'purchase_order' => trans('admin/licenses/form.purchase_order'),
'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]),
'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]),
'manufacturer' => trans('general.manufacturer'),

View File

@@ -301,9 +301,9 @@ class Asset extends Depreciable
// This asset is not currently assigned to anyone and is not deleted...
if ((! $this->assigned_to) && (! $this->deleted_at)) {
// The asset status is not archived and is deployable
if (($this->assetstatus) && ($this->assetstatus->archived == '0')
&& ($this->assetstatus->deployable == '1'))
// The asset is not archived and the status is deployable
if (($this->assetstatus) && ($this->archived == '0')
&& ($this->assetstatus->status_type == 'deployable'))
{
return true;
@@ -1146,9 +1146,7 @@ class Asset extends Depreciable
public function scopePending($query)
{
return $query->whereHas('assetstatus', function ($query) {
$query->where('deployable', '=', 0)
->where('pending', '=', 1)
->where('archived', '=', 0);
$query->where('status_type', '=', 'deployable');
});
}
@@ -1195,9 +1193,7 @@ class Asset extends Depreciable
{
return $query->whereNull('assets.assigned_to')
->whereHas('assetstatus', function ($query) {
$query->where('deployable', '=', 1)
->where('pending', '=', 0)
->where('archived', '=', 0);
$query->where('status_type', '=', 'deployable');
});
}
@@ -1212,9 +1208,7 @@ class Asset extends Depreciable
public function scopeUndeployable($query)
{
return $query->whereHas('assetstatus', function ($query) {
$query->where('deployable', '=', 0)
->where('pending', '=', 0)
->where('archived', '=', 0);
$query->where('status_type', '!=', 'deployable');
});
}
@@ -1229,7 +1223,7 @@ class Asset extends Depreciable
public function scopeNotArchived($query)
{
return $query->whereHas('assetstatus', function ($query) {
$query->where('archived', '=', 0);
$query->where('status_type', '!=', 'archived');
});
}
@@ -1406,9 +1400,7 @@ class Asset extends Depreciable
public function scopeArchived($query)
{
return $query->whereHas('assetstatus', function ($query) {
$query->where('deployable', '=', 0)
->where('pending', '=', 0)
->where('archived', '=', 1);
$query->where('status_type', '=', 'archived');
});
}
@@ -1440,9 +1432,8 @@ class Asset extends Depreciable
return Company::scopeCompanyables($query->where($table.'.requestable', '=', 1))
->whereHas('assetstatus', function ($query) {
$query->where(function ($query) {
$query->where('deployable', '=', 1)
->where('archived', '=', 0); // you definitely can't request something that's archived
})->orWhere('pending', '=', 1); // we've decided that even though an asset may be 'pending', you can still request it
$query->where('status_type', '!=', 'archived'); // you definitely can't request something that's archived
}); // we've decided that even though an asset may be 'pending', you can still request it
});
}

View File

@@ -116,7 +116,7 @@ final class Company extends SnipeModel
if ($current_user->company_id != null) {
return $current_user->company_id;
} else {
return static::getIdFromInput($unescaped_input);
return null;
}
}
}

View File

@@ -8,9 +8,6 @@ trait CompanyableTrait
* This trait is used to scope models to the current company. To use this scope on companyable models,
* we use the "use Companyable;" statement at the top of the mode.
*
* We CANNOT USE THIS ON USERS, as it causes an infinite loop and prevents users from logging in, since this scope will be
* applied to the currently logged in (or logging in) user in addition to the user model for viewing lists of users.
*
* @see \App\Models\Company\Company::scopeCompanyables()
* @return void
*/

View File

@@ -38,6 +38,7 @@ class Component extends SnipeModel
'min_amt' => 'integer|min:0|nullable',
'purchase_date' => 'date_format:Y-m-d|nullable',
'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999',
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
];
/**
@@ -60,6 +61,8 @@ class Component extends SnipeModel
'company_id',
'supplier_id',
'location_id',
'manufacturer_id',
'model_number',
'name',
'purchase_cost',
'purchase_date',
@@ -77,7 +80,15 @@ class Component extends SnipeModel
*
* @var array
*/
protected $searchableAttributes = ['name', 'order_number', 'serial', 'purchase_cost', 'purchase_date', 'notes'];
protected $searchableAttributes = [
'name',
'order_number',
'serial',
'purchase_cost',
'purchase_date',
'notes',
'model_number',
];
/**
* The relations and their attributes that should be included when searching the model.
@@ -89,6 +100,7 @@ class Component extends SnipeModel
'company' => ['name'],
'location' => ['name'],
'supplier' => ['name'],
'manufacturer' => ['name'],
];
@@ -183,6 +195,19 @@ class Component extends SnipeModel
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
}
/**
* Establishes the item -> manufacturer relationship
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v3.0]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function manufacturer()
{
return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id');
}
/**
* Establishes the component -> action logs relationship
*
@@ -311,6 +336,19 @@ class Component extends SnipeModel
return $query->leftJoin('suppliers', 'components.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
}
/**
* Query builder scope to order on manufacturer
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderManufacturer($query, $order)
{
return $query->leftJoin('manufacturers', 'components.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order);
}
public function scopeOrderByCreatedBy($query, $order)
{
return $query->leftJoin('users as admin_sort', 'components.created_by', '=', 'admin_sort.id')->select('components.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order);

View File

@@ -138,9 +138,7 @@ class Location extends SnipeModel
{
return $this->hasMany(\App\Models\Asset::class, 'location_id')
->whereHas('assetstatus', function ($query) {
$query->where('status_labels.deployable', '=', 1)
->orWhere('status_labels.pending', '=', 1)
->orWhere('status_labels.archived', '=', 0);
$query->whereNot('status_labels.status_type', '=', 'archived');
});
}

View File

@@ -78,6 +78,7 @@ class Manufacturer extends SnipeModel
&& (($this->licenses_count ?? $this->licenses()->count()) === 0)
&& (($this->consumables_count ?? $this->consumables()->count()) === 0)
&& (($this->accessories_count ?? $this->accessories()->count()) === 0)
&& (($this->components_count ?? $this->components()->count()) === 0)
&& ($this->deleted_at == '');
}
@@ -106,6 +107,10 @@ class Manufacturer extends SnipeModel
return $this->hasMany(\App\Models\Consumable::class, 'manufacturer_id');
}
public function components()
{
return $this->hasMany(\App\Models\Component::class, 'manufacturer_id');
}
public function adminuser()
{

View File

@@ -23,17 +23,16 @@ class Statuslabel extends SnipeModel
protected $rules = [
'name' => 'required|string|unique_undeleted',
'notes' => 'string|nullable',
'deployable' => 'required',
'pending' => 'required',
'archived' => 'required',
'status_type' => 'required|in:deployable,pending,archived,undeployable1',
];
protected $fillable = [
'archived',
'deployable',
'status_type',
'name',
'notes',
'pending',
'color',
'show_in_nav',
'default_label',
];
use Searchable;
@@ -76,54 +75,6 @@ class Statuslabel extends SnipeModel
* @since [v1.0]
* @return string
*/
public function getStatuslabelType()
{
if (($this->pending == '1') && ($this->archived == '0') && ($this->deployable == '0')) {
return 'pending';
} elseif (($this->pending == '0') && ($this->archived == '1') && ($this->deployable == '0')) {
return 'archived';
} elseif (($this->pending == '0') && ($this->archived == '0') && ($this->deployable == '0')) {
return 'undeployable';
}
return 'deployable';
}
/**
* Query builder scope to for pending status types
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopePending()
{
return $this->where('pending', '=', 1)
->where('archived', '=', 0)
->where('deployable', '=', 0);
}
/**
* Query builder scope for archived status types
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeArchived()
{
return $this->where('pending', '=', 0)
->where('archived', '=', 1)
->where('deployable', '=', 0);
}
/**
* Query builder scope for deployable status types
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeDeployable()
{
return $this->where('pending', '=', 0)
->where('archived', '=', 0)
->where('deployable', '=', 1);
}
/**
* Query builder scope for undeployable status types
@@ -132,40 +83,10 @@ class Statuslabel extends SnipeModel
*/
public function scopeUndeployable()
{
return $this->where('pending', '=', 0)
->where('archived', '=', 0)
->where('deployable', '=', 0);
return $this->whereNot('status_type', '=', 'deployable');
}
/**
* Helper function to determine type attributes
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v1.0]
* @return string
*/
public static function getStatuslabelTypesForDB($type)
{
$statustype['pending'] = 0;
$statustype['deployable'] = 0;
$statustype['archived'] = 0;
if ($type == 'pending') {
$statustype['pending'] = 1;
$statustype['deployable'] = 0;
$statustype['archived'] = 0;
} elseif ($type == 'deployable') {
$statustype['pending'] = 0;
$statustype['deployable'] = 1;
$statustype['archived'] = 0;
} elseif ($type == 'archived') {
$statustype['pending'] = 0;
$statustype['deployable'] = 0;
$statustype['archived'] = 1;
}
return $statustype;
}
public function scopeOrderByCreatedBy($query, $order)
{

View File

@@ -38,6 +38,7 @@ class CheckoutConsumableNotification extends Notification
$this->note = $note;
$this->target = $checkedOutTo;
$this->acceptance = $acceptance;
$this->qty = $consumable->checkout_qty;
$this->settings = Setting::getSettings();
}
@@ -173,7 +174,6 @@ class CheckoutConsumableNotification extends Notification
*/
public function toMail()
{
Log::debug($this->item->getImageUrl());
$eula = $this->item->getEula();
$req_accept = $this->item->requireAcceptance();
@@ -188,6 +188,7 @@ class CheckoutConsumableNotification extends Notification
'eula' => $eula,
'req_accept' => $req_accept,
'accept_url' => $accept_url,
'qty' => $this->qty,
])
->subject(trans('mail.Confirm_consumable_delivery'));
}

View File

@@ -479,19 +479,6 @@ class AssetPresenter extends Presenter
return $interval;
}
/**
* @return string
* This handles the status label "meta" status of "deployed" if
* it's assigned. Should maybe deprecate.
*/
public function statusMeta()
{
if ($this->model->assigned) {
return 'deployed';
}
return $this->model->assetstatus->getStatuslabelType();
}
/**
* @return string

View File

@@ -66,8 +66,20 @@ class ComponentPresenter extends Presenter
'title' => trans('general.supplier'),
'visible' => false,
'formatter' => 'suppliersLinkObjFormatter',
],
[
], [
'field' => 'model_number',
'searchable' => true,
'sortable' => true,
'title' => trans('admin/models/table.modelnumber'),
], [
'field' => 'manufacturer',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.manufacturer'),
'visible' => false,
'formatter' => 'manufacturersLinkObjFormatter',
], [
'field' => 'qty',
'searchable' => false,
'sortable' => true,

View File

@@ -311,7 +311,7 @@ class DepreciationReportPresenter extends Presenter
if ($this->model->assigned) {
return 'deployed';
}
return $this->model->assetstatus->getStatuslabelType();
return $this->model->assetstatus->status_type;
}
/**

View File

@@ -124,8 +124,15 @@ class ManufacturerPresenter extends Presenter
'title' => trans('general.accessories'),
'visible' => true,
'class' => 'css-accessory',
],
[
], [
'field' => 'components_count',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.components'),
'visible' => true,
'class' => 'css-component',
], [
'field' => 'created_by',
'searchable' => false,
'sortable' => true,

View File

@@ -21,14 +21,6 @@ class StatusLabelPresenter extends Presenter
'switchable' => true,
'title' => trans('general.id'),
'visible' => false,
],
[
'field' => 'reorder_icon',
'searchable' => false,
'sortable' => false,
'switchable' => false,
'title' => trans('admin/custom_fields/general.reorder'),
'visible' => true,
], [
'field' => 'name',
'searchable' => true,
@@ -38,9 +30,9 @@ class StatusLabelPresenter extends Presenter
'visible' => true,
'formatter' => 'statuslabelsAssetLinkFormatter',
],[
'field' => 'type',
'searchable' => false,
'sortable' => false,
'field' => 'status_type',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/statuslabels/table.status_type'),
'visible' => true,

View File

@@ -40,7 +40,7 @@ class AssetCannotBeCheckedOutToNondeployableStatus implements DataAwareRule, Val
// Check to see if any of the assign-ish fields are set
if ((isset($this->data['assigned_to'])) || (isset($this->data['assigned_user'])) || (isset($this->data['assigned_location'])) || (isset($this->data['assigned_asset'])) || (isset($this->data['assigned_type']))) {
if (($value) && ($label = Statuslabel::find($value)) && ($label->getStatuslabelType()!='deployable')) {
if (($value) && ($label = Statuslabel::find($value)) && ($label->status_type!='deployable')) {
$fail(trans('admin/hardware/form.asset_not_deployable'));
}

View File

@@ -74,7 +74,6 @@
"ext-exif": "*"
},
"require-dev": {
"brianium/paratest": "^7.0",
"fakerphp/faker": "^1.16",
"larastan/larastan": "^2.9",
"mockery/mockery": "^1.4",

170
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3819ab4ef72eb77fabe494c0e746b83b",
"content-hash": "5341bc5be02b3c33e28e46e06dd99f29",
"packages": [
{
"name": "alek13/slack",
@@ -4461,16 +4461,16 @@
},
{
"name": "livewire/livewire",
"version": "v3.5.9",
"version": "v3.5.12",
"source": {
"type": "git",
"url": "https://github.com/livewire/livewire.git",
"reference": "d04a229058afa76116d0e39209943a8ea3a7f888"
"reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/livewire/livewire/zipball/d04a229058afa76116d0e39209943a8ea3a7f888",
"reference": "d04a229058afa76116d0e39209943a8ea3a7f888",
"url": "https://api.github.com/repos/livewire/livewire/zipball/3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d",
"reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d",
"shasum": ""
},
"require": {
@@ -4525,7 +4525,7 @@
"description": "A front-end framework for Laravel.",
"support": {
"issues": "https://github.com/livewire/livewire/issues",
"source": "https://github.com/livewire/livewire/tree/v3.5.9"
"source": "https://github.com/livewire/livewire/tree/v3.5.12"
},
"funding": [
{
@@ -4533,7 +4533,7 @@
"type": "github"
}
],
"time": "2024-10-01T12:40:06+00:00"
"time": "2024-10-15T19:35:06+00:00"
},
{
"name": "masterminds/html5",
@@ -11790,101 +11790,6 @@
],
"time": "2024-04-13T18:00:56+00:00"
},
{
"name": "brianium/paratest",
"version": "v7.3.1",
"source": {
"type": "git",
"url": "https://github.com/paratestphp/paratest.git",
"reference": "551f46f52a93177d873f3be08a1649ae886b4a30"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/551f46f52a93177d873f3be08a1649ae886b4a30",
"reference": "551f46f52a93177d873f3be08a1649ae886b4a30",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-simplexml": "*",
"fidry/cpu-core-counter": "^0.5.1 || ^1.0.0",
"jean85/pretty-package-versions": "^2.0.5",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
"phpunit/php-code-coverage": "^10.1.7",
"phpunit/php-file-iterator": "^4.1.0",
"phpunit/php-timer": "^6.0",
"phpunit/phpunit": "^10.4.2",
"sebastian/environment": "^6.0.1",
"symfony/console": "^6.3.4 || ^7.0.0",
"symfony/process": "^6.3.4 || ^7.0.0"
},
"require-dev": {
"doctrine/coding-standard": "^12.0.0",
"ext-pcov": "*",
"ext-posix": "*",
"infection/infection": "^0.27.6",
"phpstan/phpstan": "^1.10.40",
"phpstan/phpstan-deprecation-rules": "^1.1.4",
"phpstan/phpstan-phpunit": "^1.3.15",
"phpstan/phpstan-strict-rules": "^1.5.2",
"squizlabs/php_codesniffer": "^3.7.2",
"symfony/filesystem": "^6.3.1 || ^7.0.0"
},
"bin": [
"bin/paratest",
"bin/paratest.bat",
"bin/paratest_for_phpstorm"
],
"type": "library",
"autoload": {
"psr-4": {
"ParaTest\\": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Scaturro",
"email": "scaturrob@gmail.com",
"role": "Developer"
},
{
"name": "Filippo Tessarotto",
"email": "zoeslam@gmail.com",
"role": "Developer"
}
],
"description": "Parallel testing for PHP",
"homepage": "https://github.com/paratestphp/paratest",
"keywords": [
"concurrent",
"parallel",
"phpunit",
"testing"
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
"source": "https://github.com/paratestphp/paratest/tree/v7.3.1"
},
"funding": [
{
"url": "https://github.com/sponsors/Slamdunk",
"type": "github"
},
{
"url": "https://paypal.me/filippotessarotto",
"type": "paypal"
}
],
"time": "2023-10-31T09:24:17+00:00"
},
{
"name": "clue/ndjson-react",
"version": "v1.3.0",
@@ -12781,65 +12686,6 @@
},
"time": "2020-07-09T08:09:16+00:00"
},
{
"name": "jean85/pretty-package-versions",
"version": "2.0.6",
"source": {
"type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4",
"reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2.0.0",
"php": "^7.1|^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^1.4",
"phpunit/phpunit": "^7.5|^8.5|^9.4",
"vimeo/psalm": "^4.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Jean85\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alessandro Lai",
"email": "alessandro.lai85@gmail.com"
}
],
"description": "A library to get pretty versions strings of installed dependencies",
"keywords": [
"composer",
"package",
"release",
"versions"
],
"support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6"
},
"time": "2024-03-08T09:58:59+00:00"
},
{
"name": "justinrainbow/json-schema",
"version": "5.3.0",
@@ -16658,5 +16504,5 @@
"ext-pdo": "*"
},
"platform-dev": [],
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}

View File

@@ -280,7 +280,6 @@ return [
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
// Illuminate\Translation\TranslationServiceProvider::class, //replaced on next line
App\Providers\SnipeTranslationServiceProvider::class, //we REPLACE the default Laravel translator with our own
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
@@ -373,7 +372,7 @@ return [
'Image' => Intervention\Image\ImageServiceProvider::class,
'Carbon' => Carbon\Carbon::class,
'Helper' => App\Helpers\Helper::class,
// makes it much easier to use 'Helper::blah' in blades (which is where we usually use this)
'StorageHelper' => App\Helpers\StorageHelper::class,
'Icon' => App\Helpers\IconHelper::class,
'Socialite' => Laravel\Socialite\Facades\Socialite::class,

View File

@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v7.0.13',
'full_app_version' => 'v7.0.13 - build 15514-gdc0949da7',
'build_version' => '15514',
'full_app_version' => 'v7.0.13 - build 15666-g03b01689b',
'build_version' => '15666',
'prerelease_version' => '',
'hash_version' => 'gdc0949da7',
'full_hash' => 'v7.0.13-265-gdc0949da7',
'hash_version' => 'g03b01689b',
'full_hash' => 'v7.0.13-144-g03b01689b',
'branch' => 'develop',
);

View File

@@ -34,7 +34,8 @@ class AssetFactory extends Factory
'rtd_location_id' => Location::factory(),
'serial' => $this->faker->uuid(),
'status_id' => function () {
return Statuslabel::where('name', 'Ready to Deploy')->first() ?? Statuslabel::factory()->rtd()->create(['name' => 'Ready to Deploy']);
// $status = Statuslabel::factory()->create(); dd($status) ;
return Statuslabel::where('status_type', 'deployable')->first() ?? Statuslabel::factory()->create(['name' => 'Ready to Deploy', 'status_type' => 'deployable'])->id;
},
'created_by' => User::factory()->superuser(),
'asset_tag' => $this->faker->unixTime('now'),

View File

@@ -7,6 +7,7 @@ use App\Models\Asset;
use App\Models\Category;
use App\Models\Company;
use App\Models\Component;
use App\Models\Manufacturer;
use App\Models\Consumable;
use App\Models\Location;
use App\Models\User;
@@ -30,6 +31,7 @@ class ComponentFactory extends Factory
*/
public function definition()
{
return [
'name' => $this->faker->text(20),
'category_id' => Category::factory(),
@@ -42,12 +44,14 @@ class ComponentFactory extends Factory
'min_amt' => $this->faker->numberBetween($min = 1, $max = 2),
'company_id' => Company::factory(),
'supplier_id' => Supplier::factory(),
'model_number' => $this->faker->numberBetween(1000000, 50000000),
];
}
public function ramCrucial4()
{
return $this->state(function () {
$manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']);
return $this->state(function () use ($manufacturer) {
return [
'name' => 'Crucial 4GB DDR3L-1600 SODIMM',
'category_id' => function () {
@@ -55,6 +59,7 @@ class ComponentFactory extends Factory
},
'qty' => 10,
'min_amt' => 2,
'manufacturer_id' => $manufacturer->id,
'location_id' => Location::factory(),
];
});
@@ -62,7 +67,8 @@ class ComponentFactory extends Factory
public function ramCrucial8()
{
return $this->state(function () {
$manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']);
return $this->state(function () use ($manufacturer) {
return [
'name' => 'Crucial 8GB DDR3L-1600 SODIMM Memory for Mac',
'category_id' => function () {
@@ -70,13 +76,15 @@ class ComponentFactory extends Factory
},
'qty' => 10,
'min_amt' => 2,
'manufacturer_id' => $manufacturer->id,
];
});
}
public function ssdCrucial120()
{
return $this->state(function () {
$manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']);
return $this->state(function () use ($manufacturer) {
return [
'name' => 'Crucial BX300 120GB SATA Internal SSD',
'category_id' => function () {
@@ -84,13 +92,15 @@ class ComponentFactory extends Factory
},
'qty' => 10,
'min_amt' => 2,
'manufacturer_id' => $manufacturer->id,
];
});
}
public function ssdCrucial240()
{
return $this->state(function () {
$manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']);
return $this->state(function () use ($manufacturer) {
return [
'name' => 'Crucial BX300 240GB SATA Internal SSD',
'category_id' => function () {
@@ -98,6 +108,7 @@ class ComponentFactory extends Factory
},
'qty' => 10,
'min_amt' => 2,
'manufacturer_id' => $manufacturer->id,
];
});
}

View File

@@ -28,9 +28,7 @@ class StatuslabelFactory extends Factory
'updated_at' => $this->faker->dateTime(),
'created_by' => User::factory()->superuser(),
'deleted_at' => null,
'deployable' => 0,
'pending' => 0,
'archived' => 0,
'status_type' => 'deployable',
'notes' => '',
];
}
@@ -40,7 +38,7 @@ class StatuslabelFactory extends Factory
return $this->state(function () {
return [
'notes' => $this->faker->sentence(),
'deployable' => 1,
'status_type' => 'deployable',
'default_label' => 1,
];
});
@@ -56,7 +54,7 @@ class StatuslabelFactory extends Factory
return $this->state(function () {
return [
'notes' => $this->faker->sentence(),
'pending' => 1,
'status_type' => 'pending',
'default_label' => 1,
];
});
@@ -67,8 +65,8 @@ class StatuslabelFactory extends Factory
return $this->state(function () {
return [
'notes' => 'These assets are permanently undeployable',
'archived' => 1,
'default_label' => 0,
'status_type' => 'archived',
];
});
}
@@ -79,6 +77,7 @@ class StatuslabelFactory extends Factory
return [
'name' => 'Out for Diagnostics',
'default_label' => 0,
'status_type' => 'pending',
];
});
}
@@ -89,6 +88,7 @@ class StatuslabelFactory extends Factory
return [
'name' => 'Out for Repair',
'default_label' => 0,
'status_type' => 'pending',
];
});
}
@@ -99,6 +99,7 @@ class StatuslabelFactory extends Factory
return [
'name' => 'Broken - Not Fixable',
'default_label' => 0,
'status_type' => 'archived',
];
});
}
@@ -109,6 +110,7 @@ class StatuslabelFactory extends Factory
return [
'name' => 'Lost/Stolen',
'default_label' => 0,
'status_type' => 'archived',
];
});
}

View File

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

View File

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

View File

@@ -0,0 +1,71 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use App\Models\StatusLabel;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (!Schema::hasColumn('status_labels', 'status_type')) {
Schema::table('status_labels', function (Blueprint $table) {
$table->string('status_type')->after('name')->default('deployable');
});
DB::table('status_labels')->where('pending', 1)->update(['status_type' => 'pending']);
DB::table('status_labels')->where('archived', 1)->update(['status_type' => 'archived']);
DB::table('status_labels')->where('deployable', 1)->update(['status_type' => 'deployable']);
Schema::table('status_labels', function (Blueprint $table) {
$table->renameColumn('deployable', 'legacy_deployable');
});
Schema::table('status_labels', function (Blueprint $table) {
$table->renameColumn('pending', 'legacy_pending');
});
Schema::table('status_labels', function (Blueprint $table) {
$table->renameColumn('archived', 'legacy_archived');
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (Schema::hasColumn('status_labels', 'status_type')) {
Schema::table('status_labels', function (Blueprint $table) {
$table->dropColumn('status_type');
});
Schema::table('status_labels', function (Blueprint $table) {
$table->renameColumn('legacy_deployable', 'deployable');
});
Schema::table('status_labels', function (Blueprint $table) {
$table->renameColumn('legacy_pending', 'pending');
});
Schema::table('status_labels', function (Blueprint $table) {
$table->renameColumn('legacy_archived', 'archived');
});
}
}
};

View File

@@ -37,6 +37,7 @@ class SettingsSeeder extends Seeder
$settings->support_footer = 'on';
$settings->pwd_secure_min = '8';
$settings->default_avatar = 'default.png';
$settings->show_archived_in_list = 0;
$settings->save();
if ($user = User::where('username', '=', 'admin')->first()) {

View File

@@ -17,21 +17,27 @@ class StatuslabelSeeder extends Seeder
Statuslabel::factory()->rtd()->create([
'name' => 'Ready to Deploy',
'created_by' => $admin->id,
'status_type' => 'deployable',
'legacy_deployable' => 1,
]);
Statuslabel::factory()->pending()->create([
'name' => 'Pending',
'created_by' => $admin->id,
'status_type' => 'pending',
'legacy_pending' => 1,
]);
Statuslabel::factory()->archived()->create([
'name' => 'Archived',
'created_by' => $admin->id,
'status_type' => 'archived',
'legacy_archived' => 1,
]);
Statuslabel::factory()->outForDiagnostics()->create(['created_by' => $admin->id]);
Statuslabel::factory()->outForRepair()->create(['created_by' => $admin->id]);
Statuslabel::factory()->broken()->create(['created_by' => $admin->id]);
Statuslabel::factory()->lost()->create(['created_by' => $admin->id]);
Statuslabel::factory()->outForDiagnostics()->pending()->create(['created_by' => $admin->id]);
Statuslabel::factory()->outForRepair()->pending()->create(['created_by' => $admin->id]);
Statuslabel::factory()->broken()->archived()->create(['created_by' => $admin->id]);
Statuslabel::factory()->lost()->archived()->create(['created_by' => $admin->id]);
}
}

6
package-lock.json generated
View File

@@ -35,7 +35,6 @@
"select2": "4.0.13",
"sheetjs": "^2.0.0",
"signature_pad": "^4.2.0",
"tablednd": "^1.0.5",
"tableexport.jquery.plugin": "1.30.0",
"tether": "^1.4.0",
"webpack": "^5.94.0"
@@ -10389,11 +10388,6 @@
"acorn-node": "^1.2.0"
}
},
"node_modules/tablednd": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tablednd/-/tablednd-1.0.5.tgz",
"integrity": "sha512-iC230jusaekjzzAy0jigZLbzb7jKKnW9xiNiZ4W17p7y8AvBYf5KunyrIE3HwL1FhL2VEeogZtjP5VeBeIW4xg=="
},
"node_modules/tableexport.jquery.plugin": {
"version": "1.30.0",
"license": "MIT",

View File

@@ -55,7 +55,6 @@
"select2": "4.0.13",
"sheetjs": "^2.0.0",
"signature_pad": "^4.2.0",
"tablednd": "^1.0.5",
"tableexport.jquery.plugin": "1.30.0",
"tether": "^1.4.0",
"webpack": "^5.94.0"

View File

@@ -2,8 +2,6 @@
.fix-sticky{position:fixed !important;overflow:hidden;z-index:100}.fix-sticky table thead{background:#fff}.fix-sticky table thead.thead-light{background:#e9ecef}.fix-sticky table thead.thead-dark{background:#212529}
.reorder-rows-on-drag-class td{background-color:#eee;box-shadow:6px 4px 5px 1px #555,0 1px 0 #ccc inset,0 -1px 0 #ccc inset;-box-shadow:6px 4px 5px 1px #555,0 1px 0 #ccc inset,0 -1px 0 #ccc inset}.reorder-rows-on-drag-class td:last-child{box-shadow:0 9px 4px -4px #555,0 1px 0 #ccc inset,0 -1px 0 #ccc inset,-1px 0 0 #ccc inset;-box-shadow:0 9px 4px -4px #555,0 1px 0 #ccc inset,0 -1px 0 #ccc inset,-1px 0 0 #ccc inset}
/*
* dragtable
*

File diff suppressed because it is too large Load Diff

View File

@@ -108,8 +108,8 @@
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=393aaa7b368b0670fc42434c8cca7dc7",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=9a35fafd5eb2a66d108b8b669e84f9e1",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=393d720a0f9aba560094fbc8d3b0c0f0",
"/js/build/vendor.js": "/js/build/vendor.js?id=5269eb5a6beb74f03387c78938cf17b2",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=bdb65484f5b26ceb5d8ca13331ff2749",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=6660df122e24940d42d03c06775fec7b",
"/js/dist/all.js": "/js/dist/all.js?id=e0a4b1a80b09333a460973137f39eab4"
}

View File

@@ -2557,7 +2557,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
}
function bindInputValue(el, value) {
if (el.type === "radio") {
if (isRadio(el)) {
if (el.attributes.value === void 0) {
el.value = value;
}
@@ -2568,7 +2568,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
el.checked = checkedAttrLooseCompare(el.value, value);
}
}
} else if (el.type === "checkbox") {
} else if (isCheckbox(el)) {
if (Number.isInteger(value)) {
el.value = value;
} else if (!Array.isArray(value) && typeof value !== "boolean" && ![null, void 0].includes(value)) {
@@ -2707,6 +2707,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
return attr;
}
function isCheckbox(el) {
return el.type === "checkbox" || el.localName === "ui-checkbox" || el.localName === "ui-switch";
}
function isRadio(el) {
return el.type === "radio" || el.localName === "ui-radio";
}
function debounce2(func, wait) {
var timeout;
return function() {
@@ -2860,7 +2866,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
get raw() {
return raw;
},
version: "3.14.1",
version: "3.14.3",
flushAndStopDeferringMutations,
dontAutoEvaluateFunctions,
disableEffectScheduling,
@@ -3296,7 +3302,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
setValue(getInputValue(el, modifiers, e, getValue()));
});
if (modifiers.includes("fill")) {
if ([void 0, null, ""].includes(getValue()) || el.type === "checkbox" && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
if ([void 0, null, ""].includes(getValue()) || isCheckbox(el) && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
setValue(getInputValue(el, modifiers, { target: el }, getValue()));
}
}
@@ -3336,7 +3342,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
return mutateDom(() => {
if (event instanceof CustomEvent && event.detail !== void 0)
return event.detail !== null && event.detail !== void 0 ? event.detail : event.target.value;
else if (el.type === "checkbox") {
else if (isCheckbox(el)) {
if (Array.isArray(currentValue)) {
let newValue = null;
if (modifiers.includes("number")) {
@@ -3367,7 +3373,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
});
} else {
let newValue;
if (el.type === "radio") {
if (isRadio(el)) {
if (event.target.checked) {
newValue = event.target.value;
} else {
@@ -6820,8 +6826,6 @@ var require_module_cjs8 = __commonJS({
let toAttributes = Array.from(to.attributes);
for (let i = domAttributes.length - 1; i >= 0; i--) {
let name = domAttributes[i].name;
if (name === "style")
continue;
if (!to.hasAttribute(name)) {
from2.removeAttribute(name);
}
@@ -6829,8 +6833,6 @@ var require_module_cjs8 = __commonJS({
for (let i = toAttributes.length - 1; i >= 0; i--) {
let name = toAttributes[i].name;
let value = toAttributes[i].value;
if (name === "style")
continue;
if (from2.getAttribute(name) !== value) {
from2.setAttribute(name, value);
}
@@ -9015,6 +9017,44 @@ function injectStyles() {
document.head.appendChild(style);
}
// js/plugins/navigate/popover.js
function packUpPersistedPopovers(persistedEl) {
persistedEl.querySelectorAll(":popover-open").forEach((el) => {
el.setAttribute("data-navigate-popover-open", "");
let animations = el.getAnimations();
el._pausedAnimations = animations.map((animation) => ({
keyframes: animation.effect.getKeyframes(),
options: {
duration: animation.effect.getTiming().duration,
easing: animation.effect.getTiming().easing,
fill: animation.effect.getTiming().fill,
iterations: animation.effect.getTiming().iterations
},
currentTime: animation.currentTime,
playState: animation.playState
}));
animations.forEach((i) => i.pause());
});
}
function unPackPersistedPopovers(persistedEl) {
persistedEl.querySelectorAll("[data-navigate-popover-open]").forEach((el) => {
el.removeAttribute("data-navigate-popover-open");
queueMicrotask(() => {
if (!el.isConnected)
return;
el.showPopover();
el.getAnimations().forEach((i) => i.finish());
if (el._pausedAnimations) {
el._pausedAnimations.forEach(({ keyframes, options, currentTime, now, playState }) => {
let animation = el.animate(keyframes, options);
animation.currentTime = currentTime;
});
delete el._pausedAnimations;
}
});
});
}
// js/plugins/navigate/page.js
var oldBodyScriptTagHashes = [];
var attributesExemptFromScriptTagHashing = [
@@ -9153,7 +9193,7 @@ var autofocus = false;
function navigate_default(Alpine19) {
Alpine19.navigate = (url) => {
let destination = createUrlObjectFromString(url);
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
history: false,
cached: false
@@ -9184,7 +9224,7 @@ function navigate_default(Alpine19) {
storeThePrefetchedHtmlForWhenALinkIsClicked(html, destination, finalDestination);
});
whenItIsReleased(() => {
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
history: false,
cached: false
@@ -9198,7 +9238,7 @@ function navigate_default(Alpine19) {
function navigateTo(destination, shouldPushToHistoryState = true) {
showProgressBar && showAndStartProgressBar();
fetchHtmlOrUsePrefetchedHtml(destination, (html, finalDestination) => {
fireEventForOtherLibariesToHookInto("alpine:navigating");
fireEventForOtherLibrariesToHookInto("alpine:navigating");
restoreScroll && storeScrollInformationInHtmlBeforeNavigatingAway();
showProgressBar && finishAndHideProgressBar();
cleanupAlpineElementsOnThePageThatArentInsideAPersistedElement();
@@ -9206,6 +9246,7 @@ function navigate_default(Alpine19) {
preventAlpineFromPickingUpDomChanges(Alpine19, (andAfterAllThis) => {
enablePersist && storePersistantElementsForLater((persistedEl) => {
packUpPersistedTeleports(persistedEl);
packUpPersistedPopovers(persistedEl);
});
if (shouldPushToHistoryState) {
updateUrlAndStoreLatestHtmlForFutureBackButtons(html, finalDestination);
@@ -9216,6 +9257,7 @@ function navigate_default(Alpine19) {
removeAnyLeftOverStaleTeleportTargets(document.body);
enablePersist && putPersistantElementsBack((persistedEl, newStub) => {
unPackPersistedTeleports(persistedEl);
unPackPersistedPopovers(persistedEl);
});
restoreScrollPositionOrScrollToTop();
afterNewScriptsAreDoneLoading(() => {
@@ -9224,7 +9266,7 @@ function navigate_default(Alpine19) {
autofocus && autofocusElementsWithTheAutofocusAttribute();
});
nowInitializeAlpineOnTheNewPage(Alpine19);
fireEventForOtherLibariesToHookInto("alpine:navigated");
fireEventForOtherLibrariesToHookInto("alpine:navigated");
});
});
});
@@ -9234,7 +9276,7 @@ function navigate_default(Alpine19) {
whenTheBackOrForwardButtonIsClicked((ifThePageBeingVisitedHasntBeenCached) => {
ifThePageBeingVisitedHasntBeenCached((url) => {
let destination = createUrlObjectFromString(url);
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
history: true,
cached: false
@@ -9246,7 +9288,7 @@ function navigate_default(Alpine19) {
});
}, (html, url, currentPageUrl, currentPageKey) => {
let destination = createUrlObjectFromString(url);
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
history: true,
cached: true
@@ -9254,29 +9296,31 @@ function navigate_default(Alpine19) {
if (prevented)
return;
storeScrollInformationInHtmlBeforeNavigatingAway();
fireEventForOtherLibariesToHookInto("alpine:navigating");
fireEventForOtherLibrariesToHookInto("alpine:navigating");
updateCurrentPageHtmlInSnapshotCacheForLaterBackButtonClicks(currentPageUrl, currentPageKey);
preventAlpineFromPickingUpDomChanges(Alpine19, (andAfterAllThis) => {
enablePersist && storePersistantElementsForLater((persistedEl) => {
packUpPersistedTeleports(persistedEl);
packUpPersistedPopovers(persistedEl);
});
swapCurrentPageWithNewHtml(html, () => {
removeAnyLeftOverStaleProgressBars();
removeAnyLeftOverStaleTeleportTargets(document.body);
enablePersist && putPersistantElementsBack((persistedEl, newStub) => {
unPackPersistedTeleports(persistedEl);
unPackPersistedPopovers(persistedEl);
});
restoreScrollPositionOrScrollToTop();
andAfterAllThis(() => {
autofocus && autofocusElementsWithTheAutofocusAttribute();
nowInitializeAlpineOnTheNewPage(Alpine19);
fireEventForOtherLibariesToHookInto("alpine:navigated");
fireEventForOtherLibrariesToHookInto("alpine:navigated");
});
});
});
});
setTimeout(() => {
fireEventForOtherLibariesToHookInto("alpine:navigated");
fireEventForOtherLibrariesToHookInto("alpine:navigated");
});
}
function fetchHtmlOrUsePrefetchedHtml(fromDestination, callback) {
@@ -9293,7 +9337,7 @@ function preventAlpineFromPickingUpDomChanges(Alpine19, callback) {
});
});
}
function fireEventForOtherLibariesToHookInto(name, detail) {
function fireEventForOtherLibrariesToHookInto(name, detail) {
let event = new CustomEvent(name, {
cancelable: true,
bubbles: true,
@@ -9809,6 +9853,7 @@ function morph2(component, el, html) {
},
lookahead: false
});
trigger("morphed", { el, component });
}
function isntElement(el) {
return typeof el.hasAttribute !== "function";
@@ -10878,3 +10923,4 @@ focus-trap/dist/focus-trap.js:
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
*)
*/
//# sourceMappingURL=livewire.esm.js.map

View File

@@ -1975,7 +1975,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
}
function bindInputValue(el, value) {
if (el.type === "radio") {
if (isRadio(el)) {
if (el.attributes.value === void 0) {
el.value = value;
}
@@ -1986,7 +1986,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
el.checked = checkedAttrLooseCompare(el.value, value);
}
}
} else if (el.type === "checkbox") {
} else if (isCheckbox(el)) {
if (Number.isInteger(value)) {
el.value = value;
} else if (!Array.isArray(value) && typeof value !== "boolean" && ![null, void 0].includes(value)) {
@@ -2125,6 +2125,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
return attr;
}
function isCheckbox(el) {
return el.type === "checkbox" || el.localName === "ui-checkbox" || el.localName === "ui-switch";
}
function isRadio(el) {
return el.type === "radio" || el.localName === "ui-radio";
}
function debounce(func, wait) {
var timeout;
return function() {
@@ -2278,7 +2284,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
get raw() {
return raw;
},
version: "3.14.1",
version: "3.14.3",
flushAndStopDeferringMutations,
dontAutoEvaluateFunctions,
disableEffectScheduling,
@@ -3361,7 +3367,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
setValue(getInputValue(el, modifiers, e, getValue()));
});
if (modifiers.includes("fill")) {
if ([void 0, null, ""].includes(getValue()) || el.type === "checkbox" && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
if ([void 0, null, ""].includes(getValue()) || isCheckbox(el) && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
setValue(getInputValue(el, modifiers, { target: el }, getValue()));
}
}
@@ -3401,7 +3407,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
return mutateDom(() => {
if (event instanceof CustomEvent && event.detail !== void 0)
return event.detail !== null && event.detail !== void 0 ? event.detail : event.target.value;
else if (el.type === "checkbox") {
else if (isCheckbox(el)) {
if (Array.isArray(currentValue)) {
let newValue = null;
if (modifiers.includes("number")) {
@@ -3432,7 +3438,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
});
} else {
let newValue;
if (el.type === "radio") {
if (isRadio(el)) {
if (event.target.checked) {
newValue = event.target.value;
} else {
@@ -4971,11 +4977,11 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
var checked = getCheckedRadio(radioSet, node.form);
return !checked || checked === node;
};
var isRadio = function isRadio2(node) {
var isRadio2 = function isRadio22(node) {
return isInput(node) && node.type === "radio";
};
var isNonTabbableRadio = function isNonTabbableRadio2(node) {
return isRadio(node) && !isTabbableRadio(node);
return isRadio2(node) && !isTabbableRadio(node);
};
var isZeroArea = function isZeroArea2(node) {
var _node$getBoundingClie = node.getBoundingClientRect(), width = _node$getBoundingClie.width, height = _node$getBoundingClie.height;
@@ -7625,6 +7631,44 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
document.head.appendChild(style);
}
// js/plugins/navigate/popover.js
function packUpPersistedPopovers(persistedEl) {
persistedEl.querySelectorAll(":popover-open").forEach((el) => {
el.setAttribute("data-navigate-popover-open", "");
let animations = el.getAnimations();
el._pausedAnimations = animations.map((animation) => ({
keyframes: animation.effect.getKeyframes(),
options: {
duration: animation.effect.getTiming().duration,
easing: animation.effect.getTiming().easing,
fill: animation.effect.getTiming().fill,
iterations: animation.effect.getTiming().iterations
},
currentTime: animation.currentTime,
playState: animation.playState
}));
animations.forEach((i) => i.pause());
});
}
function unPackPersistedPopovers(persistedEl) {
persistedEl.querySelectorAll("[data-navigate-popover-open]").forEach((el) => {
el.removeAttribute("data-navigate-popover-open");
queueMicrotask(() => {
if (!el.isConnected)
return;
el.showPopover();
el.getAnimations().forEach((i) => i.finish());
if (el._pausedAnimations) {
el._pausedAnimations.forEach(({ keyframes, options, currentTime, now, playState }) => {
let animation = el.animate(keyframes, options);
animation.currentTime = currentTime;
});
delete el._pausedAnimations;
}
});
});
}
// js/plugins/navigate/page.js
var oldBodyScriptTagHashes = [];
var attributesExemptFromScriptTagHashing = [
@@ -7763,7 +7807,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
function navigate_default(Alpine3) {
Alpine3.navigate = (url) => {
let destination = createUrlObjectFromString(url);
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
history: false,
cached: false
@@ -7794,7 +7838,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
storeThePrefetchedHtmlForWhenALinkIsClicked(html, destination, finalDestination);
});
whenItIsReleased(() => {
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
history: false,
cached: false
@@ -7808,7 +7852,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
function navigateTo(destination, shouldPushToHistoryState = true) {
showProgressBar && showAndStartProgressBar();
fetchHtmlOrUsePrefetchedHtml(destination, (html, finalDestination) => {
fireEventForOtherLibariesToHookInto("alpine:navigating");
fireEventForOtherLibrariesToHookInto("alpine:navigating");
restoreScroll && storeScrollInformationInHtmlBeforeNavigatingAway();
showProgressBar && finishAndHideProgressBar();
cleanupAlpineElementsOnThePageThatArentInsideAPersistedElement();
@@ -7816,6 +7860,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
preventAlpineFromPickingUpDomChanges(Alpine3, (andAfterAllThis) => {
enablePersist && storePersistantElementsForLater((persistedEl) => {
packUpPersistedTeleports(persistedEl);
packUpPersistedPopovers(persistedEl);
});
if (shouldPushToHistoryState) {
updateUrlAndStoreLatestHtmlForFutureBackButtons(html, finalDestination);
@@ -7826,6 +7871,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
removeAnyLeftOverStaleTeleportTargets(document.body);
enablePersist && putPersistantElementsBack((persistedEl, newStub) => {
unPackPersistedTeleports(persistedEl);
unPackPersistedPopovers(persistedEl);
});
restoreScrollPositionOrScrollToTop();
afterNewScriptsAreDoneLoading(() => {
@@ -7834,7 +7880,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
autofocus && autofocusElementsWithTheAutofocusAttribute();
});
nowInitializeAlpineOnTheNewPage(Alpine3);
fireEventForOtherLibariesToHookInto("alpine:navigated");
fireEventForOtherLibrariesToHookInto("alpine:navigated");
});
});
});
@@ -7844,7 +7890,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
whenTheBackOrForwardButtonIsClicked((ifThePageBeingVisitedHasntBeenCached) => {
ifThePageBeingVisitedHasntBeenCached((url) => {
let destination = createUrlObjectFromString(url);
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
history: true,
cached: false
@@ -7856,7 +7902,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
});
}, (html, url, currentPageUrl, currentPageKey) => {
let destination = createUrlObjectFromString(url);
let prevented = fireEventForOtherLibariesToHookInto("alpine:navigate", {
let prevented = fireEventForOtherLibrariesToHookInto("alpine:navigate", {
url: destination,
history: true,
cached: true
@@ -7864,29 +7910,31 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
if (prevented)
return;
storeScrollInformationInHtmlBeforeNavigatingAway();
fireEventForOtherLibariesToHookInto("alpine:navigating");
fireEventForOtherLibrariesToHookInto("alpine:navigating");
updateCurrentPageHtmlInSnapshotCacheForLaterBackButtonClicks(currentPageUrl, currentPageKey);
preventAlpineFromPickingUpDomChanges(Alpine3, (andAfterAllThis) => {
enablePersist && storePersistantElementsForLater((persistedEl) => {
packUpPersistedTeleports(persistedEl);
packUpPersistedPopovers(persistedEl);
});
swapCurrentPageWithNewHtml(html, () => {
removeAnyLeftOverStaleProgressBars();
removeAnyLeftOverStaleTeleportTargets(document.body);
enablePersist && putPersistantElementsBack((persistedEl, newStub) => {
unPackPersistedTeleports(persistedEl);
unPackPersistedPopovers(persistedEl);
});
restoreScrollPositionOrScrollToTop();
andAfterAllThis(() => {
autofocus && autofocusElementsWithTheAutofocusAttribute();
nowInitializeAlpineOnTheNewPage(Alpine3);
fireEventForOtherLibariesToHookInto("alpine:navigated");
fireEventForOtherLibrariesToHookInto("alpine:navigated");
});
});
});
});
setTimeout(() => {
fireEventForOtherLibariesToHookInto("alpine:navigated");
fireEventForOtherLibrariesToHookInto("alpine:navigated");
});
}
function fetchHtmlOrUsePrefetchedHtml(fromDestination, callback) {
@@ -7903,7 +7951,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
});
});
}
function fireEventForOtherLibariesToHookInto(name, detail) {
function fireEventForOtherLibrariesToHookInto(name, detail) {
let event = new CustomEvent(name, {
cancelable: true,
bubbles: true,
@@ -8212,8 +8260,6 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
let toAttributes = Array.from(to.attributes);
for (let i = domAttributes.length - 1; i >= 0; i--) {
let name = domAttributes[i].name;
if (name === "style")
continue;
if (!to.hasAttribute(name)) {
from2.removeAttribute(name);
}
@@ -8221,8 +8267,6 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
for (let i = toAttributes.length - 1; i >= 0; i--) {
let name = toAttributes[i].name;
let value = toAttributes[i].value;
if (name === "style")
continue;
if (from2.getAttribute(name) !== value) {
from2.setAttribute(name, value);
}
@@ -8914,6 +8958,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
},
lookahead: false
});
trigger2("morphed", { el, component });
}
function isntElement(el) {
return typeof el.hasAttribute !== "function";

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,2 +1,2 @@
{"/livewire.js":"923613aa"}
{"/livewire.js":"38dc8241"}

View File

@@ -14,6 +14,8 @@ return [
'error' => 'Asset was not created, please try again. :(',
'success' => 'Asset created successfully. :)',
'success_linked' => 'Asset with tag :tag was created successfully. <strong><a href=":link" style="color: white;">Click here to view</a></strong>.',
'multi_success_linked' => 'Asset with tag :links was created successfully.|:count assets were created succesfully. :links.',
'partial_failure' => 'An asset was unable to be created. Reason: :failures|:count assets were unable to be created. Reasons: :failures',
],
'update' => [
@@ -79,6 +81,11 @@ return [
'no_assets_selected' => 'You must select at least one asset from the list',
],
'multi-checkout' => [
'error' => 'Asset was not checked out, please try again|Assets were not checked out, please try again',
'success' => 'Asset checked out successfully.|Assets checked out successfully.',
],
'checkin' => [
'error' => 'Asset was not checked in, please try again',
'success' => 'Asset checked in successfully.',

View File

@@ -230,7 +230,7 @@ return [
'purchase_date' => 'Purchase Date',
'qty' => 'QTY',
'quantity' => 'Quantity',
'quantity_minimum' => 'You have :count items below or almost below minimum quantity levels',
'quantity_minimum' => 'You have one item below or almost below minimum quantity levels|You have :count items below or almost below minimum quantity levels',
'quickscan_checkin' => 'Quick Scan Checkin',
'quickscan_checkin_status' => 'Checkin Status',
'ready_to_deploy' => 'Ready to Deploy',
@@ -434,6 +434,7 @@ return [
'alt_uploaded_image_thumbnail' => 'Uploaded thumbnail',
'placeholder_kit' => 'Select a kit',
'file_not_found' => 'File not found',
'log_record_not_found' => 'No record for that log entry was found.',
'preview_not_available' => '(no preview)',
'setup' => 'Setup',
'pre_flight' => 'Pre-Flight',

View File

@@ -155,102 +155,16 @@
@can('accessories.files', $accessory)
<div class="tab-pane" id="files">
<div class="table table-responsive">
<div class="row">
<div class="col-md-12">
<table
data-cookie-id-table="accessoryUploadsTable"
data-id-table="accessoryUploadsTable"
id="accessoryUploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-accessories-uploads-{{ str_slug($accessory->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@if ($accessory->uploads->count() > 0)
@foreach ($accessory->uploads as $file)
<tr>
<td>
<x-icon type="paperclip" class="fa-2x" />
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if ($file->filename)
@if ( Helper::checkUploadIsImage($file->get_src('accessories')))
<a href="{{ route('show.accessoryfile', ['accessoryId' => $accessory->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show.accessoryfile', ['accessoryId' => $accessory->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@endif
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists('private_uploads/accessories/'.$file->filename) ? Storage::size('private_uploads/accessories/'.$file->filename) : '') }}">
{{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/accessories/'.$file->filename) ? Storage::size('private_uploads/accessories/'.$file->filename) : '') }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td style="white-space: nowrap;">
@if ($file->filename)
<a href="{{ route('show.accessoryfile', [$accessory->id, $file->id]) }}" class="btn btn-sm btn-default">
<i class="fas fa-download" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show.accessoryfile', [$accessory->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm" href="{{ route('delete/accessoryfile', [$accessory->id, $file->id]) }}" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}" data-title="{{ trans('general.delete') }}">
<i class="fas fa-trash icon-white" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8">{{ trans('general.no_results') }}</td>
</tr>
@endif
</tbody>
</table>
<div class="row">
<div class="col-md-12">
<x-filestable
filepath="private_uploads/accessories/"
showfile_routename="show.accessoryfile"
deletefile_routename="delete/accessoryfile"
:object="$accessory" />
</div>
</div>
</div>
</div>
</div> <!-- /.tab-pane -->
</div> <!-- /.tab-pane -->
@endcan
</div>
</div>
@@ -265,7 +179,8 @@
@if ($accessory->image!='')
<div class="row">
<div class="col-md-12 text-center" style="padding-bottom: 15px;">
<a href="{{ Storage::disk('public')->url('accessories/'.e($accessory->image)) }}" data-toggle="lightbox"><img src="{{ Storage::disk('public')->url('accessories/'.e($accessory->image)) }}" class="img-responsive img-thumbnail" alt="{{ $accessory->name }}"></a>
<a href="{{ Storage::disk('public')->url('accessories/'.e($accessory->image)) }}" data-toggle="lightbox" data-type="image">
<img src="{{ Storage::disk('public')->url('accessories/'.e($accessory->image)) }}" class="img-responsive img-thumbnail" alt="{{ $accessory->name }}"></a>
</div>
</div>
@endif

View File

@@ -0,0 +1,142 @@
<!-- begin redirect submit options -->
@props([
'filepath',
'object',
'showfile_routename',
'deletefile_routename',
])
<!-- begin non-ajaxed file listing table -->
<div class="table-responsive">
<table
data-cookie-id-table="{{ str_slug($object->name) }}UploadsTable"
data-id-table="{{ str_slug($object->name) }}UploadsTable"
id="{{ str_slug($object->name) }}}UploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-license-uploads-{{ str_slug($object->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="false" data-field="id" data-sortable="true">
{{trans('general.id')}}
</th>
<th data-visible="true" data-field="type" data-sortable="true">
{{trans('general.file_type')}}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">
{{ trans('general.image') }}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">
{{ trans('general.file_name') }}
</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">
{{ trans('general.filesize') }}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">
{{ trans('general.notes') }}
</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">
{{ trans('general.download') }}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">
{{ trans('general.created_at') }}
</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_by" data-sortable="true">
{{ trans('general.created_by') }}
</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">
{{ trans('table.actions') }}
</th>
</tr>
</thead>
<tbody>
@foreach ($object->uploads as $file)
<tr>
<td>
{{ $file->id }}
</td>
<td data-sort-value="{{ pathinfo($filepath.$file->filename, PATHINFO_EXTENSION) }}">
@if (Storage::exists($filepath.$file->filename))
<span class="sr-only">{{ pathinfo($filepath.$file->filename, PATHINFO_EXTENSION) }}</span>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true" data-tooltip="true" data-title="{{ pathinfo($filepath.$file->filename, PATHINFO_EXTENSION) }}"></i>
@endif
</td>
<td>
@if (($file->filename) && (Storage::exists($filepath.$file->filename)))
@if (Helper::checkUploadIsImage($file->get_src(str_plural(strtolower(class_basename(get_class($object)))))))
<a href="{{ route($showfile_routename, [$object->id, $file->id, 'inline' => 'true']) }}" data-toggle="lightbox" data-type="image">
<img src="{{ route($showfile_routename, [$object->id, $file->id, 'inline' => 'true']) }}" class="img-thumbnail" style="max-width: 50px;">
</a>
@else
{{ trans('general.preview_not_available') }}
@endif
@else
<x-icon type="x" class="text-danger" />
{{ trans('general.file_not_found') }}
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists($filepath.$file->filename)) ? Storage::size($filepath.$file->filename) : '' }}">
{{ (Storage::exists($filepath.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size($filepath.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td style="white-space: nowrap;">
@if ($file->filename)
@if (Storage::exists($filepath.$file->filename))
<a href="{{ route($showfile_routename, [$object->id, $file->id]) }}" class="btn btn-sm btn-default">
<x-icon type="download" />
<span class="sr-only">
{{ trans('general.download') }}
</span>
</a>
<a href="{{ StorageHelper::allowSafeInline($filepath.$file->filename) ? route($showfile_routename, [$object->id, $file->id, 'inline' => 'true']) : '#' }}" class="btn btn-sm btn-default{{ StorageHelper::allowSafeInline($filepath.$file->filename) ? '' : ' disabled' }}" target="_blank">
<x-icon type="external-link" />
</a>
@endif
@endif
</td>
<td>
{{ $file->created_at }}
</td>
<td>
{{ $file->created_by }}
</td>
<td>
<a class="btn delete-asset btn-danger btn-sm hidden-print" href="{{ route($deletefile_routename, [$object->id, $file->id]) }}" data-content="Are you sure you wish to delete this file?" data-title="{{ trans('general.delete') }} {{ $file->filename }}?">
<x-icon type="delete" />
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<!-- end non-ajaxed file listing table -->

View File

@@ -20,6 +20,8 @@
@include ('partials.forms.edit.quantity')
@include ('partials.forms.edit.minimum_quantity')
@include ('partials.forms.edit.serial', ['fieldname' => 'serial'])
@include ('partials.forms.edit.manufacturer-select', ['translated_name' => trans('general.manufacturer'), 'fieldname' => 'manufacturer_id'])
@include ('partials.forms.edit.model_number')
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
@include ('partials.forms.edit.location-select', ['translated_name' => trans('general.location'), 'fieldname' => 'location_id'])
@include ('partials.forms.edit.supplier-select', ['translated_name' => trans('general.supplier'), 'fieldname' => 'supplier_id'])

View File

@@ -140,96 +140,14 @@
@can('components.files', $component)
<div class="tab-pane" id="files">
<div class="table-responsive">
<table
data-cookie-id-table="componentUploadsTable"
data-id-table="componentUploadsTable"
id="componentUploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-components-uploads-{{ str_slug($component->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@if ($component->uploads->count() > 0)
@foreach ($component->uploads as $file)
<tr>
<td>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if ($file->filename)
@if ( Helper::checkUploadIsImage($file->get_src('components')))
<a href="{{ route('show.componentfile', ['componentId' => $component->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show.componentfile', ['componentId' => $component->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@endif
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists('private_uploads/components/'.$file->filename) ? Storage::size('private_uploads/components/'.$file->filename) : '') }}">
{{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/components/'.$file->filename) ? Storage::size('private_uploads/components/'.$file->filename) : '') }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if ($file->filename)
<nobr><a href="{{ route('show.componentfile', [$component->id, $file->id]) }}" class="btn btn-sm btn-default">
<x-icon type="download" />
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show.componentfile', [$component->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
</nobr>
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm" href="{{ route('delete/componentfile', [$component->id, $file->id]) }}" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}" data-title="{{ trans('general.delete') }}">
<i class="fas fa-trash icon-white" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8">{{ trans('general.no_results') }}</td>
</tr>
@endif
</tbody>
</table>
<div class="row">
<div class="col-md-12">
<x-filestable
filepath="private_uploads/components/"
showfile_routename="show.componentfile"
deletefile_routename="delete/componentfile"
:object="$component" />
</div>
</div>
</div> <!-- /.tab-pane -->
@endcan
@@ -243,7 +161,7 @@
<div class="col-md-3">
@if ($component->image!='')
<div class="col-md-12 text-center" style="padding-bottom: 15px;">
<a href="{{ Storage::disk('public')->url('components/'.e($component->image)) }}" data-toggle="lightbox">
<a href="{{ Storage::disk('public')->url('components/'.e($component->image)) }}" data-toggle="lightbox" data-type="image">
<img src="{{ Storage::disk('public')->url('components/'.e($component->image)) }}" class="img-responsive img-thumbnail" alt="{{ $component->name }}"></a>
</div>

View File

@@ -91,7 +91,7 @@
<label for="qty" class="col-md-3 control-label">{{ trans('general.qty') }}</label>
<div class="col-md-7 col-sm-12 required">
<div class="col-md-2" style="padding-left:0px">
<input class="form-control" type="number" name="qty" id="qty" value="1" min="1" max="{{$consumable->numRemaining()}}" maxlength="999999" />
<input class="form-control" type="number" name="checkout_qty" id="checkout_qty" value="1" min="1" max="{{$consumable->numRemaining()}}" maxlength="999999" />
</div>
</div>
{!! $errors->first('qty', '<div class="col-md-8 col-md-offset-3"><span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span></div>') !!}

View File

@@ -89,7 +89,7 @@
@if ($consumable->image!='')
<div class="col-md-12 text-center" style="padding-bottom: 20px;">
<a href="{{ Storage::disk('public')->url('consumables/'.e($consumable->image)) }}" data-toggle="lightbox">
<a href="{{ Storage::disk('public')->url('consumables/'.e($consumable->image)) }}" data-toggle="lightbox" data-type="image">
<img src="{{ Storage::disk('public')->url('consumables/'.e($consumable->image)) }}" class="img-responsive img-thumbnail" alt="{{ $consumable->name }}"></a>
</div>
@endif
@@ -428,102 +428,18 @@
<div class="tab-pane" id="files">
<div class="row">
<div class="col-md-12">
<x-filestable
filepath="private_uploads/consumables/"
showfile_routename="show.consumablefile"
deletefile_routename="delete/consumablefile"
:object="$consumable" />
<div class="col-md-12 col-sm-12">
<div class="table-responsive">
<table
data-cookie-id-table="consumableUploadsTable"
data-id-table="consumableUploadsTable"
id="consumableUploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-consumables-uploads-{{ str_slug($consumable->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@if ($consumable->uploads->count() > 0)
@foreach ($consumable->uploads as $file)
<tr>
<td>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if ($file->filename)
@if ( Helper::checkUploadIsImage($file->get_src('consumables')))
<a href="{{ route('show.consumablefile', ['consumableId' => $consumable->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show.consumablefile', ['consumableId' => $consumable->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@endif
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists('private_uploads/consumables/'.$file->filename) ? Storage::size('private_uploads/consumables/'.$file->filename) : '') }}">
{{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/consumables/'.$file->filename) ? Storage::size('private_uploads/consumables/'.$file->filename) : '') }}
</td>
<td>
@if ($file->note)
{!! nl2br(Helper::parseEscapedMarkedownInline($file->note)) !!}
@endif
</td>
<td>
@if ($file->filename)
<a href="{{ route('show.consumablefile', [$consumable->id, $file->id]) }}" class="btn btn-sm btn-default">
<i class="fas fa-download" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show.consumablefile', [$consumable->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm" href="{{ route('delete/consumablefile', [$consumable->id, $file->id]) }}" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}" data-title="{{ trans('general.delete') }}">
<i class="fas fa-trash icon-white" aria-hidden="true"></i>
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8">{{ trans('general.no_results') }}</td>
</tr>
@endif
</tbody>
</table>
</div>
</div>
</div> <!--/ROW-->
</div>
</div><!--/FILES-->
<div class="tab-pane" id="history">

View File

@@ -47,6 +47,7 @@
<!-- drag handle -->
<span class="handle">
<i class="fas fa-ellipsis-v"></i>
<i class="fas fa-ellipsis-v"></i>
</span>
</td>
@endcan

View File

@@ -34,7 +34,7 @@
<div class="dashboard small-box bg-teal">
<div class="inner">
<h3>{{ number_format(\App\Models\Asset::AssetsForShow()->count()) }}</h3>
<p>{{ strtolower(trans('general.assets')) }}</p>
<p>{{ trans('general.assets') }}</p>
</div>
<div class="icon" aria-hidden="true">
<x-icon type="assets" />
@@ -54,7 +54,7 @@
<div class="dashboard small-box bg-maroon">
<div class="inner">
<h3>{{ number_format($counts['license']) }}</h3>
<p>{{ strtolower(trans('general.licenses')) }}</p>
<p>{{ trans('general.licenses') }}</p>
</div>
<div class="icon" aria-hidden="true">
<x-icon type="licenses" />
@@ -75,7 +75,7 @@
<div class="dashboard small-box bg-orange">
<div class="inner">
<h3> {{ number_format($counts['accessory']) }}</h3>
<p>{{ strtolower(trans('general.accessories')) }}</p>
<p>{{ trans('general.accessories') }}</p>
</div>
<div class="icon" aria-hidden="true">
<x-icon type="accessories" />
@@ -96,7 +96,7 @@
<div class="dashboard small-box bg-purple">
<div class="inner">
<h3> {{ number_format($counts['consumable']) }}</h3>
<p>{{ strtolower(trans('general.consumables')) }}</p>
<p>{{ trans('general.consumables') }}</p>
</div>
<div class="icon" aria-hidden="true">
<x-icon type="consumables" />
@@ -115,7 +115,7 @@
<div class="dashboard small-box bg-yellow">
<div class="inner">
<h3>{{ number_format($counts['component']) }}</h3>
<p>{{ strtolower(trans('general.components')) }}</p>
<p>{{ trans('general.components') }}</p>
</div>
<div class="icon" aria-hidden="true">
<x-icon type="components" />
@@ -135,7 +135,7 @@
<div class="dashboard small-box bg-light-blue">
<div class="inner">
<h3>{{ number_format($counts['user']) }}</h3>
<p>{{ strtolower(trans('general.people')) }}</p>
<p>{{ trans('general.people') }}</p>
</div>
<div class="icon" aria-hidden="true">
<x-icon type="users" />

View File

@@ -115,5 +115,12 @@
@section('moar_scripts')
@include('partials/assets-assigned')
<script nonce="{{ csrf_token() }}">
$(function () {
//if there's already a user selected, make sure their checked-out assets show up
// (if there isn't one, it won't do anything)
$('#assigned_user').change();
});
</script>
@stop

View File

@@ -168,7 +168,7 @@
<div class="col-md-12 text-center">
@if (($asset->image) || (($asset->model) && ($asset->model->image!='')))
<div class="text-center col-md-12" style="padding-bottom: 15px;">
<a href="{{ ($asset->getImageUrl()) ? $asset->getImageUrl() : null }}" data-toggle="lightbox">
<a href="{{ ($asset->getImageUrl()) ? $asset->getImageUrl() : null }}" data-toggle="lightbox" data-type="image">
<img src="{{ ($asset->getImageUrl()) ? $asset->getImageUrl() : null }}" class="assetimg img-responsive" alt="{{ $asset->getDisplayNameAttribute() }}">
</a>
</div>
@@ -1362,102 +1362,11 @@
<div class="tab-pane fade" id="files">
<div class="row{{ ($asset->uploads->count() > 0 ) ? '' : ' hidden-print' }}">
<div class="col-md-12">
@if ($asset->uploads->count() > 0)
<table
class="table table-striped snipe-table"
id="assetFileHistory"
data-pagination="true"
data-id-table="assetFileHistory"
data-search="true"
data-side-pagination="client"
data-sortable="true"
data-show-columns="true"
data-show-fullscreen="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
"fileName": "export-asset-{{ $asset->id }}-files",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'
data-cookie-id-table="assetFileHistory">
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($asset->uploads as $file)
<tr>
<td><i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i></td>
<td>
@if ( Helper::checkUploadIsImage($file->get_src('assets')))
<a href="{{ route('show/assetfile', ['assetId' => $asset->id, 'fileId' =>$file->id]) }}" data-toggle="lightbox" data-type="image" data-title="{{ $file->filename }}" data-footer="{{ Helper::getFormattedDateObject($asset->last_checkout, 'datetime', false) }}">
<img src="{{ route('show/assetfile', ['assetId' => $asset->id, 'fileId' =>$file->id]) }}" style="max-width: 50px;">
</a>
@endif
</td>
<td>
@if (Storage::exists('private_uploads/assets/'.$file->filename))
{{ $file->filename }}
@else
<del>{{ $file->filename }}</del>
@endif
</td>
<td data-value="{{ (Storage::exists('private_uploads/assets/'.$file->filename) ? Storage::size('private_uploads/assets/'.$file->filename) : '') }}">
{{ @Helper::formatFilesizeUnits(Storage::exists('private_uploads/assets/'.$file->filename) ? Storage::size('private_uploads/assets/'.$file->filename) : '') }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if (($file->filename) && (Storage::exists('private_uploads/assets/'.$file->filename)))
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'download'=>'true']) }}" class="btn btn-sm btn-default hidden-print">
<x-icon type="download" />
</a>
<a href="{{ route('show/assetfile', [$asset->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default hidden-print" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>
@if ($file->created_at)
{{ Helper::getFormattedDateObject($file->created_at, 'datetime', false) }}
@endif
</td>
<td>
@can('update', \App\Models\Asset::class)
<a class="btn delete-asset btn-sm btn-danger btn-sm hidden-print" href="{{ route('delete/assetfile', [$asset->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}">
<x-icon type="delete" />
</a>
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-info alert-block hidden-print">
<x-icon type="info-circle" />
{{ trans('general.no_results') }}
</div>
@endif
<x-filestable
filepath="private_uploads/assets/"
showfile_routename="show/assetfile"
deletefile_routename="delete/modelfile"
:object="$asset" />
</div> <!-- /.col-md-12 -->
</div> <!-- /.row -->
</div> <!-- /.tab-pane files -->
@@ -1467,101 +1376,11 @@
<div class="row{{ (($asset->model) && ($asset->model->uploads->count() > 0)) ? '' : ' hidden-print' }}">
<div class="col-md-12">
@if (($asset->model) && ($asset->model->uploads->count() > 0))
<table
class="table table-striped snipe-table"
id="assetModelFileHistory"
data-pagination="true"
data-id-table="assetModelFileHistory"
data-search="true"
data-side-pagination="client"
data-sortable="true"
data-show-columns="true"
data-show-fullscreen="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
"fileName": "export-assetmodel-{{ $asset->model->id }}-files",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'
data-cookie-id-table="assetFileHistory">
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($asset->model->uploads as $file)
<tr>
<td><i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i></td>
<td>
@if ( Helper::checkUploadIsImage($file->get_src('assetmodels')))
<a href="{{ route('show/modelfile', ['modelID' => $asset->model->id, 'fileId' =>$file->id]) }}" data-toggle="lightbox" data-type="image" data-title="{{ $file->filename }}" data-footer="{{ Helper::getFormattedDateObject($asset->last_checkout, 'datetime', false) }}">
<img src="{{ route('show/modelfile', ['modelID' => $asset->model->id, 'fileId' =>$file->id]) }}" style="max-width: 50px;">
</a>
@endif
</td>
<td>
@if (Storage::exists('private_uploads/assetmodels/'.$file->filename))
{{ $file->filename }}
@else
<del>{{ $file->filename }}</del>
@endif
</td>
<td data-value="{{ (Storage::exists('private_uploads/assetmodels/'.$file->filename)) ? Storage::size('private_uploads/assetmodels/'.$file->filename) : '' }}">
{{ (Storage::exists('private_uploads/assetmodels/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/assetmodels/'.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if (($file->filename) && (Storage::exists('private_uploads/assetmodels/'.$file->filename)))
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id]) }}" class="btn btn-sm btn-default hidden-print">
<x-icon type="download" class="hidden-print" />
</a>
<a href="{{ route('show/modelfile', [$asset->model->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default hidden-print" target="_blank">
<x-icon type="external-link" class="hidden-print" />
</a>
@endif
</td>
<td>
@if ($file->created_at)
{{ Helper::getFormattedDateObject($file->created_at, 'datetime', false) }}
@endif
</td>
<td>
@can('update', \App\Models\AssetModel::class)
<a class="btn delete-asset btn-sm btn-danger btn-sm hidden-print" href="{{ route('delete/modelfile', [$asset->model->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}">
<x-icon type="delete" class="hidden-print"/></i>
</a>
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-info alert-block">
<x-icon type="info-circle" />
{{ trans('general.no_results') }}
</div>
@endif
<x-filestable
filepath="private_uploads/assetmodels/"
showfile_routename="show/modelfile"
deletefile_routename="userfile.destroy"
:object="$asset->model" />
</div> <!-- /.col-md-12 -->
</div> <!-- /.row -->

View File

@@ -279,7 +279,7 @@ dir="{{ Helper::determineLanguageDirection() }}">
@endif
</a>
<ul class="dropdown-menu">
<li class="header">{{ trans('general.quantity_minimum', array('count' => count($alert_items))) }}</li>
<li class="header">{{ trans_choice('general.quantity_minimum', count($alert_items)) }}</li>
<li>
<!-- inner menu: contains the actual data -->
<ul class="menu">
@@ -287,7 +287,7 @@ dir="{{ Helper::determineLanguageDirection() }}">
@for($i = 0; count($alert_items) > $i; $i++)
<li><!-- Task item -->
<a href="{{route($alert_items[$i]['type'].'.show', $alert_items[$i]['id'])}}">
<a href="{{ route($alert_items[$i]['type'].'.show', $alert_items[$i]['id'])}}">
<h2 class="task_menu">{{ $alert_items[$i]['name'] }}
<small class="pull-right">
{{ $alert_items[$i]['remaining'] }} {{ trans('general.remaining') }}

View File

@@ -464,100 +464,13 @@
@can('licenses.files', $license)
<div class="tab-pane" id="files">
<div class="table-responsive">
<table
data-cookie-id-table="licenseUploadsTable"
data-id-table="licenseUploadsTable"
id="licenseUploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-license-uploads-{{ str_slug($license->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@if ($license->uploads->count() > 0)
@foreach ($license->uploads as $file)
<tr>
<td>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if ($file->filename)
@if ((Storage::exists('private_uploads/licenses/'.$file->filename)) && ( Helper::checkUploadIsImage($file->get_src('licenses'))))
<a href="{{ route('show.licensefile', ['licenseId' => $license->id, 'fileId' => $file->id, 'download' => 'false']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show.licensefile', ['licenseId' => $license->id, 'fileId' => $file->id]) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@endif
@endif
</td>
<td>
@if (Storage::exists('private_uploads/licenses/'.$file->filename))
{{ $file->filename }}
@else
<del>{{ $file->filename }}</del>
@endif
</td>
<td data-value="{{ (Storage::exists('private_uploads/licenses/'.$file->filename)) ? Storage::size('private_uploads/licenses/'.$file->filename) : '' }}">
{{ (Storage::exists('private_uploads/licenses/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/licenses/'.$file->filename)) : '' }}
</td>
<x-filestable
filepath="private_uploads/licenses/"
showfile_routename="show.licensefile"
deletefile_routename="delete/licensefile"
:object="$license" />
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if ($file->filename)
<a href="{{ route('show.licensefile', [$license->id, $file->id]) }}" class="btn btn-sm btn-default">
<x-icon type="download"/>
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show.licensefile', [$license->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm" href="{{ route('delete/licensefile', [$license->id, $file->id]) }}" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}" data-title="{{ trans('general.delete') }}">
<x-icon type="delete" />
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="8">{{ trans('general.no_results') }}</td>
</tr>
@endif
</tbody>
</table>
</div>
</div> <!-- /.tab-pane -->
@endcan

View File

@@ -79,6 +79,19 @@
{{ trans('general.consumables') }}
{!! ($manufacturer->consumables->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->consumables->count()).'</badge>' : '' !!}
</span>
</a>
</li>
<li>
<a href="#components" data-toggle="tab">
<span class="hidden-lg hidden-md">
<x-icon type="components" class="fa-2x" />
</span>
<span class="hidden-xs hidden-sm">
{{ trans('general.components') }}
{!! ($manufacturer->components->count() > 0 ) ? '<badge class="badge badge-secondary">'.number_format($manufacturer->components->count()).'</badge>' : '' !!}
</span>
</a>
</li>
@@ -186,12 +199,35 @@
class="table table-striped snipe-table"
data-url="{{ route('api.consumables.index', ['manufacturer_id' => $manufacturer->id]) }}"
data-export-options='{
"fileName": "export-manufacturers-{{ str_slug($manufacturer->name) }}-consumabled-{{ date('Y-m-d') }}",
"fileName": "export-manufacturers-{{ str_slug($manufacturer->name) }}-consumables-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div> <!-- /.tab-pan consumables-->
<div class="tab-pane fade" id="components">
<table
data-columns="{{ \App\Presenters\ComponentPresenter::dataTableLayout() }}"
data-cookie-id-table="componentsTable"
data-pagination="true"
data-id-table="componentsTable"
data-search="true"
data-show-footer="true"
data-side-pagination="server"
data-show-columns="true"
data-show-export="true"
data-show-refresh="true"
data-sort-order="asc"
id="componentsTable"
class="table table-striped snipe-table"
data-url="{{ route('api.components.index', ['manufacturer_id' => $manufacturer->id]) }}"
data-export-options='{
"fileName": "export-manufacturers-{{ str_slug($manufacturer->name) }}-components-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'>
</table>
</div> <!-- /.tab-pan consumables-->

View File

@@ -107,99 +107,11 @@
<div class="row">
<div class="col-md-12">
@if ($model->uploads->count() > 0)
<table
class="table table-striped snipe-table"
id="modelFileHistory"
data-pagination="true"
data-id-table="modelFileHistory"
data-search="true"
data-side-pagination="client"
data-sortable="true"
data-show-columns="true"
data-show-fullscreen="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
"fileName": "export-asset-{{ $model->id }}-files",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'
data-cookie-id-table="assetFileHistory">
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($model->uploads as $file)
<tr>
<td><i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i></td>
<td>
@if ((Storage::exists('private_uploads/assetmodels/'.$file->filename)) && ( Helper::checkUploadIsImage($file->get_src('assetmodels'))))
<a href="{{ route('show/modelfile', ['modelID' => $model->id, 'fileId' => $file->id]) }}" data-toggle="lightbox" data-type="image" data-title="{{ $file->filename }}">
<img src="{{ route('show/modelfile', ['modelID' => $model->id, 'fileId' =>$file->id]) }}" style="max-width: 50px;">
</a>
@endif
</td>
<td>
@if (Storage::exists('private_uploads/assetmodels/'.$file->filename))
{{ $file->filename }}
@else
<del>{{ $file->filename }}</del>
@endif
</td>
<td data-value="{{ (Storage::exists('private_uploads/assetmodels/'.$file->filename)) ? Storage::size('private_uploads/assetmodels/'.$file->filename) : '' }}">
{{ (Storage::exists('private_uploads/assetmodels/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/assetmodels/'.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td style="white-space: nowrap">
@if (($file->filename) && (Storage::exists('private_uploads/assetmodels/'.$file->filename)))
<a href="{{ route('show/modelfile', [$model->id, $file->id]) }}" class="btn btn-sm btn-default">
<i class="fas fa-download" aria-hidden="true"></i>
</a>
<a href="{{ route('show/modelfile', [$model->id, $file->id, 'inline'=>'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
</td>
<td>
@if ($file->created_at)
{{ Helper::getFormattedDateObject($file->created_at, 'datetime', false) }}
@endif
</td>
<td>
@can('update', \App\Models\AssetModel::class)
<a class="btn delete-asset btn-sm btn-danger btn-sm" href="{{ route('delete/assetfile', [$model->id, $file->id]) }}" data-tooltip="true" data-title="Delete" data-content="{{ trans('general.delete_confirm', ['item' => $file->filename]) }}"><i class="fas fa-trash icon-white" aria-hidden="true"></i></a>
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
@else
<div class="alert alert-info alert-block">
<i class="fas fa-info-circle"></i>
{{ trans('general.no_results') }}
</div>
@endif
<x-filestable
filepath="private_uploads/assetmodels/"
showfile_routename="show/modelfile"
deletefile_routename="delete/modelfile"
:object="$model" />
</div> <!-- /.col-md-12 -->
</div> <!-- /.row -->

View File

@@ -11,6 +11,9 @@
| **{{ trans('mail.checkout_date') }}** | {{ $checkout_date }} |
@endif
| **{{ trans('general.consumable') }}** | {{ $item->name }} |
@if (isset($qty))
| **{{ trans('general.qty') }}** | {{ $qty }} |
@endif
@if (isset($item->manufacturer))
| **{{ trans('general.manufacturer') }}** | {{ $item->manufacturer->name }} |
@endif

View File

@@ -16,17 +16,20 @@
</label>
<select name="bulk_actions" class="form-control select2" aria-label="bulk_actions" style="min-width: 350px;">
@if((isset($status)) && ($status == 'Deleted'))
@can('delete', \App\Models\Asset::class)
<option value="restore">{{trans('button.restore')}}</option>
@endcan
@can('delete', \App\Models\Asset::class)
<option value="restore">{{trans('button.restore')}}</option>
@endcan
@else
@can('update', \App\Models\Asset::class)
<option value="edit">{{ trans('button.edit') }}</option>
@endcan
@can('delete', \App\Models\Asset::class)
<option value="delete">{{ trans('button.delete') }}</option>
@endcan
<option value="labels" {{$snipeSettings->shortcuts_enabled == 1 ? "accesskey=l" : ''}}>{{ trans_choice('button.generate_labels', 2) }}</option>
@can('update', \App\Models\Asset::class)
<option value="edit">{{ trans('button.edit') }}</option>
@endcan
@can('checkout', \App\Models\Asset::class)
<option value="checkout">{{ trans('general.bulk_checkout') }}</option>
@endcan
@can('delete', \App\Models\Asset::class)
<option value="delete">{{ trans('button.delete') }}</option>
@endcan
<option value="labels" {{$snipeSettings->shortcuts_enabled == 1 ? "accesskey=l" : ''}}>{{ trans_choice('button.generate_labels', 2) }}</option>
@endif
</select>

View File

@@ -98,52 +98,14 @@
},
formatNoMatches: function () {
return '{{ trans('table.no_matching_records') }}';
},
{{--onDrop: function (table, row) {--}}
{{-- var rows = table.tBodies[0].rows;--}}
{{-- var debugStr = "Row dropped was " + row.id + ". New order: ";--}}
{{-- for (var i = 0; i < rows.length; i++) {--}}
{{-- debugStr += rows[i].id + " ";--}}
{{-- }--}}
{{-- --}}
{{-- $.ajax({--}}
{{-- url: '{{ route('api.reorder') }}',--}}
{{-- type: 'POST',--}}
{{-- data: {--}}
{{-- ids: rows.map(function (row) {--}}
{{-- return row.id;--}}
{{-- })--}}
{{--},--}}
}
});
});
$(function () {
var debugStr = '';
$('.snipe-table').on('reorder-row.bs.table', function (e, data, row){
console.log('All Data:');
console.log(data);
console.log('Dragged Row Data:');
console.dir(row);
console.log('E:');
console.dir(e);
for (var i = 0; i < data.length; i++) {
debugStr += data[i].id + " ";
}
alert('re-order ID: ' + row.id + ' to ' + debugStr);
});
});
});
// function onReorderDrop (rows, row) {
// var rows = table.tBodies[0].rows;
// var debugStr = "Row dropped was " + row.id + ". New order: ";
// for (var i = 0; i < rows.length; i++) {
// debugStr += rows[i].id + " ";
// }
// }

View File

@@ -22,13 +22,13 @@
@include ('partials.forms.edit.name', ['translated_name' => trans('general.name')])
<!-- Label type -->
<div class="form-group{{ $errors->has('statuslabel_types') ? ' has-error' : '' }}">
<div class="form-group{{ $errors->has('status_type') ? ' has-error' : '' }}">
<label for="statuslabel_types" class="col-md-3 control-label">
{{ trans('admin/statuslabels/table.status_type') }}
</label>
<div class="col-md-7 required">
{{ Form::select('statuslabel_types', $statuslabel_types, $item->getStatuslabelType(), array('class'=>'select2', 'style'=>'width: 100%; min-width:400px', 'aria-label'=>'statuslabel_types')) }}
{!! $errors->first('statuslabel_types', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
{{ Form::select('status_type', $status_types, $item->status_type, array('class'=>'select2', 'style'=>'width: 100%; min-width:400px', 'aria-label'=>'status_type')) }}
{!! $errors->first('status_type', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
</div>

View File

@@ -24,7 +24,6 @@
<div class="box-body">
<div class="table-responsive">
<table
data-columns="{{ \App\Presenters\StatusLabelPresenter::dataTableLayout() }}"
data-cookie-id-table="statuslabelsTable"
@@ -37,8 +36,6 @@
data-show-export="true"
data-show-fullscreen="true"
data-show-refresh="true"
data-use-row-attr-func="true"
data-reorderable-rows="true"
data-sort-order="asc"
data-sort-name="name"
id="statuslabelsTable"

View File

@@ -971,102 +971,11 @@
<div class="row">
<div class="col-md-12 col-sm-12">
<div class="table-responsive">
<table
data-cookie-id-table="userUploadsTable"
data-id-table="userUploadsTable"
id="userUploadsTable"
data-search="true"
data-pagination="true"
data-side-pagination="client"
data-show-columns="true"
data-show-fullscreen="true"
data-show-export="true"
data-show-footer="true"
data-toolbar="#upload-toolbar"
data-show-refresh="true"
data-sort-order="asc"
data-sort-name="name"
class="table table-striped snipe-table"
data-export-options='{
"fileName": "export-license-uploads-{{ str_slug($user->name) }}-{{ date('Y-m-d') }}",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","delete","download","icon"]
}'>
<thead>
<tr>
<th data-visible="true" data-field="icon" data-sortable="true">{{trans('general.file_type')}}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="image">{{ trans('general.image') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="filename" data-sortable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="filesize">{{ trans('general.filesize') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="notes" data-sortable="true">{{ trans('general.notes') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="download">{{ trans('general.download') }}</th>
<th class="col-md-2" data-searchable="true" data-visible="true" data-field="created_at" data-sortable="true">{{ trans('general.created_at') }}</th>
<th class="col-md-1" data-searchable="true" data-visible="true" data-field="actions">{{ trans('table.actions') }}</th>
</tr>
</thead>
<tbody>
@foreach ($user->uploads as $file)
<tr>
<td>
<i class="{{ Helper::filetype_icon($file->filename) }} icon-med" aria-hidden="true"></i>
<span class="sr-only">{{ Helper::filetype_icon($file->filename) }}</span>
</td>
<td>
@if (($file->filename) && (Storage::exists('private_uploads/users/'.$file->filename)))
@if (Helper::checkUploadIsImage($file->get_src('users')))
<a href="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" data-toggle="lightbox" data-type="image"><img src="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" class="img-thumbnail" style="max-width: 50px;"></a>
@else
{{ trans('general.preview_not_available') }}
@endif
@else
<x-icon type="x" class="text-danger" />
{{ trans('general.file_not_found') }}
@endif
</td>
<td>
{{ $file->filename }}
</td>
<td data-value="{{ (Storage::exists('private_uploads/users/'.$file->filename)) ? Storage::size('private_uploads/users/'.$file->filename) : '' }}">
{{ (Storage::exists('private_uploads/users/'.$file->filename)) ? Helper::formatFilesizeUnits(Storage::size('private_uploads/users/'.$file->filename)) : '' }}
</td>
<td>
@if ($file->note)
{{ $file->note }}
@endif
</td>
<td>
@if ($file->filename)
@if (Storage::exists('private_uploads/users/'.$file->filename))
<a href="{{ route('show/userfile', [$user->id, $file->id]) }}" class="btn btn-sm btn-default">
<x-icon type="download" />
<span class="sr-only">{{ trans('general.download') }}</span>
</a>
<a href="{{ route('show/userfile', [$user->id, $file->id, 'inline' => 'true']) }}" class="btn btn-sm btn-default" target="_blank">
<x-icon type="external-link" />
</a>
@endif
@endif
</td>
<td>{{ $file->created_at }}</td>
<td>
<a class="btn delete-asset btn-danger btn-sm hidden-print" href="{{ route('userfile.destroy', [$user->id, $file->id]) }}" data-content="Are you sure you wish to delete this file?" data-title="{{ trans('general.delete') }} {{ $file->filename }}?">
<x-icon type="delete" />
<span class="sr-only">{{ trans('general.delete') }}</span>
</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<x-filestable
filepath="private_uploads/users/"
showfile_routename="show/userfile"
deletefile_routename="userfile.destroy"
:object="$user" />
</div>
</div> <!--/ROW-->
</div><!--/FILES-->

View File

@@ -0,0 +1,37 @@
<?php
namespace Tests\Feature\Accessories\Ui;
use App\Models\Accessory;
use App\Models\Category;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\Support\ProvidesDataForFullMultipleCompanySupportTesting;
use Tests\TestCase;
class CreateAccessoryWithFullMultipleCompanySupportTest extends TestCase
{
use ProvidesDataForFullMultipleCompanySupportTesting;
#[DataProvider('dataForFullMultipleCompanySupportTesting')]
public function testAdheresToFullMultipleCompaniesSupportScoping($data)
{
['actor' => $actor, 'company_attempting_to_associate' => $company, 'assertions' => $assertions] = $data();
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs($actor)
->post(route('accessories.store'), [
'redirect_option' => 'index',
'name' => 'My Cool Accessory',
'qty' => '1',
'category_id' => Category::factory()->create()->id,
'company_id' => $company->id,
]);
$accessory = Accessory::withoutGlobalScopes()->where([
'name' => 'My Cool Accessory',
])->sole();
$assertions($accessory);
}
}

View File

@@ -13,9 +13,23 @@ class AssetIndexTest extends TestCase
{
public function testAssetApiIndexReturnsExpectedAssets()
{
Asset::factory()->count(3)->create();
$this->withoutExceptionHandling();
$this->actingAsForApi(User::factory()->superuser()->create())
if ($assets = Asset::factory()->count(3)->make()) {
//dd($assets);
foreach ($assets as $asset) {
\Log::error($asset->asset_tag);
\Log::error($asset->assetstatus->name);
\Log::error($asset->assetstatus->status_type);
}
} else {
\Log::error('No created');
}
$response = $this->actingAsForApi(User::factory()->superuser()->create())
->getJson(
route('api.assets.index', [
'sort' => 'name',
@@ -29,6 +43,7 @@ class AssetIndexTest extends TestCase
'rows',
])
->assertJson(fn(AssertableJson $json) => $json->has('rows', 3)->etc());
\Log::error($response);
}
public function testAssetApiIndexReturnsDisplayUpcomingAuditsDue()

View File

@@ -560,6 +560,9 @@ class StoreAssetTest extends TestCase
$this->assertTrue($asset->assignedAssets()->find($response['payload']['id'])->is($apiAsset));
}
/**
* @link https://app.shortcut.com/grokability/story/24475
*/
public function testCompanyIdNeedsToBeInteger()
{
$this->actingAsForApi(User::factory()->createAssets()->create())

View File

@@ -0,0 +1,58 @@
<?php
namespace Tests\Feature\Assets\Api;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\Support\ProvidesDataForFullMultipleCompanySupportTesting;
use Tests\TestCase;
class StoreAssetWithFullMultipleCompanySupportTest extends TestCase
{
use ProvidesDataForFullMultipleCompanySupportTesting;
/**
* @link https://github.com/snipe/snipe-it/issues/15654
*/
#[DataProvider('dataForFullMultipleCompanySupportTesting')]
public function testAdheresToFullMultipleCompaniesSupportScoping($data)
{
['actor' => $actor, 'company_attempting_to_associate' => $company, 'assertions' => $assertions] = $data();
$this->settings->enableMultipleFullCompanySupport();
$response = $this->actingAsForApi($actor)
->postJson(route('api.assets.store'), [
'asset_tag' => 'random_string',
'company_id' => $company->id,
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->readyToDeploy()->create()->id,
]);
$asset = Asset::withoutGlobalScopes()->findOrFail($response['payload']['id']);
$assertions($asset);
}
#[DataProvider('dataForFullMultipleCompanySupportTesting')]
public function testHandlesCompanyIdBeingString($data)
{
['actor' => $actor, 'company_attempting_to_associate' => $company, 'assertions' => $assertions] = $data();
$this->settings->enableMultipleFullCompanySupport();
$response = $this->actingAsForApi($actor)
->postJson(route('api.assets.store'), [
'asset_tag' => 'random_string',
'company_id' => (string) $company->id,
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->readyToDeploy()->create()->id,
]);
$asset = Asset::withoutGlobalScopes()->findOrFail($response['payload']['id']);
$assertions($asset);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Tests\Feature\Assets\Ui;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\Support\ProvidesDataForFullMultipleCompanySupportTesting;
use Tests\TestCase;
class StoreAssetWithFullMultipleCompanySupportTest extends TestCase
{
use ProvidesDataForFullMultipleCompanySupportTesting;
#[DataProvider('dataForFullMultipleCompanySupportTesting')]
public function testAdheresToFullMultipleCompaniesSupportScoping($data)
{
['actor' => $actor, 'company_attempting_to_associate' => $company, 'assertions' => $assertions] = $data();
$this->settings->enableMultipleFullCompanySupport();
$this->actingAs($actor)
->post(route('hardware.store'), [
'asset_tags' => ['1' => '1234'],
'model_id' => AssetModel::factory()->create()->id,
'status_id' => Statuslabel::factory()->create()->id,
'company_id' => $company->id,
]);
$asset = Asset::where('asset_tag', '1234')->sole();
$assertions($asset);
}
}

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