diff --git a/.chipperci.yml b/.chipperci.yml new file mode 100644 index 0000000000..fa3b3fd920 --- /dev/null +++ b/.chipperci.yml @@ -0,0 +1,59 @@ +version: 1 + +environment: + php: 8.0 + node: 12 + +services: + - mysql: 5.7 + - dusk: + +on: + push: + branches: + - master + - develop + +pipeline: + - name: Setup + cmd: | + cp -v .env.example .env + + composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Generate Key + cmd: | + php artisan key:generate --force + + - name: Passport Keys + cmd: | + php artisan passport:keys + + - name: Run Migrations + cmd: | + # php artisan migrate --force + + - name: PHPUnit Unit Tests + cmd: | + # php artisan test --testsuite Unit + + - name: PHPUnit Feature Tests + cmd: | + # php artisan test --testsuite Feature + + - name: Browser Tests + cmd: | + cp -v .env.dusk.example .env.dusk.ci + sed -i "s@APP_ENV=.*@APP_ENV=ci@g" .env.dusk.ci + sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci + #sed -i "s@DB_HOST=.*@DB_HOST=mysql@g" .env.dusk.ci + sed -i "s@DB_HOST=.*@DB_HOST=$DB_HOST@g" .env.dusk.ci + sed -i "s@DB_USERNAME=.*@DB_USERNAME=chipperci@g" .env.dusk.ci + sed -i "s@DB_DATABASE=.*@DB_DATABASE=chipperci@g" .env.dusk.ci + sed -i "s@DB_PASSWORD=.*@DB_PASSWORD=secret@g" .env.dusk.ci + + php -S [::0]:8000 -t public 2>server.log & + sleep 2 + php artisan dusk:chrome-driver $CHROME_DRIVER + php artisan dusk --env=ci + diff --git a/.env.example b/.env.example index c523ef0c04..fbf5c4cfff 100644 --- a/.env.example +++ b/.env.example @@ -175,6 +175,15 @@ REQUIRE_SAML=false API_THROTTLE_PER_MINUTE=120 CSV_ESCAPE_FORMULAS=true +# -------------------------------------------- +# OPTIONAL: HASHING +# -------------------------------------------- +HASHING_DRIVER='bcrypt' +BCRYPT_ROUNDS=10 +ARGON_MEMORY=1024 +ARGON_THREADS=2 +ARGON_TIME=2 + # -------------------------------------------- # OPTIONAL: SCIM # -------------------------------------------- diff --git a/app/Console/Commands/SystemBackup.php b/app/Console/Commands/SystemBackup.php index b7ca526052..3b51bcc736 100644 --- a/app/Console/Commands/SystemBackup.php +++ b/app/Console/Commands/SystemBackup.php @@ -11,7 +11,7 @@ class SystemBackup extends Command * * @var string */ - protected $name = 'snipeit:backup'; + protected $signature = 'snipeit:backup {--filename=}'; /** * The console command description. @@ -37,7 +37,18 @@ class SystemBackup extends Command */ public function handle() { - // - $this->call('backup:run'); + if ($this->option('filename')) { + $filename = $this->option('filename'); + + // Make sure the filename ends in .zip + if (!ends_with($filename, '.zip')) { + $filename = $filename.'.zip'; + } + + $this->call('backup:run', ['--filename' => $filename]); + } else { + $this->call('backup:run'); + } + } } diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index 726e164ba8..645e2624b2 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -121,7 +121,6 @@ class AcceptanceController extends Controller $pdf_filename = 'accepted-eula-'.date('Y-m-d-h-i-s').'.pdf'; $sig_filename=''; - if ($request->input('asset_acceptance') == 'accepted') { /** @@ -153,12 +152,14 @@ class AcceptanceController extends Controller } } - // this is horrible switch($acceptance->checkoutable_type){ case 'App\Models\Asset': $pdf_view_route ='account.accept.accept-asset-eula'; $asset_model = AssetModel::find($item->model_id); + if (!$asset_model) { + return redirect()->back()->with('error', trans('admin/models/message.does_not_exist')); + } $display_model = $asset_model->name; $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; @@ -167,7 +168,7 @@ class AcceptanceController extends Controller $pdf_view_route ='account.accept.accept-accessory-eula'; $accessory = Accessory::find($item->id); $display_model = $accessory->name; - $assigned_to = User::find($item->assignedTo); + $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; case 'App\Models\LicenseSeat': @@ -250,11 +251,15 @@ class AcceptanceController extends Controller // This is the most horriblest switch($acceptance->checkoutable_type){ case 'App\Models\Asset': + $asset_model = AssetModel::find($item->model_id); + $display_model = $asset_model->name; $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; case 'App\Models\Accessory': - $assigned_to = User::find($item->assignedTo); + $accessory = Accessory::find($item->id); + $display_model = $accessory->name; + $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; case 'App\Models\LicenseSeat': @@ -266,6 +271,8 @@ class AcceptanceController extends Controller break; case 'App\Models\Consumable': + $consumable = Consumable::find($item->id); + $display_model = $consumable->name; $assigned_to = User::find($acceptance->assigned_to_id)->present()->fullName; break; } @@ -289,4 +296,4 @@ class AcceptanceController extends Controller return redirect()->to('account/accept')->with('success', $return_msg); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/AssetMaintenancesController.php index ab5635ec33..b2870792f9 100644 --- a/app/Http/Controllers/Api/AssetMaintenancesController.php +++ b/app/Http/Controllers/Api/AssetMaintenancesController.php @@ -36,7 +36,7 @@ class AssetMaintenancesController extends Controller { $this->authorize('view', Asset::class); - $maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'supplier', 'asset.company', 'admin'); + $maintenances = AssetMaintenance::select('asset_maintenances.*')->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'admin'); if ($request->filled('search')) { $maintenances = $maintenances->TextSearch($request->input('search')); diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index 1816fc9f63..ced1f013c6 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -573,6 +573,7 @@ class AssetsController extends Controller // Update custom fields in the database. // Validation for these fields is handled through the AssetRequest form request $model = AssetModel::find($request->get('model_id')); + if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { diff --git a/app/Http/Controllers/Api/CustomFieldsetsController.php b/app/Http/Controllers/Api/CustomFieldsetsController.php index 27da7733cd..df0fc940cf 100644 --- a/app/Http/Controllers/Api/CustomFieldsetsController.php +++ b/app/Http/Controllers/Api/CustomFieldsetsController.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Controller; use App\Http\Transformers\CustomFieldsetsTransformer; use App\Http\Transformers\CustomFieldsTransformer; use App\Models\CustomFieldset; +use App\Models\CustomField; use Illuminate\Http\Request; use Redirect; use View; @@ -94,6 +95,18 @@ class CustomFieldsetsController extends Controller $fieldset->fill($request->all()); if ($fieldset->save()) { + // Sync fieldset with auto_add_to_fieldsets + $fields = CustomField::select('id')->where('auto_add_to_fieldsets', '=', '1')->get(); + + if ($fields->count() > 0) { + + foreach ($fields as $field) { + $field_ids[] = $field->id; + } + + $fieldset->fields()->sync($field_ids); + } + return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.create.success'))); } diff --git a/app/Http/Controllers/Api/ImportController.php b/app/Http/Controllers/Api/ImportController.php index 6f5fc05ff3..2d7e2f7d78 100644 --- a/app/Http/Controllers/Api/ImportController.php +++ b/app/Http/Controllers/Api/ImportController.php @@ -160,7 +160,7 @@ class ImportController extends Controller // Run a backup immediately before processing if ($request->get('run-backup')) { \Log::debug('Backup manually requested via importer'); - Artisan::call('backup:run'); + Artisan::call('snipeit:backup', ['--filename' => 'pre-import-backup-'.date('Y-m-d-H:i:s')]); } else { \Log::debug('NO BACKUP requested via importer'); } @@ -193,6 +193,9 @@ class ImportController extends Controller case 'user': $redirectTo = 'users.index'; break; + case 'location': + $redirectTo = 'locations.index'; + break; } if ($errors) { //Failure diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php index f3183238bf..9ff0a0c202 100644 --- a/app/Http/Controllers/Api/ManufacturersController.php +++ b/app/Http/Controllers/Api/ManufacturersController.php @@ -23,10 +23,10 @@ class ManufacturersController extends Controller public function index(Request $request) { $this->authorize('view', Manufacturer::class); - $allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count']; + $allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'warranty_lookup_url', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count']; $manufacturers = Manufacturer::select( - ['id', 'name', 'url', 'support_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'deleted_at'] + ['id', 'name', 'url', 'support_url', 'warranty_lookup_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'deleted_at'] )->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count'); if ($request->input('deleted') == 'true') { @@ -49,6 +49,10 @@ class ManufacturersController extends Controller $manufacturers->where('support_url', '=', $request->input('support_url')); } + if ($request->filled('warranty_lookup_url')) { + $manufacturers->where('warranty_lookup_url', '=', $request->input('warranty_lookup_url')); + } + if ($request->filled('support_phone')) { $manufacturers->where('support_phone', '=', $request->input('support_phone')); } diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php index e29cbaa3fc..bbc3790d63 100644 --- a/app/Http/Controllers/CustomFieldsController.php +++ b/app/Http/Controllers/CustomFieldsController.php @@ -7,6 +7,7 @@ use App\Http\Requests\CustomFieldRequest; use App\Models\CustomField; use App\Models\CustomFieldset; use Illuminate\Support\Facades\Auth; +use Illuminate\Http\Request; use Redirect; /** @@ -45,7 +46,7 @@ class CustomFieldsController extends Controller * @see CustomFieldsController::storeField() * @author [A. Gianotto] [] * @since [v5.1.5] - * @return Redirect + * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function show() @@ -63,14 +64,17 @@ class CustomFieldsController extends Controller * @return \Illuminate\Support\Facades\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create(Request $request) { $this->authorize('create', CustomField::class); + $fieldsets = CustomFieldset::get(); return view('custom_fields.fields.edit', [ 'predefinedFormats' => Helper::predefined_formats(), - 'customFormat' => '', - ])->with('field', new CustomField()); + 'customFormat' => '', + 'fieldsets' => $fieldsets, + 'field' => new CustomField(), + ]); } /** @@ -79,7 +83,7 @@ class CustomFieldsController extends Controller * @see CustomFieldsController::createField() * @author [Brady Wetherington] [] * @since [v1.8] - * @return Redirect + * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function store(CustomFieldRequest $request) @@ -104,6 +108,7 @@ class CustomFieldsController extends Controller "show_in_email" => $show_in_email, "is_unique" => $request->get("is_unique", 0), "display_in_user_view" => $display_in_user_view, + "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0), "user_id" => Auth::id() ]); @@ -115,10 +120,20 @@ class CustomFieldsController extends Controller } if ($field->save()) { + + // Sync fields with fieldsets + $fieldset_array = $request->input('associate_fieldsets'); + if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) { + $field->fieldset()->sync(array_keys($fieldset_array)); + } else { + $field->fieldset()->sync([]); + } + + return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.create.success')); } - return redirect()->back()->withInput() + return redirect()->back()->with('selected_fieldsets', $request->input('associate_fieldsets'))->withInput() ->with('error', trans('admin/custom_fields/message.field.create.error')); } @@ -128,7 +143,7 @@ class CustomFieldsController extends Controller * * @author [A. Gianotto] [] * @since [v3.0] - * @return Redirect + * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function deleteFieldFromFieldset($field_id, $fieldset_id) @@ -147,8 +162,7 @@ class CustomFieldsController extends Controller ->with('success', trans('admin/custom_fields/message.field.delete.success')); } else { return redirect()->back()->withErrors(['message' => "Field is in use and cannot be deleted."]); - } - + } } return redirect()->back()->withErrors(['message' => "Error deleting field from fieldset"]); @@ -161,7 +175,7 @@ class CustomFieldsController extends Controller * * @author [Brady Wetherington] [] * @since [v1.8] - * @return Redirect + * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function destroy($field_id) @@ -190,12 +204,12 @@ class CustomFieldsController extends Controller * @return \Illuminate\Support\Facades\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($id) + public function edit(Request $request, $id) { if ($field = CustomField::find($id)) { $this->authorize('update', $field); - + $fieldsets = CustomFieldset::get(); $customFormat = ''; if ((stripos($field->format, 'regex') === 0) && ($field->format !== CustomField::PREDEFINED_FORMATS['MAC'])) { $customFormat = $field->format; @@ -204,6 +218,7 @@ class CustomFieldsController extends Controller return view('custom_fields.fields.edit', [ 'field' => $field, 'customFormat' => $customFormat, + 'fieldsets' => $fieldsets, 'predefinedFormats' => Helper::predefined_formats(), ]); } @@ -222,7 +237,7 @@ class CustomFieldsController extends Controller * @author [A. Gianotto] [] * @param int $id * @since [v4.0] - * @return Redirect + * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function update(CustomFieldRequest $request, $id) @@ -249,6 +264,7 @@ class CustomFieldsController extends Controller $field->show_in_email = $show_in_email; $field->is_unique = $request->get("is_unique", 0); $field->display_in_user_view = $display_in_user_view; + $field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0); if ($request->get('format') == 'CUSTOM REGEX') { $field->format = e($request->get('custom_format')); @@ -256,11 +272,21 @@ class CustomFieldsController extends Controller $field->format = e($request->get('format')); } - if($field->element == 'checkbox' || $field->element == 'radio'){ + if ($field->element == 'checkbox' || $field->element == 'radio'){ $field->format = 'ANY'; } if ($field->save()) { + + + // Sync fields with fieldsets + $fieldset_array = $request->input('associate_fieldsets'); + if ($request->has('associate_fieldsets') && (is_array($fieldset_array))) { + $field->fieldset()->sync(array_keys($fieldset_array)); + } else { + $field->fieldset()->sync([]); + } + return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/message.field.update.success')); } diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php index 8c14502285..dbee97b776 100644 --- a/app/Http/Controllers/CustomFieldsetsController.php +++ b/app/Http/Controllers/CustomFieldsetsController.php @@ -93,16 +93,27 @@ class CustomFieldsetsController extends Controller { $this->authorize('create', CustomField::class); - $cfset = new CustomFieldset([ + $fieldset = new CustomFieldset([ 'name' => e($request->get('name')), 'user_id' => Auth::user()->id, ]); - $validator = Validator::make($request->all(), $cfset->rules); - if ($validator->passes()) { - $cfset->save(); + $validator = Validator::make($request->all(), $fieldset->rules); - return redirect()->route('fieldsets.show', [$cfset->id]) + if ($validator->passes()) { + $fieldset->save(); + + // Sync fieldset with auto_add_to_fieldsets + $fields = CustomField::select('id')->where('auto_add_to_fieldsets', '=', '1')->get(); + if ($fields->count() > 0) { + foreach ($fields as $field) { + $field_ids[] = $field->id; + } + + $fieldset->fields()->sync($field_ids); + } + + return redirect()->route('fieldsets.show', [$fieldset->id]) ->with('success', trans('admin/custom_fields/message.fieldset.create.success')); } diff --git a/app/Http/Controllers/GoogleAuthController.php b/app/Http/Controllers/GoogleAuthController.php new file mode 100644 index 0000000000..84e428af3c --- /dev/null +++ b/app/Http/Controllers/GoogleAuthController.php @@ -0,0 +1,73 @@ + config('app.url').'/google/callback']); + config(['services.google.client_id' => $setting->google_client_id]); + config(['services.google.client_secret' => $setting->google_client_secret]); + } + + public function redirectToGoogle() + { + return Socialite::driver('google')->redirect(); + } + + public function handleGoogleCallback() + { + try { + $socialUser = Socialite::driver('google')->user(); + \Log::debug('Google user found'); + } catch (InvalidStateException $exception) { + \Log::debug('Google user NOT found'); + return redirect()->route('login') + ->withErrors( + [ + 'username' => [ + trans('auth/general.google_login_failed') + ], + ] + ); + } + + + $user = User::where('username', $socialUser->getEmail())->first(); + + + if ($user) { + + $user->update([ + 'avatar' => $socialUser->avatar, + ]); + + Auth::login($user, true); + return redirect()->route('home'); + } + + return redirect()->route('login') + ->withErrors( + [ + 'username' => [ + trans('admin/users/message.user_not_found'), + ], + ] + ); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/ManufacturersController.php b/app/Http/Controllers/ManufacturersController.php index 1f7d2f2c07..e98644f46f 100755 --- a/app/Http/Controllers/ManufacturersController.php +++ b/app/Http/Controllers/ManufacturersController.php @@ -68,6 +68,7 @@ class ManufacturersController extends Controller $manufacturer->user_id = Auth::id(); $manufacturer->url = $request->input('url'); $manufacturer->support_url = $request->input('support_url'); + $manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url'); $manufacturer->support_phone = $request->input('support_phone'); $manufacturer->support_email = $request->input('support_email'); $manufacturer = $request->handleImages($manufacturer); @@ -123,10 +124,11 @@ class ManufacturersController extends Controller return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist')); } - // Save the data + // Save the data $manufacturer->name = $request->input('name'); $manufacturer->url = $request->input('url'); $manufacturer->support_url = $request->input('support_url'); + $manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url'); $manufacturer->support_phone = $request->input('support_phone'); $manufacturer->support_email = $request->input('support_email'); diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 9764df923e..80fe24255c 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -641,6 +641,9 @@ class ReportsController extends Controller if (($request->filled('created_start')) && ($request->filled('created_end'))) { $assets->whereBetween('assets.created_at', [$request->input('created_start'), $request->input('created_end')]); } + if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) { + $assets->whereBetween('assets.last_checkout', [$request->input('checkout_date_start'), $request->input('checkout_date_end')]); + } if (($request->filled('expected_checkin_start')) && ($request->filled('expected_checkin_end'))) { $assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]); @@ -898,12 +901,8 @@ class ReportsController extends Controller public function getAssetMaintenancesReport() { $this->authorize('reports.view'); - // Grab all the improvements - $assetMaintenances = AssetMaintenance::with('asset', 'supplier', 'asset.company') - ->orderBy('created_at', 'DESC') - ->get(); - return view('reports/asset_maintenances', compact('assetMaintenances')); + return view('reports.asset_maintenances'); } /** diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index c65dbc7d27..fbb7f4e4ce 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -1039,6 +1039,48 @@ class SettingsController extends Controller return $pdf_branding; } + + /** + * Show Google login settings form + * + * @author [A. Gianotto] [] + * @since [v6.1.1] + * @return View + */ + public function getGoogleLoginSettings() + { + $setting = Setting::getSettings(); + return view('settings.google', compact('setting')); + } + + /** + * ShSaveow Google login settings form + * + * @author [A. Gianotto] [] + * @since [v6.1.1] + * @return View + */ + public function postGoogleLoginSettings(Request $request) + { + if (!config('app.lock_passwords')) { + $setting = Setting::getSettings(); + + $setting->google_login = $request->input('google_login', 0); + $setting->google_client_id = $request->input('google_client_id'); + $setting->google_client_secret = $request->input('google_client_secret'); + + if ($setting->save()) { + return redirect()->route('settings.index') + ->with('success', trans('admin/settings/message.update.success')); + } + + return redirect()->back()->withInput()->withErrors($setting->getErrors()); + } + + return redirect()->back()->with('error', trans('general.feature_disabled')); + } + + /** * Show the listing of backups. * @@ -1094,7 +1136,7 @@ class SettingsController extends Controller public function postBackups() { if (! config('app.lock_passwords')) { - Artisan::call('backup:run'); + Artisan::call('snipeit:backup', ['--filename' => 'manual-backup-'.date('Y-m-d-H:i:s')]); $output = Artisan::output(); // Backup completed diff --git a/app/Http/Livewire/Importer.php b/app/Http/Livewire/Importer.php index 784d3b2982..7a37b00d18 100644 --- a/app/Http/Livewire/Importer.php +++ b/app/Http/Livewire/Importer.php @@ -67,7 +67,6 @@ class Importer extends Component 'location' => 'Location', 'maintained' => 'Maintained', 'manufacturer' => 'Manufacturer', - 'notes' => 'Notes', 'order_number' => 'Order Number', 'purchase_cost' => 'Purchase Cost', 'purchase_date' => 'Purchase Date', @@ -81,11 +80,14 @@ class Importer extends Component static $accessories = [ 'model_number' => 'Model Number', + 'notes' => 'Notes', ]; static $assets = [ 'asset_tag' => 'Asset Tag', 'asset_model' => 'Model Name', + 'asset_notes' => 'Asset Notes', + 'model_notes' => 'Model Notes', 'byod' => 'BYOD', 'checkout_class' => 'Checkout Type', 'checkout_location' => 'Checkout Location', @@ -99,6 +101,7 @@ class Importer extends Component static $consumables = [ 'item_no' => "Item Number", 'model_number' => "Model Number", + 'notes' => 'Notes', 'min_amt' => "Minimum Quantity", ]; @@ -111,13 +114,15 @@ class Importer extends Component 'purchase_order' => 'Purchase Order', 'reassignable' => 'Reassignable', 'seats' => 'Seats', + 'notes' => 'Notes', ]; static $users = [ 'employee_num' => 'Employee Number', 'first_name' => 'First Name', - 'jobtitle' => 'Job Title', 'last_name' => 'Last Name', + 'notes' => 'Notes', + 'jobtitle' => 'Job Title', 'phone_number' => 'Phone Number', 'manager_first_name' => 'Manager First Name', 'manager_last_name' => 'Manager Last Name', @@ -126,7 +131,25 @@ class Importer extends Component 'city' => 'City', 'state' => 'State', 'country' => 'Country', - 'vip' => 'VIP' + 'zip' => 'Zip', + 'vip' => 'VIP', + 'remote' => 'Remote', + ]; + + static $locations = [ + 'name' => 'Name', + 'address' => 'Address', + 'address2' => 'Address 2', + 'city' => 'City', + 'state' => 'State', + 'country' => 'Country', + 'zip' => 'Zip', + 'currency' => 'Currency', + 'ldap_ou' => 'LDAP OU', + 'manager_username' => 'Manager Username', + 'manager' => 'Manager', + 'parent_location' => 'Parent Location', + 'notes' => 'Notes', ]; //array of "real fieldnames" to a list of aliases for that field @@ -150,6 +173,11 @@ class Importer extends Component 'QTY', 'Quantity' ], + 'zip' => + [ + 'Postal Code', + 'Post Code' + ], 'min_amt' => [ 'Min Amount', @@ -159,6 +187,31 @@ class Importer extends Component [ 'Next Audit', ], + 'address2' => + [ + 'Address 2', + 'Address2', + ], + 'ldap_ou' => + [ + 'LDAP OU', + 'OU', + ], + 'parent_location' => + [ + 'Parent', + 'Parent Location', + ], + 'manager' => + [ + 'Managed By', + 'Manager Name', + 'Manager Full Name', + ], + 'manager_username' => + [ + 'Manager Username', + ], ]; @@ -181,6 +234,9 @@ class Importer extends Component case 'user': $results = self::$general + self::$users; break; + case 'location': + $results = self::$general + self::$locations; + break; default: $results = self::$general; } @@ -252,7 +308,6 @@ class Importer extends Component $this->authorize('import'); $this->progress = -1; // '-1' means 'don't show the progressbar' $this->progress_bar_class = 'progress-bar-warning'; - \Log::debug("Hey, we are calling MOUNT (in the importer-file) !!!!!!!!"); //fcuk $this->importTypes = [ 'asset' => trans('general.assets'), 'accessory' => trans('general.accessories'), @@ -260,6 +315,7 @@ class Importer extends Component 'component' => trans('general.components'), 'license' => trans('general.licenses'), 'user' => trans('general.users'), + 'location' => trans('general.locations'), ]; $this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean @@ -273,8 +329,7 @@ class Importer extends Component public function selectFile($id) { - \Log::debug("TOGGLE EVENT FIRED!"); - \Log::debug("The ID we are trying to find is AS FOLLOWS: ".$id); + $this->activeFile = Import::find($id); $this->field_map = null; foreach($this->activeFile->header_row as $element) { @@ -284,11 +339,9 @@ class Importer extends Component $this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings } } - //$this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : []; // this is wrong $this->file_id = $id; $this->import_errors = null; $this->statusText = null; - \Log::debug("The import type we are about to try and load up is gonna be this: ".$this->activeFile->import_type); } diff --git a/app/Http/Requests/CustomFieldRequest.php b/app/Http/Requests/CustomFieldRequest.php index 7c6ba1e97e..0c2ec0ae60 100644 --- a/app/Http/Requests/CustomFieldRequest.php +++ b/app/Http/Requests/CustomFieldRequest.php @@ -26,6 +26,8 @@ class CustomFieldRequest extends FormRequest { $rules = []; + $rules['associate_fieldsets.*'] = 'nullable|integer|exists:custom_fieldsets,id'; + switch ($this->method()) { // Brand new @@ -54,4 +56,11 @@ class CustomFieldRequest extends FormRequest return $rules; } + + public function messages() + { + return [ + 'associate_fieldsets.*.exists' => trans('admin/custom_fields/message/does_not_exist'), + ]; + } } diff --git a/app/Http/Transformers/CategoriesTransformer.php b/app/Http/Transformers/CategoriesTransformer.php index faf05f7f4b..d5e1ceb51b 100644 --- a/app/Http/Transformers/CategoriesTransformer.php +++ b/app/Http/Transformers/CategoriesTransformer.php @@ -38,6 +38,9 @@ class CategoriesTransformer case 'component': $category->item_count = $category->components_count; break; + case 'license': + $category->item_count = $category->licenses_count; + break; default: $category->item_count = 0; } diff --git a/app/Http/Transformers/CustomFieldsTransformer.php b/app/Http/Transformers/CustomFieldsTransformer.php index 9ada5183ab..db467be0bd 100644 --- a/app/Http/Transformers/CustomFieldsTransformer.php +++ b/app/Http/Transformers/CustomFieldsTransformer.php @@ -48,6 +48,7 @@ class CustomFieldsTransformer 'type' => e($field->element), 'required' => (($field->pivot) && ($field->pivot->required=='1')) ? true : false, 'display_in_user_view' => ($field->display_in_user_view =='1') ? true : false, + 'auto_add_to_fieldsets' => ($field->auto_add_to_fieldsets == '1') ? true : false, 'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/ManufacturersTransformer.php b/app/Http/Transformers/ManufacturersTransformer.php index bbcbda12b4..9c84fd50fe 100644 --- a/app/Http/Transformers/ManufacturersTransformer.php +++ b/app/Http/Transformers/ManufacturersTransformer.php @@ -29,6 +29,7 @@ class ManufacturersTransformer 'url' => e($manufacturer->url), 'image' => ($manufacturer->image) ? Storage::disk('public')->url('manufacturers/'.e($manufacturer->image)) : null, 'support_url' => e($manufacturer->support_url), + 'warranty_lookup_url' => e($manufacturer->warranty_lookup_url), 'support_phone' => e($manufacturer->support_phone), 'support_email' => e($manufacturer->support_email), 'assets_count' => (int) $manufacturer->assets_count, diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index 1ee90b399c..dfc8084666 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -65,19 +65,22 @@ abstract class Importer 'email' => 'email', 'username' => 'username', 'address' => 'address', + 'address2' => 'address2', 'city' => 'city', 'state' => 'state', 'country' => 'country', + 'zip' => 'zip', 'jobtitle' => 'job title', 'employee_num' => 'employee number', 'phone_number' => 'phone number', 'first_name' => 'first name', 'last_name' => 'last name', 'department' => 'department', - 'manager_first_name' => 'manager first name', - 'manager_last_name' => 'manager last name', + 'manager_name' => 'manager full name', + 'manager_username' => 'manager username', 'min_amt' => 'minimum quantity', 'remote' => 'remote', + 'vip' => 'vip', ]; /** * Map of item fields->csv names @@ -119,7 +122,7 @@ abstract class Importer } else { $this->csv = Reader::createFromString($file); } - $this->tempPassword = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20); + $this->tempPassword = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40); } // Cached Values for import lookups @@ -198,11 +201,11 @@ abstract class Importer $val = $default; $key = $this->lookupCustomKey($key); - $this->log("Custom Key: ${key}"); + // $this->log("Custom Key: ${key}"); if (array_key_exists($key, $array)) { $val = Encoding::toUTF8(trim($array[$key])); } - $this->log("${key}: ${val}"); + //$this->log("${key}: ${val}"); return $val; } @@ -280,8 +283,9 @@ abstract class Importer * @return User Model w/ matching name * @internal param array $user_array User details parsed from csv */ - protected function createOrFetchUser($row) + protected function createOrFetchUser($row, $type = 'user') { + $user_array = [ 'full_name' => $this->findCsvMatch($row, 'full_name'), 'email' => $this->findCsvMatch($row, 'email'), @@ -292,31 +296,36 @@ abstract class Importer 'remote' => $this->fetchHumanBoolean(($this->findCsvMatch($row, 'remote'))), ]; - // Maybe we're lucky and the user already exists. - if ($user = User::where('username', $user_array['username'])->first()) { - $this->log('User '.$user_array['username'].' already exists'); - return $user; + if ($type == 'manager') { + $user_array['full_name'] = $this->findCsvMatch($row, 'manager'); + $user_array['username'] = $this->findCsvMatch($row, 'manager_username'); } - // If the full name is empty, bail out--we need this to extract first name (at the very least) - if (empty($user_array['full_name'])) { - $this->log('Insufficient user data provided (Full name is required)- skipping user creation, just adding asset'); + // Maybe we're lucky and the username was passed and it already exists. + if (!empty($user_array['username'])) { + if ($user = User::where('username', $user_array['username'])->first()) { + $this->log('User '.$user_array['username'].' already exists'); + return $user; + } + } + + // If the full name and username is empty, bail out--we need this to extract first name (at the very least) + if ((empty($user_array['username'])) && (empty($user_array['full_name']))) { + $this->log('Insufficient user data provided (Full name or username is required) - skipping user creation.'); + \Log::debug(print_r($user_array, true)); + \Log::debug(print_r($row, true)); return false; } - // Is the user actually an ID? - if ($user = $this->findUserByNumber($user_array['full_name'])) { - return $user; - } - $this->log('User does not appear to be an id with number: '.$user_array['full_name'].'. Continuing through our processes'); // Populate email if it does not exist. if (empty($user_array['email'])) { $user_array['email'] = User::generateEmailFromFullName($user_array['full_name']); } + // Get some fields for first name and last name based off of full name $user_formatted_array = User::generateFormattedNameFromFullName($user_array['full_name'], Setting::getSettings()->username_format); $user_array['first_name'] = $user_formatted_array['first_name']; $user_array['last_name'] = $user_formatted_array['last_name']; @@ -326,14 +335,12 @@ abstract class Importer if ($this->usernameFormat == 'email') { $user_array['username'] = $user_array['email']; } - } - // Does this ever actually fire?? - // Check for a matching user after trying to guess username. - if ($user = User::where('username', $user_array['username'])->first()) { - $this->log('User '.$user_array['username'].' already exists'); - - return $user; + // Check for a matching username one more time after trying to guess username. + if ($user = User::where('username', $user_array['username'])->first()) { + $this->log('User '.$user_array['username'].' already exists'); + return $user; + } } // If at this point we have not found a username or first name, bail out in shame. @@ -341,7 +348,7 @@ abstract class Importer return false; } - // No Luck, let's create one. + // No luck finding a user on username or first name, let's create one. $user = new User; $user->first_name = $user_array['first_name']; $user->last_name = $user_array['last_name']; @@ -356,9 +363,9 @@ abstract class Importer if ($user->save()) { $this->log('User '.$user_array['username'].' created'); - return $user; } + $this->logError($user, 'User "'.$user_array['username'].'" was not able to be created.'); return false; diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php index 7848ec6b85..f989457dc6 100644 --- a/app/Importer/ItemImporter.php +++ b/app/Importer/ItemImporter.php @@ -60,8 +60,8 @@ class ItemImporter extends Importer $this->item['department_id'] = $this->createOrFetchDepartment($item_department); } - $item_manager_first_name = $this->findCsvMatch($row, 'manage_first_name'); - $item_manager_last_name = $this->findCsvMatch($row, 'manage_last_name'); + $item_manager_first_name = $this->findCsvMatch($row, 'manager_first_name'); + $item_manager_last_name = $this->findCsvMatch($row, 'manager_last_name'); if ($this->shouldUpdateField($item_manager_first_name)) { $this->item['manager_id'] = $this->fetchManager($item_manager_first_name, $item_manager_last_name); @@ -112,6 +112,10 @@ class ItemImporter extends Importer return $this->createOrFetchUser($row); } + if (get_class($this) != LocationImporter::class) { + return; + } + if (strtolower($this->item['checkout_class']) === 'location' && $this->findCsvMatch($row, 'checkout_location') != null ) { return Location::findOrFail($this->createOrFetchLocation($this->findCsvMatch($row, 'checkout_location'))); } diff --git a/app/Importer/LocationImporter.php b/app/Importer/LocationImporter.php new file mode 100644 index 0000000000..25140abe00 --- /dev/null +++ b/app/Importer/LocationImporter.php @@ -0,0 +1,102 @@ +createLocationIfNotExists($row); + } + + /** + * Create a location if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createLocationIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createLocationIfNotExists(array $row) + { + + $editingLocation = false; + $location = Location::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($location) { + if (! $this->updating) { + $this->log('A matching Location '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Location'); + $editingLocation = true; + } else { + $this->log('No Matching Location, Create a new one'); + $location = new Location; + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = $this->findCsvMatch($row, 'name'); + $this->item['address'] = $this->findCsvMatch($row, 'address'); + $this->item['address2'] = $this->findCsvMatch($row, 'address2'); + $this->item['city'] = $this->findCsvMatch($row, 'city'); + $this->item['state'] = $this->findCsvMatch($row, 'state'); + $this->item['country'] = $this->findCsvMatch($row, 'country'); + $this->item['zip'] = $this->findCsvMatch($row, 'zip'); + $this->item['currency'] = $this->findCsvMatch($row, 'currency'); + $this->item['ldap_ou'] = $this->findCsvMatch($row, 'ldap_ou'); + $this->item['manager'] = $this->findCsvMatch($row, 'manager'); + $this->item['manager_username'] = $this->findCsvMatch($row, 'manager_username'); + $this->item['user_id'] = \Auth::user()->id; + + if ($this->findCsvMatch($row, 'parent_location')) { + $this->item['parent_id'] = $this->createOrFetchLocation($this->findCsvMatch($row, 'parent_location')); + } + + if (!empty($this->item['manager'])) { + if ($manager = $this->createOrFetchUser($row, 'manager')) { + $this->item['manager_id'] = $manager->id; + } + } + + \Log::debug('Item array is: '); + \Log::debug(print_r($this->item, true)); + + + if ($editingLocation) { + \Log::debug('Updating existing location'); + $location->update($this->sanitizeItemForUpdating($location)); + } else { + \Log::debug('Creating location'); + $location->fill($this->sanitizeItemForStoring($location)); + } + + if ($location->save()) { + $this->log('Location '.$location->name.' created or updated from CSV import'); + return $location; + + } else { + \Log::debug($location->getErrors()); + return $location->errors; + } + + + } +} \ No newline at end of file diff --git a/app/Models/Asset.php b/app/Models/Asset.php index a5211e8001..ac431253bc 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -90,7 +90,7 @@ class Asset extends Depreciable protected $rules = [ 'name' => 'max:255|nullable', - 'model_id' => 'required|integer|exists:models,id', + 'model_id' => 'required|integer|exists:models,id,deleted_at,NULL', 'status_id' => 'required|integer|exists:status_labels,id', 'company_id' => 'integer|nullable', 'warranty_months' => 'numeric|nullable|digits_between:0,240', @@ -909,7 +909,13 @@ class Asset extends Depreciable return false; } - + public function getComponentCost(){ + $cost = 0; + foreach($this->components as $component) { + $cost += $component->pivot->assigned_qty*$component->purchase_cost; + } + return $cost; + } /** * ----------------------------------------------- diff --git a/app/Models/CheckoutAcceptance.php b/app/Models/CheckoutAcceptance.php index c3c34b0571..4a4360c40a 100644 --- a/app/Models/CheckoutAcceptance.php +++ b/app/Models/CheckoutAcceptance.php @@ -3,13 +3,14 @@ namespace App\Models; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Notifications\Notifiable; class CheckoutAcceptance extends Model { - use SoftDeletes, Notifiable; + use HasFactory, SoftDeletes, Notifiable; protected $casts = [ 'accepted_at' => 'datetime', diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php index fcab5b25ff..b39650e74b 100644 --- a/app/Models/CustomField.php +++ b/app/Models/CustomField.php @@ -52,6 +52,7 @@ class CustomField extends Model 'name' => 'required|unique:custom_fields', 'element' => 'required|in:text,listbox,textarea,checkbox,radio', 'field_encrypted' => 'nullable|boolean', + 'auto_add_to_fieldsets' => 'boolean', ]; /** @@ -69,6 +70,8 @@ class CustomField extends Model 'show_in_email', 'is_unique', 'display_in_user_view', + 'auto_add_to_fieldsets', + ]; /** diff --git a/app/Models/Location.php b/app/Models/Location.php index 8cea9bda12..2f4e78dcc7 100755 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -26,11 +26,12 @@ class Location extends SnipeModel protected $table = 'locations'; protected $rules = [ 'name' => 'required|min:2|max:255|unique_undeleted', - 'city' => 'min:2|max:255|nullable', - 'country' => 'min:2|max:255|nullable', - 'address' => 'max:80|nullable', - 'address2' => 'max:80|nullable', - 'zip' => 'min:3|max:10|nullable', + 'address' => 'max:191|nullable', + 'address2' => 'max:191|nullable', + 'city' => 'max:191|nullable', + 'state' => 'min:2|max:191|nullable', + 'country' => 'min:2|max:191|nullable', + 'zip' => 'max:10|nullable', 'manager_id' => 'exists:users,id|nullable', 'parent_id' => 'non_circular:locations,id', ]; diff --git a/app/Models/Manufacturer.php b/app/Models/Manufacturer.php index 5f01c3c273..da4f26b02f 100755 --- a/app/Models/Manufacturer.php +++ b/app/Models/Manufacturer.php @@ -23,8 +23,9 @@ class Manufacturer extends SnipeModel protected $rules = [ 'name' => 'required|min:2|max:255|unique:manufacturers,name,NULL,id,deleted_at,NULL', 'url' => 'url|nullable', - 'support_url' => 'url|nullable', 'support_email' => 'email|nullable', + 'support_url' => 'nullable|url', + 'warranty_lookup_url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://' ]; protected $hidden = ['user_id']; @@ -51,6 +52,7 @@ class Manufacturer extends SnipeModel 'support_phone', 'support_url', 'url', + 'warranty_lookup_url', ]; use Searchable; diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 61be790e00..6c95d6b01e 100755 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -76,6 +76,7 @@ class Setting extends Model 'audit_interval' => 'numeric|nullable', 'custom_forgot_pass_url' => 'url|nullable', 'privacy_policy_link' => 'nullable|url', + 'google_client_id' => 'nullable|ends_with:apps.googleusercontent.com' ]; protected $fillable = [ @@ -86,6 +87,9 @@ class Setting extends Model 'webhook_endpoint', 'webhook_channel', 'webhook_botname', + 'google_login', + 'google_client_id', + 'google_client_secret', ]; /** diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php index fa00fbad2a..e198d10c10 100755 --- a/app/Models/Supplier.php +++ b/app/Models/Supplier.php @@ -16,17 +16,17 @@ class Supplier extends SnipeModel protected $table = 'suppliers'; protected $rules = [ - 'name' => 'required|min:1|max:255|unique_undeleted', - 'address' => 'max:250|nullable', - 'address2' => 'max:250|nullable', - 'city' => 'max:255|nullable', - 'state' => 'max:32|nullable', - 'country' => 'max:3|nullable', + 'name' => 'required|min:1|max:255|unique_undeleted', 'fax' => 'min:7|max:35|nullable', 'phone' => 'min:7|max:35|nullable', 'contact' => 'max:100|nullable', 'notes' => 'max:191|nullable', // Default string length is 191 characters.. 'email' => 'email|max:150|nullable', + 'address' => 'max:250|nullable', + 'address2' => 'max:250|nullable', + 'city' => 'max:191|nullable', + 'state' => 'min:2|max:191|nullable', + 'country' => 'min:2|max:191|nullable', 'zip' => 'max:10|nullable', 'url' => 'sometimes|nullable|string|max:250', ]; diff --git a/app/Models/User.php b/app/Models/User.php index 36e1c8ac47..1e63ebad71 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -98,6 +98,11 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'start_date' => 'nullable|date_format:Y-m-d', 'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date', 'autoassign_licenses' => 'boolean', + 'address' => 'max:191|nullable', + 'city' => 'max:191|nullable', + 'state' => 'min:2|max:191|nullable', + 'country' => 'min:2|max:191|nullable', + 'zip' => 'max:10|nullable', ]; /** diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php index 4be0f56010..ce476d082c 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -534,6 +534,18 @@ class AssetPresenter extends Presenter return false; } + /** + * Used to take user created warranty URL and dynamically fill in the needed values per asset + * @return string + */ + public function dynamicWarrantyUrl() + { + $warranty_lookup_url = $this->model->model->manufacturer->warranty_lookup_url; + $url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale,$warranty_lookup_url)); + $url = (str_replace('{SERIAL}',$this->model->serial,$url)); + return $url; + } + /** * Url to view this item. * @return string diff --git a/app/Presenters/LicensePresenter.php b/app/Presenters/LicensePresenter.php index 5a44bf52ab..f2d54549e1 100644 --- a/app/Presenters/LicensePresenter.php +++ b/app/Presenters/LicensePresenter.php @@ -151,6 +151,21 @@ class LicensePresenter extends Presenter 'visible' => false, 'title' => trans('general.order_number'), ], [ + 'field' => 'created_at', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.created_at'), + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.updated_at'), + 'formatter' => 'dateDisplayFormatter', + ], + [ 'field' => 'notes', 'searchable' => true, 'sortable' => true, diff --git a/app/Presenters/ManufacturerPresenter.php b/app/Presenters/ManufacturerPresenter.php index 5b44a76933..f5c15f1fe5 100644 --- a/app/Presenters/ManufacturerPresenter.php +++ b/app/Presenters/ManufacturerPresenter.php @@ -47,7 +47,7 @@ class ManufacturerPresenter extends Presenter 'switchable' => true, 'title' => trans('admin/manufacturers/table.url'), 'visible' => true, - 'formatter' => 'linkFormatter', + 'formatter' => 'externalLinkFormatter', ], [ 'field' => 'support_url', @@ -56,7 +56,7 @@ class ManufacturerPresenter extends Presenter 'switchable' => true, 'title' => trans('admin/manufacturers/table.support_url'), 'visible' => true, - 'formatter' => 'linkFormatter', + 'formatter' => 'externalLinkFormatter', ], [ @@ -78,6 +78,15 @@ class ManufacturerPresenter extends Presenter 'visible' => true, 'formatter' => 'emailFormatter', ], + [ + 'field' => 'warranty_lookup_url', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/manufacturers/table.warranty_lookup_url'), + 'visible' => false, + 'formatter' => 'externalLinkFormatter', + ], [ 'field' => 'assets_count', diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index fbb41a4d51..080a2d10e9 100644 --- a/app/Presenters/UserPresenter.php +++ b/app/Presenters/UserPresenter.php @@ -7,6 +7,7 @@ use App\Models\Setting; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; /** * Class UserPresenter @@ -399,17 +400,25 @@ class UserPresenter extends Presenter public function gravatar() { if ($this->avatar) { + + // Check if it's a google avatar or some external avatar + if (Str::startsWith($this->avatar, ['http://', 'https://'])) { + return $this->avatar; + } + + // Otherwise assume it's an uploaded image return Storage::disk('public')->url('avatars/'.e($this->avatar)); } if (Setting::getSettings()->load_remote == '1') { if ($this->model->gravatar != '') { + $gravatar = md5(strtolower(trim($this->model->gravatar))); - return '//gravatar.com/avatar/'.$gravatar; - } elseif ($this->email != '') { - $gravatar = md5(strtolower(trim($this->email))); + } elseif ($this->email != '') { + + $gravatar = md5(strtolower(trim($this->email))); return '//gravatar.com/avatar/'.$gravatar; } } diff --git a/composer.json b/composer.json index 2f1007fa73..165a4a08f6 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "php": ">=7.4 <8.3", + "php": ">=7.4.3 <8.2", "ext-curl": "*", "ext-fileinfo": "*", "ext-json": "*", @@ -46,6 +46,7 @@ "laravel/helpers": "^1.4", "laravel/passport": "^10.1", "laravel/slack-notification-channel": "^2.3", + "laravel/socialite": "^5.6", "laravel/tinker": "^2.6", "laravel/ui": "^3.3", "laravelcollective/html": "^6.2", diff --git a/composer.lock b/composer.lock index c34d46ce69..1ef75690aa 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "590171872e4a6a29c78efde99fbbf00e", + "content-hash": "4c82b2e171fb02a3ef024906db5d74c9", "packages": [ { "name": "alek13/slack", @@ -2673,6 +2673,7 @@ "type": "github" } ], + "abandoned": true, "time": "2022-02-23T14:25:13+00:00" }, { @@ -3604,6 +3605,75 @@ }, "time": "2022-01-12T18:07:54+00:00" }, + { + "name": "laravel/socialite", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/socialite.git", + "reference": "a14a177f2cc71d8add71e2b19e00800e83bdda09" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/socialite/zipball/a14a177f2cc71d8add71e2b19e00800e83bdda09", + "reference": "a14a177f2cc71d8add71e2b19e00800e83bdda09", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "league/oauth1-client": "^1.10.1", + "php": "^7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Socialite\\SocialiteServiceProvider" + ], + "aliases": { + "Socialite": "Laravel\\Socialite\\Facades\\Socialite" + } + } + }, + "autoload": { + "psr-4": { + "Laravel\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.", + "homepage": "https://laravel.com", + "keywords": [ + "laravel", + "oauth" + ], + "support": { + "issues": "https://github.com/laravel/socialite/issues", + "source": "https://github.com/laravel/socialite" + }, + "time": "2023-01-20T15:42:35+00:00" + }, { "name": "laravel/tinker", "version": "v2.7.2", @@ -3803,6 +3873,7 @@ "issues": "https://github.com/LaravelCollective/html/issues", "source": "https://github.com/LaravelCollective/html" }, + "abandoned": "spatie/laravel-html", "time": "2022-02-08T21:02:54+00:00" }, { @@ -4532,6 +4603,82 @@ ], "time": "2022-04-17T13:12:02+00:00" }, + { + "name": "league/oauth1-client", + "version": "v1.10.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth1-client.git", + "reference": "d6365b901b5c287dd41f143033315e2f777e1167" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167", + "reference": "d6365b901b5c287dd41f143033315e2f777e1167", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "guzzlehttp/psr7": "^1.7|^2.0", + "php": ">=7.1||>=8.0" + }, + "require-dev": { + "ext-simplexml": "*", + "friendsofphp/php-cs-fixer": "^2.17", + "mockery/mockery": "^1.3.3", + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5||9.5" + }, + "suggest": { + "ext-simplexml": "For decoding XML-based responses." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev", + "dev-develop": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\OAuth1\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Corlett", + "email": "bencorlett@me.com", + "homepage": "http://www.webcomm.com.au", + "role": "Developer" + } + ], + "description": "OAuth 1.0 Client Library", + "keywords": [ + "Authentication", + "SSO", + "authorization", + "bitbucket", + "identity", + "idp", + "oauth", + "oauth1", + "single sign on", + "trello", + "tumblr", + "twitter" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth1-client/issues", + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1" + }, + "time": "2022-04-15T14:02:14+00:00" + }, { "name": "league/oauth2-server", "version": "8.3.5", @@ -5007,6 +5154,10 @@ "source": "https://github.com/maennchen/ZipStream-PHP/tree/2.2.1" }, "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, { "url": "https://opencollective.com/zipstream", "type": "open_collective" @@ -13984,6 +14135,7 @@ "type": "github" } ], + "abandoned": true, "time": "2020-12-07T05:51:20+00:00" }, { @@ -16410,7 +16562,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.4 <8.3", + "php": ">=7.4.3 <8.2", "ext-curl": "*", "ext-fileinfo": "*", "ext-json": "*", diff --git a/config/app.php b/config/app.php index 8833ea2ae7..2559b8012c 100755 --- a/config/app.php +++ b/config/app.php @@ -216,7 +216,8 @@ return [ */ 'require_saml' => env('REQUIRE_SAML', false), - + + /* |-------------------------------------------------------------------------- | Demo Mode Lockdown @@ -294,6 +295,7 @@ return [ Laravel\Tinker\TinkerServiceProvider::class, Unicodeveloper\DumbPassword\DumbPasswordServiceProvider::class, Eduardokum\LaravelMailAutoEmbed\ServiceProvider::class, + Laravel\Socialite\SocialiteServiceProvider::class, /* * Application Service Providers... @@ -366,6 +368,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) + 'Socialite' => Laravel\Socialite\Facades\Socialite::class, ], diff --git a/config/hashing.php b/config/hashing.php index d3c8e2fb22..06f18a0c6d 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -15,7 +15,7 @@ return [ | */ - 'driver' => 'bcrypt', + 'driver' => env('HASHING_DRIVER', 'bcrypt'), /* |-------------------------------------------------------------------------- @@ -44,9 +44,9 @@ return [ */ 'argon' => [ - 'memory' => 1024, - 'threads' => 2, - 'time' => 2, + 'memory' => env('ARGON_MEMORY', 1024), + 'threads' => env('ARGON_THREADS', 2), + 'time' => env('ARGON_TIME', 2), ], ]; diff --git a/config/mail.php b/config/mail.php index d47d182ed3..f650cf5387 100755 --- a/config/mail.php +++ b/config/mail.php @@ -140,8 +140,8 @@ return [ | */ - 'sendmail' => '/usr/sbin/sendmail -bs', - + 'sendmail' => env('SENDMAIL_PATH', ini_get('sendmail_path')), + 'markdown' => [ 'theme' => 'default', 'paths' => [ diff --git a/config/version.php b/config/version.php index f8c297c9ca..4f60d60906 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v6.1.0', - 'full_app_version' => 'v6.1.0 - build 10161-ga8ca3ad2a', - 'build_version' => '10161', + 'app_version' => 'v6.1.1-pre', + 'full_app_version' => 'v6.1.1-pre - build 10534-g609b1646e', + 'build_version' => '10534', 'prerelease_version' => '', - 'hash_version' => 'ga8ca3ad2a', - 'full_hash' => 'v6.1.0-127-ga8ca3ad2a', + 'hash_version' => 'g609b1646e', + 'full_hash' => 'v6.1.1-pre-292-g609b1646e', 'branch' => 'develop', ); \ No newline at end of file diff --git a/database/factories/CheckoutAcceptanceFactory.php b/database/factories/CheckoutAcceptanceFactory.php new file mode 100644 index 0000000000..b5744527f8 --- /dev/null +++ b/database/factories/CheckoutAcceptanceFactory.php @@ -0,0 +1,41 @@ + Asset::class, + 'checkoutable_id' => Asset::factory(), + 'assigned_to_id' => User::factory(), + ]; + } + + public function forAccessory() + { + return $this->state([ + 'checkoutable_type' => Accessory::class, + 'checkoutable_id' => Accessory::factory(), + ]); + } + + public function pending() + { + return $this->state([ + 'accepted_at' => null, + 'declined_at' => null, + ]); + } +} diff --git a/database/factories/CustomFieldFactory.php b/database/factories/CustomFieldFactory.php index bfa41b4d8d..adcca9cae1 100644 --- a/database/factories/CustomFieldFactory.php +++ b/database/factories/CustomFieldFactory.php @@ -25,6 +25,7 @@ class CustomFieldFactory extends Factory 'name' => $this->faker->catchPhrase(), 'format' => '', 'element' => 'text', + 'auto_add_to_fieldsets' => '0', ]; } diff --git a/database/factories/ManufacturerFactory.php b/database/factories/ManufacturerFactory.php index ab22262a78..4e736b8d8a 100644 --- a/database/factories/ManufacturerFactory.php +++ b/database/factories/ManufacturerFactory.php @@ -38,6 +38,7 @@ class ManufacturerFactory extends Factory 'name' => 'Apple', 'url' => 'https://apple.com', 'support_url' => 'https://support.apple.com', + 'warranty_lookup_url' => 'https://checkcoverage.apple.com', 'image' => 'apple.jpg', ]; }); @@ -50,6 +51,7 @@ class ManufacturerFactory extends Factory 'name' => 'Microsoft', 'url' => 'https://microsoft.com', 'support_url' => 'https://support.microsoft.com', + 'warranty_lookup_url' => 'https://account.microsoft.com/devices', 'image' => 'microsoft.png', ]; }); @@ -62,6 +64,7 @@ class ManufacturerFactory extends Factory 'name' => 'Dell', 'url' => 'https://dell.com', 'support_url' => 'https://support.dell.com', + 'warranty_lookup_url' => 'https://www.dell.com/support/home/en-us/Products/?app=warranty', 'image' => 'dell.png', ]; }); diff --git a/database/migrations/2023_04_25_085912_add_autoadd_to_customfields.php b/database/migrations/2023_04_25_085912_add_autoadd_to_customfields.php new file mode 100644 index 0000000000..918bec9c5c --- /dev/null +++ b/database/migrations/2023_04_25_085912_add_autoadd_to_customfields.php @@ -0,0 +1,34 @@ +boolean('auto_add_to_fieldsets')->nullable()->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('custom_fields', function (Blueprint $table) { + if (Schema::hasColumn('custom_fields', 'auto_add_to_fieldsets')) { + $table->dropColumn('auto_add_to_fieldsets'); + } + }); + } +} diff --git a/database/migrations/2023_04_26_160235_add_warranty_url_to_manufacturers.php b/database/migrations/2023_04_26_160235_add_warranty_url_to_manufacturers.php new file mode 100644 index 0000000000..52a655f2e4 --- /dev/null +++ b/database/migrations/2023_04_26_160235_add_warranty_url_to_manufacturers.php @@ -0,0 +1,32 @@ +string('warranty_lookup_url')->after('support_url')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('manufacturers', function (Blueprint $table) { + $table->dropColumn('warranty_lookup_url'); + }); + } +} diff --git a/database/migrations/2023_05_08_132921_increase_state_to_more_than_3.php b/database/migrations/2023_05_08_132921_increase_state_to_more_than_3.php new file mode 100644 index 0000000000..7ffc0c3860 --- /dev/null +++ b/database/migrations/2023_05_08_132921_increase_state_to_more_than_3.php @@ -0,0 +1,40 @@ +string('state', 191)->nullable()->default(null)->change(); + }); + + Schema::table('suppliers', function (Blueprint $table) { + $table->string('state', 191)->nullable()->default(null)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->string('state', 3)->nullable()->default(null)->change(); + }); + + Schema::table('suppliers', function (Blueprint $table) { + $table->string('state', 32)->nullable()->default(null)->change(); + }); + } +} diff --git a/database/migrations/2023_05_10_001836_add_google_auth_to_settings.php b/database/migrations/2023_05_10_001836_add_google_auth_to_settings.php new file mode 100644 index 0000000000..7f15e0d6e1 --- /dev/null +++ b/database/migrations/2023_05_10_001836_add_google_auth_to_settings.php @@ -0,0 +1,37 @@ +boolean('google_login')->nullable()->default(0); + $table->string('google_client_id')->nullable()->default(null); + $table->string('google_client_secret')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('google_login'); + $table->dropColumn('google_client_id'); + $table->dropColumn('google_client_secret'); + + }); + } +} diff --git a/package.json b/package.json index 5d8cfd622a..0026531e77 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,6 @@ "tableexport.jquery.plugin": "1.27.0", "tether": "^1.4.0", "vue-resource": "^1.5.2", - "webpack": "^5.77.0" + "webpack": "^5.78.0" } } diff --git a/public/css/build/app.css b/public/css/build/app.css index 8ab54a3251..623ae2df1d 100644 Binary files a/public/css/build/app.css and b/public/css/build/app.css differ diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index 296136c7a6..2c4f57f858 100644 Binary files a/public/css/build/overrides.css and b/public/css/build/overrides.css differ diff --git a/public/css/dist/all.css b/public/css/dist/all.css index d5383def4c..6db24038e1 100644 Binary files a/public/css/dist/all.css and b/public/css/dist/all.css differ diff --git a/public/css/dist/skins/skin-black-dark.css b/public/css/dist/skins/skin-black-dark.css index c9f573c75f..3e17735603 100644 Binary files a/public/css/dist/skins/skin-black-dark.css and b/public/css/dist/skins/skin-black-dark.css differ diff --git a/public/css/dist/skins/skin-black-dark.min.css b/public/css/dist/skins/skin-black-dark.min.css index c9f573c75f..3e17735603 100644 Binary files a/public/css/dist/skins/skin-black-dark.min.css and b/public/css/dist/skins/skin-black-dark.min.css differ diff --git a/public/css/dist/skins/skin-blue-dark.css b/public/css/dist/skins/skin-blue-dark.css index c728a5af28..c4f2672931 100644 Binary files a/public/css/dist/skins/skin-blue-dark.css and b/public/css/dist/skins/skin-blue-dark.css differ diff --git a/public/css/dist/skins/skin-blue-dark.min.css b/public/css/dist/skins/skin-blue-dark.min.css index c728a5af28..c4f2672931 100644 Binary files a/public/css/dist/skins/skin-blue-dark.min.css and b/public/css/dist/skins/skin-blue-dark.min.css differ diff --git a/public/css/dist/skins/skin-green-dark.css b/public/css/dist/skins/skin-green-dark.css index 5bf1b300d3..8e5430e5cd 100644 Binary files a/public/css/dist/skins/skin-green-dark.css and b/public/css/dist/skins/skin-green-dark.css differ diff --git a/public/css/dist/skins/skin-green-dark.min.css b/public/css/dist/skins/skin-green-dark.min.css index 5bf1b300d3..8e5430e5cd 100644 Binary files a/public/css/dist/skins/skin-green-dark.min.css and b/public/css/dist/skins/skin-green-dark.min.css differ diff --git a/public/css/dist/skins/skin-orange-dark.css b/public/css/dist/skins/skin-orange-dark.css index 5f4e719296..1ec2c0701a 100644 Binary files a/public/css/dist/skins/skin-orange-dark.css and b/public/css/dist/skins/skin-orange-dark.css differ diff --git a/public/css/dist/skins/skin-orange-dark.min.css b/public/css/dist/skins/skin-orange-dark.min.css index 5f4e719296..1ec2c0701a 100644 Binary files a/public/css/dist/skins/skin-orange-dark.min.css and b/public/css/dist/skins/skin-orange-dark.min.css differ diff --git a/public/css/dist/skins/skin-red-dark.css b/public/css/dist/skins/skin-red-dark.css index bcce285ed1..5ae17e15da 100644 Binary files a/public/css/dist/skins/skin-red-dark.css and b/public/css/dist/skins/skin-red-dark.css differ diff --git a/public/css/dist/skins/skin-red-dark.min.css b/public/css/dist/skins/skin-red-dark.min.css index bcce285ed1..5ae17e15da 100644 Binary files a/public/css/dist/skins/skin-red-dark.min.css and b/public/css/dist/skins/skin-red-dark.min.css differ diff --git a/public/css/dist/skins/skin-yellow-dark.css b/public/css/dist/skins/skin-yellow-dark.css index fddcde6fa7..1f16540750 100644 Binary files a/public/css/dist/skins/skin-yellow-dark.css and b/public/css/dist/skins/skin-yellow-dark.css differ diff --git a/public/css/dist/skins/skin-yellow-dark.min.css b/public/css/dist/skins/skin-yellow-dark.min.css index fddcde6fa7..1f16540750 100644 Binary files a/public/css/dist/skins/skin-yellow-dark.min.css and b/public/css/dist/skins/skin-yellow-dark.min.css differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index cafa726443..931a6156e1 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,24 +1,24 @@ { "/js/build/app.js": "/js/build/app.js?id=7caeae38608edd96421f8ef59d33f5f6", "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374", - "/css/build/overrides.css": "/css/build/overrides.css?id=ce20eefb1895545e882840c480bca0dc", - "/css/build/app.css": "/css/build/app.css?id=3afc900b0a697567f8285f46aded1c89", + "/css/build/overrides.css": "/css/build/overrides.css?id=97a58ce3a89cd0043a1c54ecf63d4686", + "/css/build/app.css": "/css/build/app.css?id=02dbcc25fce08e9b9a9b285883821805", "/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=dc383f8560a8d4adb51d44fb4043e03b", "/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", - "/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=ca38553d041220a4296dda555940e056", - "/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=032f18fdd48936784cfcfe70712a68ae", - "/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=e5b6ec4691d8fd647d38722886f983e6", + "/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=e6e53eef152bba01a4c666a4d8b01117", + "/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=07273f6ca3c698a39e8fc2075af4fa07", + "/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=7c4badf178b44a4489dac90c0f772908", "/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=7b315b9612b8fde8f9c5b0ddb6bba690", "/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=7d92dea45d94be7e1d4e427c728d335d", "/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=6fe68325d5356197672c27bc77cedcb4", - "/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=218c6d947f73c767d23a663a9859d97e", - "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=87c6506e9aac3ebc68dfd99b6f983602", + "/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=d6086232485d2b9661b7f83ed482355e", + "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=bdfc704731682c67645a2248b0b8d2d7", "/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb", - "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=28b36223cf7b1d6e5f236859a4ef2b45", + "/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=e36e83c2aa3c3afdbb8ebe2c0309e91d", "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da", "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898", - "/css/dist/all.css": "/css/dist/all.css?id=41d8a74912c3b0d17cc508361530c597", + "/css/dist/all.css": "/css/dist/all.css?id=999cf8c1432f7baff31b61a04a9a8485", "/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=e2e2b1797606a266ed55549f5bb5a179", @@ -34,18 +34,18 @@ "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=a0e44dba789031b34ef150a01318b865", "/js/dist/all.js": "/js/dist/all.js?id=97b1034b75e3ac29a2eb9770d66c3370", "/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", - "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=28b36223cf7b1d6e5f236859a4ef2b45", + "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=e36e83c2aa3c3afdbb8ebe2c0309e91d", "/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=76482123f6c70e866d6b971ba91de7bb", - "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=87c6506e9aac3ebc68dfd99b6f983602", + "/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=bdfc704731682c67645a2248b0b8d2d7", "/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=f677207c6cf9678eb539abecb408c374", - "/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=032f18fdd48936784cfcfe70712a68ae", + "/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=07273f6ca3c698a39e8fc2075af4fa07", "/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=7b315b9612b8fde8f9c5b0ddb6bba690", - "/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=e5b6ec4691d8fd647d38722886f983e6", + "/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=7c4badf178b44a4489dac90c0f772908", "/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=44bf834f2110504a793dadec132a5898", - "/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=218c6d947f73c767d23a663a9859d97e", + "/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=d6086232485d2b9661b7f83ed482355e", "/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=6fe68325d5356197672c27bc77cedcb4", "/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=7d92dea45d94be7e1d4e427c728d335d", "/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", - "/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=ca38553d041220a4296dda555940e056", + "/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=e6e53eef152bba01a4c666a4d8b01117", "/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da" } diff --git a/resources/assets/less/overrides.less b/resources/assets/less/overrides.less index 60a75358fb..e8dc67ce3d 100644 --- a/resources/assets/less/overrides.less +++ b/resources/assets/less/overrides.less @@ -195,6 +195,9 @@ a.accordion-header { .btn-info.btn-outline { color: #5bc0de; } +.btn-warning{ + background-color:#f39c12 !important; +} .btn-warning.btn-outline { color: #f0ad4e; @@ -499,14 +502,14 @@ h4 { background: #FFFFFF; border-top: 1px solid #dddddd; display: table-row; - + } .row-new-striped > .row:nth-of-type(odd) { background-color: #F8F8F8; border-top: 1px solid #dddddd; display: table-row; - + } .row-new-striped div { @@ -740,7 +743,7 @@ input[type="checkbox"] { input[type="checkbox"]::before { -/** If you want to use the non-checkbox, filled square, use this instead **/ + /** If you want to use the non-checkbox, filled square, use this instead **/ content: ""; width: 1em; height: 1em; diff --git a/resources/assets/less/skins/skin-black-dark.less b/resources/assets/less/skins/skin-black-dark.less index e914d2c0a4..69a393e80d 100644 --- a/resources/assets/less/skins/skin-black-dark.less +++ b/resources/assets/less/skins/skin-black-dark.less @@ -135,6 +135,9 @@ a { #ldapad_test_results.well.well-sm{ color: var(--back-main); } +a.actions { + color:#fff !important; +} //pagination .pagination > li >a{ color: var(--light-link); @@ -147,6 +150,23 @@ a { .pagination > .active > a:hover{ background-color: var(--hover-link); } +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{ + background-color: var(--background); +} +a:hover > h2.task_menu{ + color:var(--header); +} +h2.task_menu{ + color:var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{ + background-color:var(--back-main); + color:var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu > li.header{ + background-color:var(--header); + color:var(--link); +} .main-header .navbar, .main-header .logo { background-color: var(--header); diff --git a/resources/assets/less/skins/skin-blue-dark.less b/resources/assets/less/skins/skin-blue-dark.less index e886b24f9b..6c502b1781 100644 --- a/resources/assets/less/skins/skin-blue-dark.less +++ b/resources/assets/less/skins/skin-blue-dark.less @@ -132,6 +132,9 @@ a { #ldapad_test_results.well.well-sm{ color: var(--back-main); } +a.actions { + color:#fff !important; +} //pagination .pagination > li >a{ color: var(--light-link); @@ -394,6 +397,12 @@ a { color: var(--visited-link) } } +#customFieldsTable a[href*='/models'] { + color: var(--back-sub); +} +#customFieldsTable a[href*='/fieldsets']{ + background-color: transparent; +} .row-striped { vertical-align: top; diff --git a/resources/assets/less/skins/skin-green-dark.less b/resources/assets/less/skins/skin-green-dark.less index 518146cdb0..a69f918acf 100644 --- a/resources/assets/less/skins/skin-green-dark.less +++ b/resources/assets/less/skins/skin-green-dark.less @@ -131,6 +131,9 @@ a { #ldapad_test_results.well.well-sm{ color: var(--back-main); } +a.actions { + color:#fff !important; +} //pagination .pagination > li >a{ color: var(--light-link); @@ -180,10 +183,6 @@ h2.task_menu{ color: var(--text-main); } -a:link { - color: var(--link); -} - .btn-primary.hover { color: var(--nav-link); } @@ -385,6 +384,12 @@ a { color: var(--visited-link) } } +#customFieldsTable a[href*='/models'] { + color: var(--back-sub); +} +#customFieldsTable a[href*='/fieldsets']{ + background-color: transparent; +} .row-striped { vertical-align: top; diff --git a/resources/assets/less/skins/skin-orange-dark.less b/resources/assets/less/skins/skin-orange-dark.less index bd1386d1eb..98925b475e 100644 --- a/resources/assets/less/skins/skin-orange-dark.less +++ b/resources/assets/less/skins/skin-orange-dark.less @@ -119,6 +119,9 @@ li.dropdown-item-marker { #ldapad_test_results.well.well-sm{ color: var(--back-main); } +a.actions { + color:#fff !important; +} //pagination .pagination > li >a{ color: var(--light-link); @@ -378,6 +381,12 @@ input[type=text], input[type=search] { .box-header.with-border { border-bottom: #000; } +#customFieldsTable a[href*='/models'] { + color: var(--back-sub); +} +#customFieldsTable a[href*='/fieldsets']{ + background-color: transparent; +} .row-striped { vertical-align: top; diff --git a/resources/assets/less/skins/skin-red-dark.less b/resources/assets/less/skins/skin-red-dark.less index dbb0023699..8d75bbc792 100644 --- a/resources/assets/less/skins/skin-red-dark.less +++ b/resources/assets/less/skins/skin-red-dark.less @@ -58,8 +58,6 @@ } } - - .btn, .btn:hover { color: #fff; @@ -134,6 +132,7 @@ a { #ldapad_test_results.well.well-sm{ color: var(--back-main); } + //pagination .pagination > li >a{ color: var(--light-link); @@ -186,14 +185,12 @@ h2.task_menu{ a:link { color: var(--link); } - -a:visited { - color: var(--nav-link); -} - a:hover { color: var(--hover-link); } +a:visited { + color: var(--nav-link); +} .far fa-life-ring{ color:var(--link); } @@ -380,7 +377,6 @@ input[type=text], input[type=search] { } .table-striped>tbody>tr:nth-of-type(even){ background-color: var(--back-sub-alt); - color: var(--text-alt); } #webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header { color: var(--text-main); @@ -394,16 +390,22 @@ a { color: var(--link); &:link { - color: var(--link) } + color: var(--nav-link); + } &:hover { - color: var(--hover-link); + color: var(--nav-link); text-decoration: underline; } &:visited { - color: var(--visited-link) + color: var(--nav-link); } } - +#customFieldsTable a[href*='/models'] { + color: var(--back-sub); +} +#customFieldsTable a[href*='/fieldsets']{ + background-color: transparent; +} .row-striped { vertical-align: top; line-height: 2.6; diff --git a/resources/assets/less/skins/skin-yellow-dark.less b/resources/assets/less/skins/skin-yellow-dark.less index 99678e7743..2718eb638b 100644 --- a/resources/assets/less/skins/skin-yellow-dark.less +++ b/resources/assets/less/skins/skin-yellow-dark.less @@ -52,7 +52,6 @@ } .btn, .btn:hover { - color: #000; &.btn-primary, .btn-primary:link { background-color: var(--button-default); @@ -60,7 +59,6 @@ color: #545454; } - &a.btn-primary:hover { background-color: var(--button-hover); border-color: var(--button-hover); @@ -77,6 +75,9 @@ color: #545454; } } +a.actions { + color:#fff !important; +} /** The dropdown is white, so use a darker color @@ -165,7 +166,7 @@ a.btn.btn-default{ color:var(--nav-link); } .bootstrap-table .fixed-table-container .table thead th .sortable { - color: var(--nav-link); + color: var(--text-main); } .bootstrap-table .fixed-table-toolbar .columns label { color:#000; @@ -355,12 +356,9 @@ input[type=text], input[type=search] { .skin-yellow-dark .main-header .navbar .dropdown-menu li a { color: var(--header); } -div.th-inner{ +tr th div.th-inner { color:var(--text-main); } -.fixed-table-body thead th .th-inner, .skin-yellow-dark, .skin-yellow { - background-color: var(--header)!important; -} .tab-content, .tab-pane { background-color: var(--back-main); color: var(--text-main); diff --git a/resources/lang/en/admin/accessories/general.php b/resources/lang/en/admin/accessories/general.php index 84a3e83383..bed7f38fab 100644 --- a/resources/lang/en/admin/accessories/general.php +++ b/resources/lang/en/admin/accessories/general.php @@ -17,5 +17,6 @@ return array( 'use_default_eula' => 'Use the primary default EULA instead.', 'use_default_eula_disabled' => 'Use the primary default EULA instead. No primary default EULA is set. Please add one in Settings.', 'clone' => 'Clone Accessory', + 'delete_disabled' => 'This accessory cannot be deleted yet because some items are still checked out.', ); diff --git a/resources/lang/en/admin/custom_fields/general.php b/resources/lang/en/admin/custom_fields/general.php index 9dae380aa5..1923aa7f4a 100644 --- a/resources/lang/en/admin/custom_fields/general.php +++ b/resources/lang/en/admin/custom_fields/general.php @@ -49,4 +49,6 @@ return [ 'unique' => 'Unique', 'display_in_user_view' => 'Allow the checked out user to view these values in their View Assigned Assets page', 'display_in_user_view_table' => 'Visible to User', + 'auto_add_to_fieldsets' => 'Automatically add this to every new fieldset', + 'add_to_preexisting_fieldsets' => 'Add to any existing fieldsets', ]; diff --git a/resources/lang/en/admin/hardware/table.php b/resources/lang/en/admin/hardware/table.php index 6166ba8045..10629fd22c 100644 --- a/resources/lang/en/admin/hardware/table.php +++ b/resources/lang/en/admin/hardware/table.php @@ -8,6 +8,7 @@ return [ 'change' => 'In/Out', 'checkout_date' => 'Checkout Date', 'checkoutto' => 'Checked Out', + 'components_cost' => 'Total Components Cost', 'current_value' => 'Current Value', 'diff' => 'Diff', 'dl_csv' => 'Download CSV', diff --git a/resources/lang/en/admin/manufacturers/message.php b/resources/lang/en/admin/manufacturers/message.php index 21a4bc5aaf..d6656683ae 100644 --- a/resources/lang/en/admin/manufacturers/message.php +++ b/resources/lang/en/admin/manufacturers/message.php @@ -2,6 +2,7 @@ return array( + 'support_url_help' => 'Use {LOCALE} and {SERIAL} in your URL as variables to have those values auto-populate when viewing assets.', 'does_not_exist' => 'Manufacturer does not exist.', 'assoc_users' => 'This manufacturer is currently associated with at least one model and cannot be deleted. Please update your models to no longer reference this manufacturer and try again. ', diff --git a/resources/lang/en/admin/manufacturers/table.php b/resources/lang/en/admin/manufacturers/table.php index 4e3ea9904d..38cab6fd91 100644 --- a/resources/lang/en/admin/manufacturers/table.php +++ b/resources/lang/en/admin/manufacturers/table.php @@ -10,6 +10,7 @@ return array( 'support_email' => 'Support Email', 'support_phone' => 'Support Phone', 'support_url' => 'Support URL', + 'warranty_lookup_url' => 'Warranty Lookup URL', 'update' => 'Update Manufacturer', 'url' => 'URL', diff --git a/resources/lang/en/admin/settings/general.php b/resources/lang/en/admin/settings/general.php index c69c944579..92faf85c5f 100644 --- a/resources/lang/en/admin/settings/general.php +++ b/resources/lang/en/admin/settings/general.php @@ -330,4 +330,9 @@ return [ 'setup_migration_create_user' => 'Next: Create User', 'ldap_settings_link' => 'LDAP Settings Page', 'slack_test' => 'Test Integration', + 'google_callback_help' => 'This should be entered as the callback URL in your Google OAuth app settings in your organization's Google developer console .', + 'google_login' => 'Google Workspace Login Settings', + 'enable_google_login' => 'Enable users to login with Google Workspace', + 'enable_google_login_help' => 'Users will not be automatically provisioned. They must have an existing account here AND in Google Workspace, and their username here must match their Google Workspace email address. ', + ]; diff --git a/resources/lang/en/auth/general.php b/resources/lang/en/auth/general.php index 78b6780927..4486f090b2 100644 --- a/resources/lang/en/auth/general.php +++ b/resources/lang/en/auth/general.php @@ -12,5 +12,8 @@ return [ 'remember_me' => 'Remember Me', 'username_help_top' => 'Enter your username to be emailed a password reset link.', 'username_help_bottom' => 'Your username and email address may be the same, but may not be, depending on your configuration. If you cannot remember your username, contact your administrator.

Usernames without an associated email address will not be emailed a password reset link. ', - ]; + 'google_login' => 'Or login with Google Workspace', + 'google_login_failed' => 'Google Login failed, please try again.', + +]; diff --git a/resources/lang/en/general.php b/resources/lang/en/general.php index 4874158bc9..674dec6e47 100644 --- a/resources/lang/en/general.php +++ b/resources/lang/en/general.php @@ -91,6 +91,8 @@ return [ 'debug_warning' => 'Warning!', 'debug_warning_text' => 'This application is running in production mode with debugging enabled. This can expose sensitive data if your application is accessible to the outside world. Disable debug mode by setting the APP_DEBUG value in your .env file to false.', 'delete' => 'Delete', + 'delete_confirm' => 'Are you sure you wish to delete :item?', + 'delete_confirm_no_undo' => 'Are you sure you wish to delete :item? This can not be undone.', 'deleted' => 'Deleted', 'delete_seats' => 'Deleted Seats', 'deletion_failed' => 'Deletion failed', diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 31c9dcd85d..df514da6f9 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -67,6 +67,8 @@ return [ 'array' => 'The :attribute must have at least :min items.', ], 'starts_with' => 'The :attribute must start with one of the following: :values.', + 'ends_with' => 'The :attribute must end with one of the following: :values.', + 'not_in' => 'The selected :attribute is invalid.', 'numeric' => 'The :attribute must be a number.', 'present' => 'The :attribute field must be present.', diff --git a/resources/views/accessories/view.blade.php b/resources/views/accessories/view.blade.php index a34cf572c4..b3798697be 100644 --- a/resources/views/accessories/view.blade.php +++ b/resources/views/accessories/view.blade.php @@ -12,43 +12,6 @@ @parent @stop -{{-- Right header --}} -@section('header_right') - @can('manage', \App\Models\Accessory::class) - - @endcan -@stop - {{-- Page content --}} @section('content') {{-- Page content --}} @@ -136,7 +99,7 @@ - +
@@ -271,13 +234,13 @@
-
- - @endcan - - - + + @endcan + + + + @@ -294,10 +257,10 @@ @if ($accessory->company)
-
+
{{ trans('general.company')}}
- @@ -306,10 +269,10 @@ @if ($accessory->category)
-
+
{{ trans('general.category')}}
- @@ -317,88 +280,75 @@ @if ($accessory->notes) - -
+
+
{{ trans('general.notes') }}
-
+
{!! nl2br(e($accessory->notes)) !!}
+
@endif
-
+
{{ trans('admin/accessories/general.remaining') }}
-
+
{{ $accessory->numRemaining() }}
-
+
{{ trans('general.checked_out') }}
-
+
{{ $accessory->users_count }}
+
+
+ @can('checkout', \App\Models\Accessory::class) + + @endcan + @can('update', \App\Models\Accessory::class) + + @endcan + @can('update', \App\Models\Accessory::class) + + @endcan - - @can('checkout', \App\Models\Accessory::class) - - @endcan - - -
-
-
- - - - - - - - - - - - @if ($snipeSettings->require_accept_signature=='1') - - @endif - - -
{{ trans('general.record_created') }}{{ trans('general.admin') }}{{ trans('general.action') }}{{ trans('general.item') }}{{ trans('general.target') }}{{ trans('general.notes') }}{{ trans('general.date') }}{{ trans('general.signature') }}
-
-
-
-
-
+ @can('delete', $accessory) + @if ($accessory->users_count == 0) +
+ +
+ @else + + @endif + @endcan +
+
@@ -411,5 +361,13 @@ @section('moar_scripts') + @include ('partials.bootstrap-table') @stop diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 330d7bf503..8d9f648a9a 100755 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -64,7 +64,7 @@
@if (!config('app.require_saml') && $snipeSettings->saml_enabled) -
+ -
+ @if ($snipeSettings->custom_forgot_pass_url) - {{ trans('auth/general.forgot_password') }} + @elseif (!config('app.require_saml')) - {{ trans('auth/general.forgot_password') }} + @endif -
+
+ @if (($snipeSettings->google_login=='1') && ($snipeSettings->google_client_id!='') && ($snipeSettings->google_client_secret!='')) + + + {{ trans('auth/general.google_login') }} + + @endif +
diff --git a/resources/views/custom_fields/fields/edit.blade.php b/resources/views/custom_fields/fields/edit.blade.php index 8e025e4b6b..9d51138442 100644 --- a/resources/views/custom_fields/fields/edit.blade.php +++ b/resources/views/custom_fields/fields/edit.blade.php @@ -1,8 +1,8 @@ -@php - use App\Models\CustomField; -@endphp +@extends('layouts/default', [ + 'helpText' => trans('admin/custom_fields/general.about_fieldsets_text'), + 'helpPosition' => 'right', +]) -@extends('layouts/default') {{-- Page title --}} @section('title') @@ -21,24 +21,31 @@ {{-- Page content --}} @section('content') -
-
- @if ($field->id) - {{ Form::open(['route' => ['fields.update', $field->id], 'class'=>'form-horizontal']) }} - {{ method_field('PUT') }} - @else - {{ Form::open(['route' => 'fields.store', 'class'=>'form-horizontal']) }} - @endif + @if ($field->id) + {{ Form::open(['route' => ['fields.update', $field->id], 'class'=>'form-horizontal']) }} + {{ method_field('PUT') }} + @else + {{ Form::open(['route' => 'fields.store', 'class'=>'form-horizontal']) }} + @endif + @csrf +
+
+
+ +
+ +
+
-