diff --git a/Dockerfile b/Dockerfile index 09e511ba80..ed942e2587 100644 --- a/Dockerfile +++ b/Dockerfile @@ -96,6 +96,8 @@ RUN \ && rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups" \ && mkdir -p "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \ && ln -fs "/var/lib/snipeit/keys/oauth-public.key" "/var/www/html/storage/oauth-public.key" \ + && ln -fs "/var/lib/snipeit/keys/ldap_client_tls.cert" "/var/www/html/storage/ldap_client_tls.cert" \ + && ln -fs "/var/lib/snipeit/keys/ldap_client_tls.key" "/var/www/html/storage/ldap_client_tls.key" \ && chown docker "/var/lib/snipeit/keys/" \ && chown -h docker "/var/www/html/storage/" \ && chmod +x /var/www/html/artisan \ diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php index 8f8114229e..0ebda27d89 100644 --- a/app/Console/Commands/RestoreFromBackup.php +++ b/app/Console/Commands/RestoreFromBackup.php @@ -14,7 +14,7 @@ class RestoreFromBackup extends Command */ protected $signature = 'snipeit:restore {--force : Skip the danger prompt; assuming you hit "y"} - {filename : The zip file to be migrated} + {filename : The full path of the .zip file to be migrated} {--no-progress : Don\'t show a progress bar}'; /** @@ -22,7 +22,7 @@ class RestoreFromBackup extends Command * * @var string */ - protected $description = 'Restore from a previously created backup'; + protected $description = 'Restore from a previously created Snipe-IT backup file'; /** * Create a new command instance. @@ -34,6 +34,8 @@ class RestoreFromBackup extends Command parent::__construct(); } + public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases? + /** * Execute the console command. * @@ -42,7 +44,10 @@ class RestoreFromBackup extends Command public function handle() { $dir = getcwd(); - echo "Current working directory is: $dir\n"; + if( $dir != base_path() ) { // usually only the case when running via webserver, not via command-line + \Log::debug("Current working directory is: $dir, changing directory to: ".base_path()); + chdir(base_path()); // TODO - is this *safe* to change on a running script?! + } // $filename = $this->argument('filename'); @@ -67,7 +72,7 @@ class RestoreFromBackup extends Command ZipArchive::ER_INCONS => 'Zip archive inconsistent.', ZipArchive::ER_INVAL => 'Invalid argument.', ZipArchive::ER_MEMORY => 'Malloc failure.', - ZipArchive::ER_NOENT => 'No such file.', + ZipArchive::ER_NOENT => 'No such file ('.$filename.') in directory '.$dir.'.', ZipArchive::ER_NOZIP => 'Not a zip archive.', ZipArchive::ER_OPEN => "Can't open file.", ZipArchive::ER_READ => 'Read error.', @@ -144,7 +149,7 @@ class RestoreFromBackup extends Command continue; } if (@pathinfo($raw_path)['extension'] == 'sql') { - echo "Found a sql file!\n"; + \Log::debug("Found a sql file!"); $sqlfiles[] = $raw_path; $sqlfile_indices[] = $i; continue; @@ -206,7 +211,13 @@ class RestoreFromBackup extends Command $env_vars = getenv(); $env_vars['MYSQL_PWD'] = config('database.connections.mysql.password'); - $proc_results = proc_open('mysql -h '.escapeshellarg(config('database.connections.mysql.host')).' -u '.escapeshellarg(config('database.connections.mysql.username')).' '.escapeshellarg(config('database.connections.mysql.database')), // yanked -p since we pass via ENV + // TODO notes: we are stealing the dump_binary_path (which *probably* also has your copy of the mysql binary in it. But it might not, so we might need to extend this) + // we unilaterally prepend a slash to the `mysql` command. This might mean your path could look like /blah/blah/blah//mysql - which should be fine. But maybe in some environments it isn't? + $mysql_binary = config('database.connections.mysql.dump.dump_binary_path').'/mysql'; + if( ! file_exists($mysql_binary) ) { + return $this->error("mysql tool at: '$mysql_binary' does not exist, cannot restore. Please edit DB_DUMP_PATH in your .env to point to a directory that contains the mysqldump and mysql binary"); + } + $proc_results = proc_open("$mysql_binary -h ".escapeshellarg(config('database.connections.mysql.host')).' -u '.escapeshellarg(config('database.connections.mysql.username')).' '.escapeshellarg(config('database.connections.mysql.database')), // yanked -p since we pass via ENV [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, null, @@ -233,9 +244,10 @@ class RestoreFromBackup extends Command return false; } - - while (($buffer = fgets($sql_contents)) !== false) { - //$this->info("Buffer is: '$buffer'"); + $bytes_read = 0; + while (($buffer = fgets($sql_contents, self::$buffer_size)) !== false) { + $bytes_read += strlen($buffer); + // \Log::debug("Buffer is: '$buffer'"); $bytes_written = fwrite($pipes[0], $buffer); if ($bytes_written === false) { $stdout = fgets($pipes[1]); @@ -246,6 +258,10 @@ class RestoreFromBackup extends Command return false; } } + if (!feof($sql_contents) || $bytes_read == 0) { + return $this->error("Not at end of file for sql file, or zero bytes read. aborting!"); + } + fclose($pipes[0]); fclose($sql_contents); @@ -273,7 +289,7 @@ class RestoreFromBackup extends Command $fp = $za->getStream($ugly_file_name); //$this->info("Weird problem, here are file details? ".print_r($file_details,true)); $migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w'); - while (($buffer = fgets($fp)) !== false) { + while (($buffer = fgets($fp, self::$buffer_size)) !== false) { fwrite($migrated_file, $buffer); } fclose($migrated_file); diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index 6e56b317d2..cdf37a903c 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -171,6 +171,7 @@ class AssetsController extends Controller // case we override with the actual count, so we should return 0 items. $offset = (($assets) && ($request->get('offset') > $assets->count())) ? $assets->count() : $request->get('offset', 0); + // Check to make sure the limit is not higher than the max allowed ((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results'); @@ -336,6 +337,7 @@ class AssetsController extends Controller return (new $transformer)->transformAssets($assets, $total, $request); } + /** * Returns JSON with information about an asset (by tag) for detail view. * @@ -373,9 +375,19 @@ class AssetsController extends Controller } return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200); + $assets = Asset::with('assetstatus')->with('assignedTo'); + if ($request->input('deleted', 'false') === 'true') { + $assets = $assets->withTrashed(); } + $assets = $assets->where('serial', $serial)->get(); + if ($assets) { + return (new AssetsTransformer)->transformAssets($assets, $assets->count()); + } else { + return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200); + } + } /** * Returns JSON with information about an asset for detail view. @@ -677,6 +689,8 @@ class AssetsController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); } + + /** * Restore a soft-deleted asset. * @@ -899,7 +913,7 @@ class AssetsController extends Controller } } - return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.$request->input('asset_tag').' not found')); + return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.e($request->input('asset_tag')).' not found')); diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index a52598592f..bf6ad53334 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -11,7 +11,6 @@ use App\Models\Setting; use App\Models\User; use App\Notifications\FirstAdminNotification; use App\Notifications\MailTest; -use Artisan; use Auth; use Crypt; use DB; @@ -22,6 +21,8 @@ use Image; use Input; use Redirect; use Response; +use Illuminate\Support\Str; +use Illuminate\Support\Facades\Artisan; /** * This controller handles all actions related to Settings for @@ -1018,17 +1019,25 @@ class SettingsController extends Controller $backup_files = Storage::files($path); $files_raw = []; + if (count($backup_files) > 0) { for ($f = 0; $f < count($backup_files); $f++) { // Skip dotfiles like .gitignore and .DS_STORE if ((substr(basename($backup_files[$f]), 0, 1) != '.')) { + //$lastmodified = Carbon::parse(Storage::lastModified($backup_files[$f]))->toDatetimeString(); + $file_timestamp = Storage::lastModified($backup_files[$f]); + + $files_raw[] = [ 'filename' => basename($backup_files[$f]), 'filesize' => Setting::fileSizeConvert(Storage::size($backup_files[$f])), - 'modified' => Storage::lastModified($backup_files[$f]), + 'modified_value' => $file_timestamp, + 'modified_display' => Helper::getFormattedDateObject($file_timestamp, $type = 'datetime', false), + ]; } + } } @@ -1128,6 +1137,115 @@ class SettingsController extends Controller } } + + /** + * Uploads a backup file + * + * @author [A. Gianotto] [] + * + * @since [v6.0] + * + * @return Redirect + */ + + public function postUploadBackup(Request $request) { + + if (! config('app.lock_passwords')) { + if (!$request->hasFile('file')) { + return redirect()->route('settings.backups.index')->with('error', 'No file uploaded'); + } else { + $max_file_size = Helper::file_upload_max_size(); + + $rules = [ + 'file' => 'required|mimes:zip|max:'.$max_file_size, + ]; + + $validator = \Validator::make($request->all(), $rules); + + if ($validator->passes()) { + + $upload_filename = 'uploaded-'.date('U').'-'.Str::slug(pathinfo($request->file('file')->getClientOriginalName(), PATHINFO_FILENAME)).'.zip'; + + Storage::putFileAs('app/backups', $request->file('file'), $upload_filename); + + return redirect()->route('settings.backups.index')->with('success', 'File uploaded'); + } else { + return redirect()->route('settings.backups.index')->withErrors($request->getErrors()); + } + } + + } else { + return redirect()->route('settings.backups.index')->with('error', trans('general.feature_disabled')); + } + + + + } + + /** + * Restore the backup file. + * + * @author [A. Gianotto] [] + * + * @since [v6.0] + * + * @return View + */ + public function postRestore($filename = null) + { + + if (! config('app.lock_passwords')) { + $path = 'app/backups'; + + if (Storage::exists($path.'/'.$filename)) { + + // grab the user's info so we can make sure they exist in the system + $user = User::find(Auth::user()->id); + + + // TODO: run a backup + + // TODO: add db:wipe + + + // run the restore command + Artisan::call('snipeit:restore', + [ + '--force' => true, + '--no-progress' => true, + 'filename' => storage_path($path).'/'.$filename + ]); + + $output = Artisan::output(); + + + // If it's greater than 300, it probably worked + if (strlen($output) > 300) { + \Auth::logout(); + return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.'); + } else { + return redirect()->route('settings.backups.index')->with('error', $output); + + } + //dd($output); + + // TODO: insert the user if they are not there in the old one + + + + + // log the user out + + + + } else { + return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found')); + } + } else { + return redirect()->route('settings.backups.index')->with('error', trans('general.feature_disabled')); + } + } + /** * Return a form to allow a super admin to update settings. * diff --git a/app/Http/Requests/AssetFileRequest.php b/app/Http/Requests/AssetFileRequest.php index 1aa1fadb89..f8631f23ba 100644 --- a/app/Http/Requests/AssetFileRequest.php +++ b/app/Http/Requests/AssetFileRequest.php @@ -21,7 +21,7 @@ class AssetFileRequest extends Request */ public function rules() { - $max_file_size = Helper::file_upload_max_size(); + $max_file_size = \App\Helpers\Helper::file_upload_max_size(); return [ 'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,webp|max:'.$max_file_size, diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index c492011110..45d7bca5e6 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -90,11 +90,6 @@ class ImageUploadRequest extends Request $use_db_field = $db_fieldname; } - \Log::info('Image path is: '.$path); - \Log::debug('Type is: '.$type); - \Log::debug('Form fieldname is: '.$form_fieldname); - \Log::debug('DB fieldname is: '.$use_db_field); - \Log::debug('Trying to upload to '. $path); // ConvertBase64ToFiles just changes object type, // as it cannot currently insert files to $this->files diff --git a/app/Http/Transformers/AssetsTransformer.php b/app/Http/Transformers/AssetsTransformer.php index 8af6f62c64..f3b6ba8122 100644 --- a/app/Http/Transformers/AssetsTransformer.php +++ b/app/Http/Transformers/AssetsTransformer.php @@ -93,15 +93,15 @@ class AssetsTransformer $value = (Gate::allows('superadmin')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted')); $fields_array[$field->name] = [ - 'field' => $field->convertUnicodeDbSlug(), - 'value' => $value, + 'field' => e($field->convertUnicodeDbSlug()), + 'value' => e($value), 'field_format' => $field->format, ]; } else { $fields_array[$field->name] = [ - 'field' => $field->convertUnicodeDbSlug(), - 'value' => $asset->{$field->convertUnicodeDbSlug()}, + 'field' => e($field->convertUnicodeDbSlug()), + 'value' => e($asset->{$field->convertUnicodeDbSlug()}), 'field_format' => $field->format, ]; @@ -114,24 +114,13 @@ class AssetsTransformer } $permissions_array['available_actions'] = [ - 'checkout' => Gate::allows('checkout', Asset::class), - 'checkin' => Gate::allows('checkin', Asset::class), - 'clone' => false, - 'restore' => false, - 'update' => (bool) Gate::allows('update', Asset::class), - 'delete' => ($asset->assigned_to == '' && Gate::allows('delete', Asset::class)), - ]; - - if ($asset->deleted_at != '') { - $permissions_array['available_actions'] = [ - 'checkout' => true, - 'checkin' => false, - 'clone' => Gate::allows('create', Asset::class), - 'restore' => Gate::allows('create', Asset::class), - 'update' => false, - 'delete' => false, - ]; - } + 'checkout' => ($asset->deleted_at=='' && Gate::allows('checkout', Asset::class)) ? true : false, + 'checkin' => ($asset->deleted_at=='' && Gate::allows('checkin', Asset::class)) ? true : false, + 'clone' => Gate::allows('create', Asset::class) ? true : false, + 'restore' => ($asset->deleted_at!='' && Gate::allows('create', Asset::class)) ? true : false, + 'update' => ($asset->deleted_at=='' && Gate::allows('update', Asset::class)) ? true : false, + 'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class)) ? true : false, + ]; if (request('components')=='true') { diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index f80b869d54..f5b0ae44da 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -76,6 +76,7 @@ abstract class Importer 'department' => 'department', 'manager_first_name' => 'manager first name', 'manager_last_name' => 'manager last name', + 'min_amt' => 'minimum quantity', ]; /** * Map of item fields->csv names @@ -196,11 +197,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; } diff --git a/app/Importer/import_mappings.md b/app/Importer/import_mappings.md index 7899cf679a..211a68bc9c 100644 --- a/app/Importer/import_mappings.md +++ b/app/Importer/import_mappings.md @@ -29,6 +29,7 @@ | serial number | serial | Asset, license | | status | status | Asset ? All | | supplier | supplier | Asset ? All | +| minimum quantity | min_amt | Consumable | | termination date | termination_date | License | | warranty months | warranty_months | Asset | | User Related Fields | assigned_to | Asset | diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index 2d456e12c4..13591123d7 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -68,6 +68,7 @@ class Consumable extends SnipeModel 'purchase_cost', 'purchase_date', 'qty', + 'min_amt', 'requestable', ]; @@ -185,6 +186,7 @@ class Consumable extends SnipeModel return $this->belongsTo(\App\Models\Category::class, 'category_id'); } + /** * Establishes the component -> action logs relationship * @@ -209,8 +211,8 @@ class Consumable extends SnipeModel if ($this->image) { return Storage::disk('public')->url(app('consumables_upload_path').$this->image); } - return false; + } /** @@ -225,6 +227,7 @@ class Consumable extends SnipeModel return $this->belongsToMany(\App\Models\User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('user_id')->withTrashed()->withTimestamps(); } + /** * Determine whether to send a checkin/checkout email based on * asset model category diff --git a/config/version.php b/config/version.php index 008ac13252..98c5755527 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v6-pre-alpha', - 'full_app_version' => 'v6-pre-alpha - build 6109-gace7abc1a', - 'build_version' => '6109', + 'full_app_version' => 'v6-pre-alpha - build 6506-ge75a5f13e', + 'build_version' => '6506', 'prerelease_version' => '', - 'hash_version' => 'gace7abc1a', - 'full_hash' => 'v6-pre-alpha-71-gace7abc1a', - 'branch' => 'develop-v6-integration', + 'hash_version' => 'ge75a5f13e', + 'full_hash' => 'v6-pre-alpha-13-ge75a5f13e', + 'branch' => 'develop', ); \ No newline at end of file diff --git a/docker/supervisor-exit-event-listener b/docker/supervisor-exit-event-listener index 84201634e1..409ca0565e 100644 --- a/docker/supervisor-exit-event-listener +++ b/docker/supervisor-exit-event-listener @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # A supervisor event listener which terminates supervisord if any of its child # processes enter the FATAL state. # https://stackoverflow.com/a/37527488/119527 diff --git a/package-lock.json b/package-lock.json index 463ca7dc3d..5b7b74c72a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2955,9 +2955,9 @@ "integrity": "sha1-EQPWvADPv6jPyaJZmrUYxVZD2j8=" }, "bootstrap-table": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.18.3.tgz", - "integrity": "sha512-/eFLkldDlNFi37qC/d9THfRVxMUGD34E8fQBFtXJLDHLBOVKWDTq7BV+udoP7k3FfCEyhM1jWQnQ0rMQdBv//w==" + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.19.1.tgz", + "integrity": "sha512-WvV+l1AI/C+zThaKmfHmi/IuayVNB0qdFyEhFx1jyZhO0oLtNJNANkCR3rvJf6Dkh72dsLElxpE/bzK9seEQLA==" }, "brace-expansion": { "version": "1.1.11", @@ -15845,7 +15845,7 @@ "jquery": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", - "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + "integrity": "sha1-xyoJ8Vwb3OFC9J2/EXC9+K2sJHA=" }, "jquery-form-validator": { "version": "2.3.79", @@ -15881,9 +15881,12 @@ "integrity": "sha1-G+i3twTdOFcVJwiu+x1KSzpp+zM=" }, "jquery-ui": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", - "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.0.tgz", + "integrity": "sha512-Osf7ECXNTYHtKBkn9xzbIf9kifNrBhfywFEKxOeB/OVctVmLlouV9mfc2qXCp6uyO4Pn72PXKOnj09qXetopCw==", + "requires": { + "jquery": ">=1.8.0 <4.0.0" + } }, "jquery-ui-bundle": { "version": "1.12.1", diff --git a/package.json b/package.json index 08a18509fb..bbb2b879fa 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "bootstrap-colorpicker": "^2.5.3", "bootstrap-datepicker": "^1.9.0", "bootstrap-less": "^3.3.8", - "bootstrap-table": "^1.18.3", + "bootstrap-table": "^1.19.1", "chart.js": "^2.9.4", "css-loader": "^3.6.0", "ekko-lightbox": "^5.1.1", @@ -41,7 +41,7 @@ "imagemin": "^5.3.1", "jquery-form-validator": "^2.3.79", "jquery-slimscroll": "^1.3.8", - "jquery-ui": "^1.12.1", + "jquery-ui": "^1.13.0", "jquery-ui-bundle": "^1.12.1", "jquery.iframe-transport": "^1.0.0", "less": "^4.1.1", diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index 2ce2c23272..c676b59b66 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 d0ae4b8a21..0c6cf67506 100644 Binary files a/public/css/dist/all.css and b/public/css/dist/all.css differ diff --git a/public/css/dist/bootstrap-table.css b/public/css/dist/bootstrap-table.css index c6485ceb43..9ea548eaff 100644 Binary files a/public/css/dist/bootstrap-table.css and b/public/css/dist/bootstrap-table.css differ diff --git a/public/js/build/app.js b/public/js/build/app.js index 041da5eeed..2e9db85e5c 100644 Binary files a/public/js/build/app.js and b/public/js/build/app.js differ diff --git a/public/js/build/app.js.LICENSE.txt b/public/js/build/app.js.LICENSE.txt index c50b3aa8d6..eff7f713bb 100644 Binary files a/public/js/build/app.js.LICENSE.txt and b/public/js/build/app.js.LICENSE.txt differ diff --git a/public/js/dist/all.js b/public/js/dist/all.js index f22f12b489..a677c9f013 100644 Binary files a/public/js/dist/all.js and b/public/js/dist/all.js differ diff --git a/public/js/dist/bootstrap-table.js b/public/js/dist/bootstrap-table.js index 11e73e214a..452049ad26 100644 Binary files a/public/js/dist/bootstrap-table.js and b/public/js/dist/bootstrap-table.js differ diff --git a/public/js/snipeit.js b/public/js/snipeit.js index cfa646d9e5..81c79c884b 100644 Binary files a/public/js/snipeit.js and b/public/js/snipeit.js differ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index cd55b16f02..038314dd48 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,5 +1,5 @@ { - "/js/build/app.js": "/js/build/app.js?id=c8a70594c0d99275266d", + "/js/build/app.js": "/js/build/app.js?id=16ac5c8f218827150ce0", "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=83e39e254b7f9035eddc", "/css/build/overrides.css": "/css/build/overrides.css?id=b1866ec98d44c0a8ceea", "/css/build/app.css": "/css/build/app.css?id=61d5535cb27cce41d422", @@ -18,15 +18,15 @@ "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=efda2335fa5243175850", "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=6a9d0ac448c28b88e5d6", "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=c24716a423d375902723", - "/css/dist/all.css": "/css/dist/all.css?id=138874c5ffe57b679997", + "/css/dist/all.css": "/css/dist/all.css?id=3480eded2be4cd65a83e", "/css/blue.png": "/css/blue.png?id=e83a6c29e04fe851f212", "/css/blue@2x.png": "/css/blue@2x.png?id=51135dd4d24f88f5de0b", "/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced", "/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced", - "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=93c24b4c89490bbfd73e", + "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=810d7e520c3057ee500e", "/js/build/vendor.js": "/js/build/vendor.js?id=651427cc4b45d8e68d0c", - "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=867755a1544f6c0ea828", - "/js/dist/all.js": "/js/dist/all.js?id=a233dcde4650f5d34491", + "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=0f09ee116517a9573dd0", + "/js/dist/all.js": "/js/dist/all.js?id=fbc9a3fb41192f2724e8", "/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=efda2335fa5243175850", "/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=6e35fb4cb2f1063b3047", "/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=ec96c42439cdeb022133", diff --git a/resources/assets/js/components/importer/importer-file.vue b/resources/assets/js/components/importer/importer-file.vue index 83970614e5..86a83ace22 100644 --- a/resources/assets/js/components/importer/importer-file.vue +++ b/resources/assets/js/components/importer/importer-file.vue @@ -158,6 +158,7 @@ consumables: [ {id: 'item_no', text: "Item Number"}, {id: 'model_number', text: "Model Number"}, + {id: 'min_amt', text: "Minimum Quantity"}, ], licenses: [ {id: 'asset_tag', text: 'Assigned To Asset'}, @@ -216,6 +217,7 @@ .concat(this.columnOptions.accessories) .sort(sorter); case 'consumable': + console.log('Returned consumable'); return this.columnOptions.general .concat(this.columnOptions.consumables) .sort(sorter); @@ -309,4 +311,4 @@ select2: require('../select2.vue').default } } - \ No newline at end of file + diff --git a/resources/assets/js/snipeit.js b/resources/assets/js/snipeit.js index c74340b10c..f55d16a879 100755 --- a/resources/assets/js/snipeit.js +++ b/resources/assets/js/snipeit.js @@ -84,6 +84,37 @@ var baseUrl = $('meta[name="baseUrl"]').attr('content'); var Components = {}; Components.modals = {}; + // confirm restore modal + Components.modals.confirmRestore = function() { + var $el = $('table'); + + var events = { + 'click': function(evnt) { + var $context = $(this); + var $restoreConfirmModal = $('#restoreConfirmModal'); + var href = $context.attr('href'); + var message = $context.attr('data-content'); + var title = $context.attr('data-title'); + + $('#restoreConfirmModalLabel').text(title); + $restoreConfirmModal.find('.modal-body').text(message); + $('#restoreForm').attr('action', href); + $restoreConfirmModal.modal({ + show: true + }); + return false; + } + }; + + var render = function() { + $el.on('click', '.restore-asset', events['click']); + }; + + return { + render: render + }; + }; + // confirm delete modal Components.modals.confirmDelete = function() { var $el = $('table'); @@ -121,6 +152,7 @@ var baseUrl = $('meta[name="baseUrl"]').attr('content'); * Component definition stays out of load event, execution only happens. */ $(function() { + new Components.modals.confirmRestore().render(); new Components.modals.confirmDelete().render(); }); }(jQuery, window.snipeit.settings)); diff --git a/resources/lang/en/general.php b/resources/lang/en/general.php index a4df7e6f83..e2d1c44361 100644 --- a/resources/lang/en/general.php +++ b/resources/lang/en/general.php @@ -124,6 +124,8 @@ 'image' => 'Image', 'image_delete' => 'Delete Image', 'image_upload' => 'Upload Image', + 'filetypes_accepted_help' => 'Accepted filetype is :types. Max upload size allowed is :size.|Accepted filetypes are :types. Max upload size allowed is :size.', + 'filetypes_size_help' => 'Max upload size allowed is :size.', 'image_filetypes_help' => 'Accepted filetypes are jpg, webp, png, gif, and svg. Max upload size allowed is :size.', 'import' => 'Import', 'importing' => 'Importing', diff --git a/resources/views/custom_fields/fieldsets/view.blade.php b/resources/views/custom_fields/fieldsets/view.blade.php index 82b9498fa7..892b56f16e 100644 --- a/resources/views/custom_fields/fieldsets/view.blade.php +++ b/resources/views/custom_fields/fieldsets/view.blade.php @@ -58,20 +58,29 @@ {{ $field->field_encrypted=='1' ? trans('general.yes') : trans('general.no') }} @if ($field->pivot->required) - - - {{ trans('admin/custom_fields/general.make_optional') }} - +
+ @csrf + +
+ @else - - - {{ trans('admin/custom_fields/general.make_required') }} - + +
+ @csrf + +
@endif @can('update', $custom_fieldset) +<<<<<<< HEAD {{ trans('button.remove') }} +======= +
+ @csrf + +
+>>>>>>> 476e17055beb3a2f989946812010d9f23851ca89 @endcan diff --git a/resources/views/layouts/default.blade.php b/resources/views/layouts/default.blade.php index 45ee90610a..43cc8ba94e 100644 --- a/resources/views/layouts/default.blade.php +++ b/resources/views/layouts/default.blade.php @@ -109,14 +109,14 @@ @if ($snipeSettings->brand == '3') @elseif ($snipeSettings->brand == '2') @@ -355,10 +355,17 @@ @endcan
  • - - - {{ trans('general.logout') }} + + + {{ trans('general.logout') }} + {{ csrf_field() }} + + + +
  • @@ -852,6 +859,28 @@ + + + {{-- Javascript files --}} @@ -925,6 +954,7 @@ @endif + @livewireScripts diff --git a/resources/views/licenses/view.blade.php b/resources/views/licenses/view.blade.php index 76df196c13..502f4fa9e9 100755 --- a/resources/views/licenses/view.blade.php +++ b/resources/views/licenses/view.blade.php @@ -47,7 +47,7 @@ - {{ $license->availCount()->count() }} / {{ $license->seats }} + {{ $license->availCount()->count() }} / {{ $license->seats }} diff --git a/resources/views/settings/backups.blade.php b/resources/views/settings/backups.blade.php index 78537bb92e..2018fe1212 100644 --- a/resources/views/settings/backups.blade.php +++ b/resources/views/settings/backups.blade.php @@ -7,7 +7,15 @@ @stop @section('header_right') - {{ trans('general.back') }} + + {{ trans('general.back') }} + + +
    + {{ Form::hidden('_token', csrf_token()) }} + +
    + @stop {{-- Page content --}} @@ -15,10 +23,16 @@
    -
    + +
    +
    + + +
    + - - - + + + + + + @foreach ($files as $file) @@ -43,52 +60,164 @@ {{ $file['filename'] }} - + + @endforeach
    FileCreatedSize
    FileCreatedSize {{ trans('general.delete') }}
    {{ date("M d, Y g:i A", $file['modified']) }} {{ $file['modified_display'] }} {{ $file['modified_value'] }} {{ $file['filesize'] }} @can('superadmin') + class="btn delete-asset btn-danger btn-sm {{ (config('app.lock_passwords')) ? ' disabled': '' }}" data-toggle="modal" href="{{ route('settings.backups.destroy', $file['filename']) }}" data-content="{{ trans('admin/settings/message.backup.delete_confirm') }}" data-title="{{ trans('general.delete') }} {{ e($file['filename']) }} ?" onClick="return false;"> {{ trans('general.delete') }} + + + + Restore + + @endcan
    +
    +
    +
    +
    + + +
    + +
    +
    +

    + + Upload Backup

    +
    +
    +
    + +
    + +

    Backup files on the server are stored in: {{ $path }}

    + + @if (config('app.lock_passwords')===true) +

    {{ trans('general.feature_disabled') }}

    + @else + + {{ Form::open([ + 'method' => 'POST', + 'route' => 'settings.backups.upload', + 'files' => true, + 'class' => 'form-horizontal' ]) }} + @csrf + + +
    +
    + + + + + +
    +
    + +
    +
    + +

    + +

    {{ trans_choice('general.filetypes_accepted_help', 1, ['size' => Helper::file_upload_max_size_readable(), 'types' => '.zip']) }}

    + {!! $errors->first('image', '') !!} + + +
    + +
    + + {{ Form::close() }} + @endif
    -
    -
    - -
    - -
    - {{ Form::hidden('_token', csrf_token()) }} - -

    - -

    - - @if (config('app.lock_passwords')) -

    {{ trans('general.feature_disabled') }}

    - @endif - - -
    -

    Backup files are located in: {{ $path }}

    - +
    +
    +

    + Restoring from Backup

    +
    +
    +
    +
    + +

    + Use the restore button to + restore from a previous backup. (This does not currently with with S3 file storage.)

    + +

    Your entire {{ config('app.name') }} database and any uploaded files will be completely replaced by what's in the backup file. +

    + +

    + You will be logged out once your restore is complete. +

    +

    + Very large backups may time out on the restore attempt and may still need to be run via command line. +

    + +
    + +
    +
    + + @stop @section('moar_scripts') + @include ('partials.bootstrap-table') + + + @stop diff --git a/routes/web.php b/routes/web.php index c1c439638e..a17c511516 100644 --- a/routes/web.php +++ b/routes/web.php @@ -189,6 +189,14 @@ Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'authorize:superuser [SettingsController::class, 'postBackups'] )->name('settings.backups.create'); + Route::post('/restore/{filename}', + [SettingsController::class, 'postRestore'] + )->name('settings.backups.restore'); + + Route::post('/upload', + [SettingsController::class, 'postUploadBackup'] + )->name('settings.backups.upload'); + Route::get('/', [SettingsController::class, 'getBackups'])->name('settings.backups.index'); }); diff --git a/routes/web/fields.php b/routes/web/fields.php index 50fd46efe6..312138985d 100644 --- a/routes/web/fields.php +++ b/routes/web/fields.php @@ -8,51 +8,39 @@ use Illuminate\Support\Facades\Route; * Custom Fields Routes */ -Route::group(['prefix' => 'fields', 'middleware' => ['auth']], function () { - Route::get( + +Route::group([ 'prefix' => 'fields','middleware' => ['auth'] ], function () { + + Route::post( 'required/{fieldset_id}/{field_id}', - [ - CustomFieldsetsController::class, - 'makeFieldRequired' - ] + [CustomFieldsetsController::class, 'makeFieldRequired'] )->name('fields.required'); - Route::get( + Route::post( 'optional/{fieldset_id}/{field_id}', - [ - CustomFieldsetsController::class, - 'makeFieldOptional' - ] + [CustomFieldsetsController::class, 'makeFieldOptional'] )->name('fields.optional'); - Route::get( + Route::post( '{field_id}/fieldset/{fieldset_id}/disassociate', - [ - CustomFieldsetsController::class, - 'deleteFieldFromFieldset' - ] + [CustomFieldsController::class, 'deleteFieldFromFieldset'] )->name('fields.disassociate'); Route::post( 'fieldsets/{id}/associate', - [ - CustomFieldsetsController::class, - 'associate' - ] + [CustomFieldsController::class, 'associate'] )->name('fieldsets.associate'); Route::resource('fieldsets', CustomFieldsetsController::class, [ - 'parameters' => ['fieldset' => 'field_id', 'field' => 'field_id'], - ]); + 'parameters' => ['fieldset' => 'field_id', 'field' => 'field_id'] + ]); }); - Route::resource('fields', CustomFieldsController::class, [ - 'parameters' => ['field' => 'field_id'], + 'middleware' => ['auth'], + 'parameters' => ['field' => 'field_id', 'fieldset' => 'fieldset_id'], ]); - - diff --git a/routes/web/users.php b/routes/web/users.php index 48e5e44142..bc6cd32c67 100644 --- a/routes/web/users.php +++ b/routes/web/users.php @@ -1,17 +1,50 @@ 'Users\UsersController@postCreate' ]); - Route::post('{userId}/restore', [ 'as' => 'restore/user', 'uses' => 'Users\UsersController@getRestore' ]); - Route::get('{userId}/unsuspend', [ 'as' => 'unsuspend/user', 'uses' => 'Users\UsersController@getUnsuspend' ]); - Route::post('{userId}/upload', [ 'as' => 'upload/user', 'uses' => 'Users\UserFilesController@store' ]); + // User Management + Route::post( + '{userId}/clone', + [ + Users\UsersController::class, + 'postCreate' + ] + ); + + Route::post( + '{userId}/restore', + [ + Users\UsersController::class, + 'getRestore' + ] + )->name('restore/user'); + + Route::get( + '{userId}/unsuspend', + [ + Users\UsersController::class, + 'getUnsuspend' + ] + )->name('unsuspend/user'); + + Route::post( + '{userId}/upload', + [ + Users\UserFilesController::class, + 'store' + ] + )->name('upload/user'); + Route::delete( '{userId}/deletefile/{fileId}', - [ 'as' => 'userfile.destroy', 'uses' => 'Users\UserFilesController@destroy' ] - ); + [ + Users\UserFilesController::class, + 'destroy' + ] + )->name('userfile.destroy'); + Route::group(['prefix' => 'users', 'middleware' => ['auth']], function () { diff --git a/upgrade.php b/upgrade.php index e5d00aea23..09937e6c23 100644 --- a/upgrade.php +++ b/upgrade.php @@ -178,7 +178,7 @@ echo "--------------------------------------------------------\n\n"; // can cause issues with funky caching $unused_files = [ "bootstrap/cache/compiled.php", - "bootsrap/cache/services.php", + "bootstrap/cache/services.php", "bootstrap/cache/config.php", ];