Merge branch 'develop' into snipe-it-17073-asset-requests-are-not-deleted-when-asset-is-deleted
This commit is contained in:
@@ -28,6 +28,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=db
|
||||
DB_SOCKET=null
|
||||
DB_PORT='3306'
|
||||
DB_DATABASE=snipeit
|
||||
DB_USERNAME=snipeit
|
||||
|
||||
@@ -24,6 +24,7 @@ PUBLIC_FILESYSTEM_DISK=local_public
|
||||
# --------------------------------------------
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_SOCKET=null
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=null
|
||||
DB_USERNAME=null
|
||||
|
||||
2
.github/workflows/codacy-analysis.yml
vendored
2
.github/workflows/codacy-analysis.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||
- name: Run Codacy Analysis CLI
|
||||
uses: codacy/codacy-analysis-cli-action@v4.4.5
|
||||
uses: codacy/codacy-analysis-cli-action@v4.4.7
|
||||
with:
|
||||
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
|
||||
# You can also omit the token and run the tools that support default configurations
|
||||
|
||||
@@ -133,8 +133,17 @@ class Handler extends ExceptionHandler
|
||||
// This is traaaaash but it handles models that are not found while using route model binding :(
|
||||
// The only alternative is to set that at *each* route, which is crazypants
|
||||
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
|
||||
$ids = method_exists($e, 'getIds') ? $e->getIds() : [];
|
||||
|
||||
// This gets the MVC model name from the exception and formats in a way that's less fugly
|
||||
if (in_array('bulkedit', $ids, true)) {
|
||||
$error_array = session()->get('bulk_asset_errors');
|
||||
return redirect()
|
||||
->route('hardware.bulkedit')
|
||||
->withErrors($error_array, 'bulk_asset_errors')
|
||||
->withInput();
|
||||
}
|
||||
|
||||
// This gets the MVC model name from the exception and formats in a way that's less fugly
|
||||
$model_name = strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel())))));
|
||||
$route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
|
||||
|
||||
|
||||
@@ -27,6 +27,35 @@ class StorageHelper
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMediaType($file_with_path) {
|
||||
|
||||
// The file exists and is allowed to be displayed inline
|
||||
if (Storage::exists($file_with_path)) {
|
||||
$fileinfo = pathinfo($file_with_path);
|
||||
$extension = strtolower($fileinfo['extension']);
|
||||
switch ($extension) {
|
||||
case 'jpg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'svg':
|
||||
case 'webp':
|
||||
return 'image';
|
||||
case 'pdf':
|
||||
return 'pdf';
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'ogg':
|
||||
return 'audio';
|
||||
case 'mp4':
|
||||
case 'webm':
|
||||
case 'mov':
|
||||
return 'video';
|
||||
default:
|
||||
return $extension; // Default for unknown types
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This determines the file types that should be allowed inline and checks their fileinfo extension
|
||||
@@ -52,7 +81,6 @@ class StorageHelper
|
||||
'pdf',
|
||||
'png',
|
||||
'svg',
|
||||
'svg',
|
||||
'wav',
|
||||
'webm',
|
||||
'webp',
|
||||
|
||||
@@ -154,7 +154,7 @@ class AssetModelsController extends Controller
|
||||
$assetmodel = $request->handleImages($assetmodel);
|
||||
|
||||
if ($assetmodel->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.create.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.create.success')));
|
||||
}
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
|
||||
|
||||
@@ -207,7 +207,7 @@ class AssetModelsController extends Controller
|
||||
$assetmodel = AssetModel::findOrFail($id);
|
||||
$assetmodel->fill($request->all());
|
||||
$assetmodel = $request->handleImages($assetmodel);
|
||||
|
||||
|
||||
/**
|
||||
* Allow custom_fieldset_id to override and populate fieldset_id.
|
||||
* This is stupid, but required for legacy API support.
|
||||
@@ -222,7 +222,7 @@ class AssetModelsController extends Controller
|
||||
|
||||
|
||||
if ($assetmodel->save()) {
|
||||
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success')));
|
||||
return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.update.success')));
|
||||
}
|
||||
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
|
||||
|
||||
@@ -93,19 +93,27 @@ class UploadedFilesController extends Controller
|
||||
'id',
|
||||
'filename',
|
||||
'action_type',
|
||||
'action_date',
|
||||
'note',
|
||||
'created_at',
|
||||
];
|
||||
|
||||
$uploads = $object->uploads();
|
||||
$offset = ($request->input('offset') > $object->count()) ? $object->count() : abs($request->input('offset'));
|
||||
|
||||
$uploads = Actionlog::select('action_logs.*')
|
||||
->whereNotNull('filename')
|
||||
->where('item_type', self::$map_object_type[$object_type])
|
||||
->where('item_id', $object->id)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->with('adminuser');
|
||||
|
||||
$offset = ($request->input('offset') > $uploads->count()) ? $uploads->count() : abs($request->input('offset'));
|
||||
$limit = app('api_limit_value');
|
||||
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'action_logs.created_at';
|
||||
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
|
||||
|
||||
// Text search on action_logs fields
|
||||
// We could use the normal Actionlogs text scope, but it's a very heavy query since it's searcghing across all relations
|
||||
// And we generally won't need that here
|
||||
// We could use the normal Actionlogs text scope, but it's a very heavy query since it's searching across all relations
|
||||
// and we generally won't need that here
|
||||
if ($request->filled('search')) {
|
||||
|
||||
$uploads->where(
|
||||
@@ -116,8 +124,10 @@ class UploadedFilesController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
$total = $uploads->count();
|
||||
$uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get();
|
||||
return (new UploadedFilesTransformer())->transformFiles($uploads, $uploads->count());
|
||||
|
||||
return (new UploadedFilesTransformer())->transformFiles($uploads, $total);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ use App\Notifications\CurrentInventory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -81,7 +82,12 @@ class UsersController extends Controller
|
||||
'users.autoassign_licenses',
|
||||
'users.website',
|
||||
|
||||
])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations', 'eulas')
|
||||
])->with('manager')
|
||||
->with('groups')
|
||||
->with('userloc')
|
||||
->with('company')
|
||||
->with('department')
|
||||
->with('createdBy')
|
||||
->withCount([
|
||||
'assets as assets_count' => function(Builder $query) {
|
||||
$query->withoutTrashed();
|
||||
@@ -102,6 +108,14 @@ class UsersController extends Controller
|
||||
$users = $users->where('users.activated', '=', $request->input('activated'));
|
||||
}
|
||||
|
||||
if ($request->input('admins') == 'true') {
|
||||
$users = $users->OnlyAdminsAndSuperAdmins();
|
||||
}
|
||||
|
||||
if ($request->input('superadmins') == 'true') {
|
||||
$users = $users->OnlySuperAdmins();
|
||||
}
|
||||
|
||||
if ($request->filled('company_id')) {
|
||||
$users = $users->where('users.company_id', '=', $request->input('company_id'));
|
||||
}
|
||||
@@ -475,8 +489,25 @@ class UsersController extends Controller
|
||||
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager'));
|
||||
}
|
||||
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
// check for permissions related fields and pull them out if the current user cannot edit them
|
||||
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
|
||||
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
if ($request->filled('username')) {
|
||||
$user->username = $request->input('username');
|
||||
}
|
||||
|
||||
if ($request->filled('email')) {
|
||||
$user->email = $request->input('email');
|
||||
}
|
||||
|
||||
if ($request->filled('activated')) {
|
||||
$user->activated = $request->input('activated');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// We need to use has() instead of filled()
|
||||
|
||||
@@ -161,6 +161,7 @@ class BulkAssetsController extends Controller
|
||||
|
||||
$models = $assets->unique('model_id');
|
||||
$modelNames = [];
|
||||
|
||||
foreach($models as $model) {
|
||||
$modelNames[] = $model->model->name;
|
||||
}
|
||||
@@ -196,7 +197,6 @@ class BulkAssetsController extends Controller
|
||||
|
||||
case 'edit':
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
return view('hardware/bulk')
|
||||
->with('assets', $asset_ids)
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
@@ -224,11 +224,8 @@ class BulkAssetsController extends Controller
|
||||
$error_array = array();
|
||||
|
||||
// Get the back url from the session and then destroy the session
|
||||
$bulk_back_url = route('hardware.index');
|
||||
|
||||
if ($request->session()->has('bulk_back_url')) {
|
||||
$bulk_back_url = $request->session()->pull('bulk_back_url');
|
||||
}
|
||||
$bulk_back_url = $request->session()->pull('bulk_back_url', url()->previous());
|
||||
|
||||
$custom_field_columns = CustomField::all()->pluck('db_column')->toArray();
|
||||
|
||||
@@ -543,7 +540,13 @@ class BulkAssetsController extends Controller
|
||||
} // end asset foreach
|
||||
|
||||
if ($has_errors > 0) {
|
||||
return redirect($bulk_back_url)->with('bulk_asset_errors', $error_array);
|
||||
session()->put('bulkedit_ids', $request->input('ids'));
|
||||
session()->put('bulk_asset_errors',$error_array);
|
||||
|
||||
return redirect()
|
||||
->route('hardware.bulkedit')
|
||||
->with('bulk_asset_errors', $error_array)
|
||||
->withInput();
|
||||
}
|
||||
|
||||
return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.update.success'));
|
||||
@@ -735,4 +738,33 @@ class BulkAssetsController extends Controller
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function bulkEditForm(): View|RedirectResponse
|
||||
{
|
||||
$this->authorize('update', Asset::class);
|
||||
|
||||
$asset_ids = session()->pull('bulkedit_ids', []);
|
||||
|
||||
if (empty($asset_ids)) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.update.no_assets_selected'));
|
||||
}
|
||||
|
||||
$assets = Asset::with('model')->withTrashed()->whereIn('id', $asset_ids)->get();
|
||||
|
||||
if ($assets->isEmpty()) {
|
||||
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.update.assets_do_not_exist_or_are_invalid'));
|
||||
}
|
||||
|
||||
$models = $assets->unique('model_id');
|
||||
$modelNames = [];
|
||||
foreach ($models as $model) {
|
||||
$modelNames[] = $model->model->name;
|
||||
}
|
||||
|
||||
return view('hardware/bulk')
|
||||
->with('assets', $asset_ids)
|
||||
->with('statuslabel_list', Helper::statusLabelList())
|
||||
->with('models', $models->pluck(['model']))
|
||||
->with('modelNames', $modelNames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,13 +304,16 @@ class LicensesController extends Controller
|
||||
$response = new StreamedResponse(function () {
|
||||
// Open output stream
|
||||
$handle = fopen('php://output', 'w');
|
||||
$licenses= License::with('company',
|
||||
$licenses = License::with('company',
|
||||
'manufacturer',
|
||||
'category',
|
||||
'supplier',
|
||||
'adminuser',
|
||||
'assignedusers')
|
||||
->orderBy('created_at', 'DESC');
|
||||
'assignedusers');
|
||||
if (request()->filled('category_id')) {
|
||||
$licenses = $licenses->where('category_id', request()->input('category_id'));
|
||||
}
|
||||
$licenses = $licenses->orderBy('created_at', 'DESC');
|
||||
Company::scopeCompanyables($licenses)
|
||||
->chunk(500, function ($licenses) use ($handle) {
|
||||
$headers = [
|
||||
|
||||
@@ -51,7 +51,7 @@ class ManufacturersController extends Controller
|
||||
$manufacturers_count = Manufacturer::withTrashed()->count();
|
||||
|
||||
if ($manufacturers_count == 0) {
|
||||
Artisan::call('db:seed', ['--class' => 'ManufacturerSeeder']);
|
||||
Artisan::call('db:seed', ['--class' => 'Database\\Seeders\\ManufacturerSeeder', '--force' => true]);
|
||||
return redirect()->route('manufacturers.index')->with('success', trans('general.seeding.manufacturers.success'));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,32 +25,22 @@ class UserFilesController extends Controller
|
||||
public function store(UploadFileRequest $request, User $user)
|
||||
{
|
||||
$this->authorize('update', $user);
|
||||
$files = $request->file('file');
|
||||
|
||||
if (is_null($files)) {
|
||||
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file);
|
||||
|
||||
//Log the uploaded file to the log
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_id = $user->id;
|
||||
$logAction->item_type = User::class;
|
||||
$logAction->created_by = auth()->id();
|
||||
$logAction->note = $request->input('notes');
|
||||
$logAction->target_id = null;
|
||||
$logAction->created_at = date("Y-m-d H:i:s");
|
||||
$logAction->filename = $file_name;
|
||||
$logAction->action_type = 'uploaded';
|
||||
|
||||
if (! $logAction->save()) {
|
||||
return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500);
|
||||
if ($request->hasFile('file')) {
|
||||
if (! Storage::exists('private_uploads/users')) {
|
||||
Storage::makeDirectory('private_uploads/users', 775);
|
||||
}
|
||||
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
|
||||
foreach ($request->file('file') as $file) {
|
||||
$file_name = $request->handleFile('private_uploads/users/','user-'.$user->id, $file);
|
||||
$user->logUpload($file_name, $request->get('notes'));
|
||||
}
|
||||
|
||||
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,8 @@ use App\Models\Company;
|
||||
use App\Models\Group;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Redirect;
|
||||
use Str;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use App\Notifications\CurrentInventory;
|
||||
|
||||
@@ -129,7 +123,7 @@ class UsersController extends Controller
|
||||
}
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// we have to invoke the
|
||||
// we have to invoke the form request here to handle image uploads
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
session()->put(['redirect_option' => $request->get('redirect_option')]);
|
||||
@@ -235,20 +229,14 @@ class UsersController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (auth()->user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
|
||||
// Update the user fields
|
||||
$user->username = trim($request->input('username'));
|
||||
$user->email = trim($request->input('email'));
|
||||
|
||||
$user->first_name = $request->input('first_name');
|
||||
$user->last_name = $request->input('last_name');
|
||||
$user->two_factor_optin = $request->input('two_factor_optin') ?: 0;
|
||||
$user->locale = $request->input('locale');
|
||||
$user->employee_num = $request->input('employee_num');
|
||||
$user->activated = $request->input('activated', 0);
|
||||
$user->jobtitle = $request->input('jobtitle', null);
|
||||
$user->phone = $request->input('phone');
|
||||
$user->location_id = $request->input('location_id', null);
|
||||
@@ -260,8 +248,6 @@ class UsersController extends Controller
|
||||
$user->city = $request->input('city', null);
|
||||
$user->state = $request->input('state', null);
|
||||
$user->country = $request->input('country', null);
|
||||
// if a user is editing themselves we should always keep activated true
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
$user->zip = $request->input('zip', null);
|
||||
$user->remote = $request->input('remote', 0);
|
||||
$user->vip = $request->input('vip', 0);
|
||||
@@ -270,30 +256,49 @@ class UsersController extends Controller
|
||||
$user->end_date = $request->input('end_date', null);
|
||||
$user->autoassign_licenses = $request->input('autoassign_licenses', 0);
|
||||
|
||||
// Set this here so that we can overwrite it later if the user is an admin or superadmin
|
||||
$user->activated = $request->input('activated', auth()->user()->is($user) ? 1 : $user->activated);
|
||||
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $request->input('location_id', null)]);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
// check for permissions related fields and only set them if the user has permission to edit them
|
||||
if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) {
|
||||
|
||||
$user->username = trim($request->input('username'));
|
||||
$user->email = trim($request->input('email'));
|
||||
$user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0);
|
||||
|
||||
// Do we want to update the user password?
|
||||
if ($request->filled('password')) {
|
||||
$user->password = bcrypt($request->input('password'));
|
||||
}
|
||||
|
||||
$permissions_array = $request->input('permission');
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
if (! auth()->user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
$permissions_array['superuser'] = $orig_superuser;
|
||||
}
|
||||
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Only save groups if the user is a superuser
|
||||
if (auth()->user()->isSuperUser()) {
|
||||
$user->groups()->sync($request->input('groups'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
Asset::where('assigned_type', User::class)
|
||||
->where('assigned_to', $user->id)
|
||||
->update(['location_id' => $user->location_id]);
|
||||
|
||||
$permissions_array = $request->input('permission');
|
||||
|
||||
// Strip out the superuser permission if the user isn't a superadmin
|
||||
if (! auth()->user()->isSuperUser()) {
|
||||
unset($permissions_array['superuser']);
|
||||
$permissions_array['superuser'] = $orig_superuser;
|
||||
}
|
||||
|
||||
$user->permissions = json_encode($permissions_array);
|
||||
|
||||
// Handle uploaded avatar
|
||||
app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar');
|
||||
|
||||
@@ -80,9 +80,20 @@ class UploadFileRequest extends Request
|
||||
{
|
||||
$attributes = [];
|
||||
|
||||
if ($this->file) {
|
||||
if (($this->file) && (is_array($this->file))) {
|
||||
|
||||
for ($i = 0; $i < count($this->file); $i++) {
|
||||
$attributes['file.'.$i] = $this->file[$i]->getClientOriginalName();
|
||||
|
||||
try {
|
||||
|
||||
if ($this->file[$i]) {
|
||||
$attributes['file.'.$i] = $this->file[$i]->getClientOriginalName();
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$attributes['file.'.$i] = 'Invalid file';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace App\Http\Transformers;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Helpers\StorageHelper;
|
||||
use App\Models\Actionlog;
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
@@ -16,6 +17,7 @@ use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ActionlogsTransformer
|
||||
{
|
||||
@@ -133,24 +135,6 @@ class ActionlogsTransformer
|
||||
$clean_meta= $this->changedInfo($clean_meta);
|
||||
}
|
||||
|
||||
$file_url = '';
|
||||
if($actionlog->filename!='') {
|
||||
if ($actionlog->action_type == 'accepted') {
|
||||
$file_url = route('log.storedeula.download', ['filename' => $actionlog->filename]);
|
||||
} else {
|
||||
if ($actionlog->item) {
|
||||
if ($actionlog->itemType() == 'asset') {
|
||||
$file_url = route('show/assetfile', ['asset' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'accessory') {
|
||||
$file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'license') {
|
||||
$file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
} elseif ($actionlog->itemType() == 'user') {
|
||||
$file_url = route('show/userfile', ['user' => $actionlog->item->id, 'fileId' => $actionlog->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$array = [
|
||||
'id' => (int) $actionlog->id,
|
||||
@@ -158,8 +142,10 @@ class ActionlogsTransformer
|
||||
'file' => ($actionlog->filename!='')
|
||||
?
|
||||
[
|
||||
'url' => $file_url,
|
||||
'url' => $actionlog->uploads_file_url(),
|
||||
'filename' => $actionlog->filename,
|
||||
'inlineable' => StorageHelper::allowSafeInline($actionlog->uploads_file_url()),
|
||||
'exists_on_disk' => Storage::exists($actionlog->uploads_file_path()) ? true : false,
|
||||
] : null,
|
||||
|
||||
'item' => ($actionlog->item) ? [
|
||||
|
||||
@@ -80,7 +80,6 @@ class AssetsTransformer
|
||||
'qr' => ($setting->qr_code=='1') ? config('app.url').'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png' : null,
|
||||
'alt_barcode' => ($setting->alt_barcode_enabled=='1') ? config('app.url').'/uploads/barcodes/'.str_slug($setting->alt_barcode).'-'.str_slug($asset->asset_tag).'.png' : null,
|
||||
'assigned_to' => $this->transformAssignedTo($asset),
|
||||
'jobtitle' => $asset->assigned ? e($asset->assigned->jobtitle) : null,
|
||||
'warranty_months' => ($asset->warranty_months > 0) ? e($asset->warranty_months.' '.trans('admin/hardware/form.months')) : null,
|
||||
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
|
||||
'created_by' => ($asset->adminuser) ? [
|
||||
@@ -204,6 +203,7 @@ class AssetsTransformer
|
||||
'last_name'=> ($asset->assigned->last_name) ? e($asset->assigned->last_name) : null,
|
||||
'email'=> ($asset->assigned->email) ? e($asset->assigned->email) : null,
|
||||
'employee_number' => ($asset->assigned->employee_num) ? e($asset->assigned->employee_num) : null,
|
||||
'jobtitle' => $asset->assigned->jobtitle ? e($asset->assigned->jobtitle) : null,
|
||||
'type' => 'user',
|
||||
] : null;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class LicensesTransformer
|
||||
'checkin' => Gate::allows('checkin', License::class),
|
||||
'clone' => Gate::allows('create', License::class),
|
||||
'update' => Gate::allows('update', License::class),
|
||||
'delete' => (Gate::allows('delete', License::class) && ($license->free_seats_count > 0)) ? true : false,
|
||||
'delete' => (Gate::allows('delete', License::class) && ($license->free_seats_count == $license->seats)) ? true : false,
|
||||
];
|
||||
|
||||
$array += $permissions_array;
|
||||
|
||||
@@ -32,10 +32,11 @@ class UploadedFilesTransformer
|
||||
'name' => e($file->filename),
|
||||
'item' => ($file->item_type) ? [
|
||||
'id' => (int) $file->item_id,
|
||||
'type' => strtolower(class_basename($file->item_type)),
|
||||
'type' => str_plural(strtolower(class_basename($file->item_type))),
|
||||
] : null,
|
||||
'filename' => e($file->filename),
|
||||
'filetype' => StorageHelper::getFiletype($file->uploads_file_path()),
|
||||
'mediatype' => StorageHelper::getMediaType($file->uploads_file_path()),
|
||||
'url' => $file->uploads_file_url(),
|
||||
'note' => ($file->note) ? e($file->note) : null,
|
||||
'created_by' => ($file->adminuser) ? [
|
||||
@@ -44,7 +45,7 @@ class UploadedFilesTransformer
|
||||
] : null,
|
||||
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
|
||||
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),
|
||||
'inline' => StorageHelper::allowSafeInline($file->uploads_file_path()),
|
||||
'inlineable' => StorageHelper::allowSafeInline($file->uploads_file_path()),
|
||||
'exists_on_disk' => (Storage::exists($file->uploads_file_path()) ? true : false),
|
||||
];
|
||||
|
||||
|
||||
@@ -22,6 +22,12 @@ class UsersTransformer
|
||||
public function transformUser(User $user)
|
||||
{
|
||||
|
||||
$role = '';
|
||||
if ($user->isSuperUser()) {
|
||||
$role = 'superadmin';
|
||||
} elseif ($user->isAdmin()) {
|
||||
$role = 'admin';
|
||||
}
|
||||
$array = [
|
||||
'id' => (int) $user->id,
|
||||
'avatar' => e($user->present()->gravatar) ?? null,
|
||||
@@ -59,6 +65,7 @@ class UsersTransformer
|
||||
'name'=> e($user->userloc->name),
|
||||
] : null,
|
||||
'notes'=> Helper::parseEscapedMarkedownInline($user->notes),
|
||||
'role' => $role,
|
||||
'permissions' => $user->decodePermissions(),
|
||||
'activated' => ($user->activated == '1') ? true : false,
|
||||
'autoassign_licenses' => ($user->autoassign_licenses == '1') ? true : false,
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\Department;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Notifications\WelcomeNotification;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
|
||||
@@ -81,6 +82,7 @@ class UserImporter extends ItemImporter
|
||||
$this->item['username'] = $user_formatted_array['username'];
|
||||
}
|
||||
|
||||
|
||||
// Check if a numeric ID was passed. If it does, use that above all else.
|
||||
if ((array_key_exists('id', $this->item) && ($this->item['id'] != "") && (is_numeric($this->item['id'])))) {
|
||||
$user = User::find($this->item['id']);
|
||||
@@ -90,12 +92,25 @@ class UserImporter extends ItemImporter
|
||||
|
||||
if ($user) {
|
||||
|
||||
// If the user does not want to update existing values, only add new ones, bail out
|
||||
if (! $this->updating) {
|
||||
Log::debug('A matching User '.$this->item['name'].' already exists. ');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->log('Updating User');
|
||||
|
||||
// Todo - check that this works
|
||||
if (!Gate::allows('canEditAuthFields', $user)) {
|
||||
unset($user->username);
|
||||
unset($user->email);
|
||||
unset($user->password);
|
||||
unset($user->activated);
|
||||
}
|
||||
|
||||
$user->update($this->sanitizeItemForUpdating($user));
|
||||
|
||||
// Why do we have to do this twice? Update should
|
||||
$user->save();
|
||||
|
||||
// Update the location of any assets checked out to this user
|
||||
@@ -116,8 +131,12 @@ class UserImporter extends ItemImporter
|
||||
$this->log('No matching user, creating one');
|
||||
$user = new User();
|
||||
$user->created_by = auth()->id();
|
||||
|
||||
$user->fill($this->sanitizeItemForStoring($user));
|
||||
|
||||
// TODO - check for gate here I guess
|
||||
|
||||
|
||||
if ($user->save()) {
|
||||
$this->log('User '.$this->item['name'].' was created');
|
||||
|
||||
@@ -143,6 +162,7 @@ class UserImporter extends ItemImporter
|
||||
$this->logError($user, 'User');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch an existing department, or create new if it doesn't exist
|
||||
*
|
||||
|
||||
@@ -4,10 +4,12 @@ namespace App\Listeners;
|
||||
|
||||
use App\Events\CheckoutableCheckedOut;
|
||||
use App\Mail\CheckinAccessoryMail;
|
||||
use App\Mail\CheckinComponentMail;
|
||||
use App\Mail\CheckinLicenseMail;
|
||||
use App\Mail\CheckoutAccessoryMail;
|
||||
use App\Mail\CheckoutAssetMail;
|
||||
use App\Mail\CheckinAssetMail;
|
||||
use App\Mail\CheckoutComponentMail;
|
||||
use App\Mail\CheckoutConsumableMail;
|
||||
use App\Mail\CheckoutLicenseMail;
|
||||
use App\Models\Accessory;
|
||||
@@ -22,9 +24,11 @@ use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Notifications\CheckinAccessoryNotification;
|
||||
use App\Notifications\CheckinAssetNotification;
|
||||
use App\Notifications\CheckinComponentNotification;
|
||||
use App\Notifications\CheckinLicenseSeatNotification;
|
||||
use App\Notifications\CheckoutAccessoryNotification;
|
||||
use App\Notifications\CheckoutAssetNotification;
|
||||
use App\Notifications\CheckoutComponentNotification;
|
||||
use App\Notifications\CheckoutConsumableNotification;
|
||||
use App\Notifications\CheckoutLicenseSeatNotification;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
@@ -39,7 +43,7 @@ use Osama\LaravelTeamsNotification\TeamsNotification;
|
||||
class CheckoutableListener
|
||||
{
|
||||
private array $skipNotificationsFor = [
|
||||
Component::class,
|
||||
// Component::class,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -145,7 +149,6 @@ class CheckoutableListener
|
||||
$shouldSendEmailToUser = $this->checkoutableCategoryShouldSendEmail($event->checkoutable);
|
||||
$shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress();
|
||||
$shouldSendWebhookNotification = $this->shouldSendWebhookNotification();
|
||||
|
||||
if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) {
|
||||
return;
|
||||
}
|
||||
@@ -269,6 +272,9 @@ class CheckoutableListener
|
||||
case LicenseSeat::class:
|
||||
$notificationClass = CheckinLicenseSeatNotification::class;
|
||||
break;
|
||||
case Component::class:
|
||||
$notificationClass = CheckinComponentNotification::class;
|
||||
break;
|
||||
}
|
||||
|
||||
Log::debug('Notification class: '.$notificationClass);
|
||||
@@ -299,6 +305,9 @@ class CheckoutableListener
|
||||
case LicenseSeat::class:
|
||||
$notificationClass = CheckoutLicenseSeatNotification::class;
|
||||
break;
|
||||
case Component::class:
|
||||
$notificationClass = CheckoutComponentNotification::class;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -310,6 +319,7 @@ class CheckoutableListener
|
||||
Asset::class => CheckoutAssetMail::class,
|
||||
LicenseSeat::class => CheckoutLicenseMail::class,
|
||||
Consumable::class => CheckoutConsumableMail::class,
|
||||
Component::class => CheckoutComponentMail::class,
|
||||
];
|
||||
$mailable= $lookup[get_class($event->checkoutable)];
|
||||
|
||||
@@ -322,8 +332,8 @@ class CheckoutableListener
|
||||
Accessory::class => CheckinAccessoryMail::class,
|
||||
Asset::class => CheckinAssetMail::class,
|
||||
LicenseSeat::class => CheckinLicenseMail::class,
|
||||
Component::class => CheckinComponentMail::class,
|
||||
];
|
||||
|
||||
$mailable= $lookup[get_class($event->checkoutable)];
|
||||
|
||||
return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
|
||||
@@ -469,7 +479,8 @@ class CheckoutableListener
|
||||
return match (true) {
|
||||
$checkoutable instanceof Asset => $checkoutable->model->category,
|
||||
$checkoutable instanceof Accessory,
|
||||
$checkoutable instanceof Consumable => $checkoutable->category,
|
||||
$checkoutable instanceof Consumable,
|
||||
$checkoutable instanceof Component => $checkoutable->category,
|
||||
$checkoutable instanceof LicenseSeat => $checkoutable->license->category,
|
||||
};
|
||||
}
|
||||
|
||||
71
app/Mail/CheckinComponentMail.php
Normal file
71
app/Mail/CheckinComponentMail.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Accessory;
|
||||
use App\Models\Component;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckinComponentMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(Component $component, $checkedOutTo, User $checkedInby, $note)
|
||||
{
|
||||
$this->item = $component;
|
||||
$this->target = $checkedOutTo;
|
||||
$this->admin = $checkedInby;
|
||||
$this->note = $note;
|
||||
$this->settings = Setting::getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||
|
||||
return new Envelope(
|
||||
from: $from,
|
||||
subject: trans('mail.Confirm_component_checkin'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.markdown.checkin-component',
|
||||
with: [
|
||||
'item' => $this->item,
|
||||
'admin' => $this->admin,
|
||||
'note' => $this->note,
|
||||
'target' => $this->target,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
82
app/Mail/CheckoutComponentMail.php
Normal file
82
app/Mail/CheckoutComponentMail.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Component;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckoutComponentMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(Component $component, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
|
||||
{
|
||||
$this->item = $component;
|
||||
$this->admin = $checkedOutBy;
|
||||
$this->note = $note;
|
||||
$this->target = $checkedOutTo;
|
||||
$this->acceptance = $acceptance;
|
||||
$this->qty = $component->assets->first()?->pivot?->assigned_qty;
|
||||
|
||||
$this->settings = Setting::getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
$from = new Address(config('mail.from.address'), config('mail.from.name'));
|
||||
|
||||
return new Envelope(
|
||||
from: $from,
|
||||
subject: trans('mail.Confirm_component_delivery'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
|
||||
$eula = $this->item->getEula();
|
||||
$req_accept = $this->item->requireAcceptance();
|
||||
|
||||
$accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance);
|
||||
|
||||
return new Content(
|
||||
markdown: 'mail.markdown.checkout-component',
|
||||
with: [
|
||||
'item' => $this->item,
|
||||
'admin' => $this->admin,
|
||||
'note' => $this->note,
|
||||
'target' => $this->target,
|
||||
'eula' => $eula,
|
||||
'req_accept' => $req_accept,
|
||||
'accept_url' => $accept_url,
|
||||
'qty' => $this->qty,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,8 @@ class Actionlog extends SnipeModel
|
||||
'assets' => ['asset_tag','name', 'serial', 'order_number', 'notes', 'purchase_date'],
|
||||
'assets.model' => ['name', 'model_number', 'eol', 'notes'],
|
||||
'assets.model.category' => ['name', 'notes'],
|
||||
'assets.location' => ['name'],
|
||||
'assets.defaultLoc' => ['name'],
|
||||
'assets.model.manufacturer' => ['name', 'notes'],
|
||||
'licenses' => ['name', 'serial', 'notes', 'order_number', 'license_email', 'license_name', 'purchase_order', 'purchase_date'],
|
||||
'licenses.category' => ['name', 'notes'],
|
||||
@@ -455,6 +457,11 @@ class Actionlog extends SnipeModel
|
||||
public function uploads_file_url()
|
||||
{
|
||||
|
||||
|
||||
if (($this->action_type == 'accepted') || ($this->action_type == 'declined')) {
|
||||
return route('log.storedeula.download', ['filename' => $this->filename]);
|
||||
}
|
||||
|
||||
switch ($this->item_type) {
|
||||
case Accessory::class:
|
||||
return route('show.accessoryfile', [$this->item_id, $this->id]);
|
||||
@@ -463,7 +470,7 @@ class Actionlog extends SnipeModel
|
||||
case AssetModel::class:
|
||||
return route('show/modelfile', [$this->item_id, $this->id]);
|
||||
case Consumable::class:
|
||||
return route('show/locationsfile', [$this->item_id, $this->id]);
|
||||
return route('show.consumablefile', [$this->item_id, $this->id]);
|
||||
case Component::class:
|
||||
return route('show.componentfile', [$this->item_id, $this->id]);
|
||||
case License::class:
|
||||
@@ -480,6 +487,10 @@ class Actionlog extends SnipeModel
|
||||
public function uploads_file_path()
|
||||
{
|
||||
|
||||
if (($this->action_type == 'accepted') || ($this->action_type == 'declined')) {
|
||||
return 'private_uploads/eula-pdfs/'.$this->filename;
|
||||
}
|
||||
|
||||
switch ($this->item_type) {
|
||||
case Accessory::class:
|
||||
return 'private_uploads/accessories/'.$this->filename;
|
||||
|
||||
@@ -15,6 +15,8 @@ use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -432,12 +434,34 @@ class Asset extends Depreciable
|
||||
{
|
||||
// Check to see if any of the custom fields were included on the form and if they have any values
|
||||
if (($this->model) && ($this->model->fieldset) && ($this->model->fieldset->fields)) {
|
||||
|
||||
foreach ($this->model->fieldset->fields as $field) {
|
||||
|
||||
if (($field->{$checkin_checkout} == 1) && (request()->has($field->db_column))) {
|
||||
$this->{$field->db_column} = request()->get($field->db_column);
|
||||
|
||||
if ($field->field_encrypted == '1') {
|
||||
|
||||
if (Gate::allows('assets.view.encrypted_custom_fields')) {
|
||||
if (is_array(request()->input($field->db_column))) {
|
||||
$this->{$field->db_column} = Crypt::encrypt(implode(', ', request()->input($field->db_column)));
|
||||
} else {
|
||||
$this->{$field->db_column} = Crypt::encrypt(request()->get($field->db_column));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (is_array(request()->input($field->db_column))) {
|
||||
$this->{$field->db_column} = implode(', ', request()->input($field->db_column));
|
||||
} else {
|
||||
$this->{$field->db_column} = request()->input($field->db_column);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\HasUploads;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
/**
|
||||
@@ -203,6 +205,36 @@ class Component extends SnipeModel
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id');
|
||||
}
|
||||
/**
|
||||
* Determine whether this asset requires acceptance by the assigned user
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function requireAcceptance()
|
||||
{
|
||||
return $this->category->require_acceptance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
||||
* checks for a settings level EULA
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return string | false
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
if ($this->category->eula_text) {
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> action logs relationship
|
||||
@@ -248,6 +280,19 @@ class Component extends SnipeModel
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to send a checkin/checkout email based on
|
||||
* asset model category
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function checkin_email()
|
||||
{
|
||||
return $this->category?->checkin_email;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check how many items within a component are remaining
|
||||
|
||||
@@ -214,11 +214,19 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
{
|
||||
$user_groups = $this->groups;
|
||||
if (($this->permissions == '') && (count($user_groups) == 0)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_permissions = json_decode($this->permissions, true);
|
||||
$user_permissions = $this->permissions;
|
||||
|
||||
if (is_object($this->permissions)) {
|
||||
$user_permissions = json_decode(json_encode($this->permissions), true);
|
||||
}
|
||||
|
||||
if (is_string($this->permissions)) {
|
||||
$user_permissions = json_decode($this->permissions, true);
|
||||
}
|
||||
|
||||
|
||||
$is_user_section_permissions_set = ($user_permissions != '') && array_key_exists($section, $user_permissions);
|
||||
//If the user is explicitly granted, return true
|
||||
@@ -272,6 +280,18 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
return $this->checkPermissionSection('superuser');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is an admin
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v8.1.18]
|
||||
* @return bool
|
||||
*/
|
||||
public function isAdmin()
|
||||
{
|
||||
return $this->checkPermissionSection('admin');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the user can edit their own profile
|
||||
@@ -299,13 +319,15 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets->count() === 0)
|
||||
&& ($this->licenses->count() === 0)
|
||||
&& ($this->consumables->count() === 0)
|
||||
&& ($this->accessories->count() === 0)
|
||||
&& ($this->managedLocations->count() === 0)
|
||||
&& ($this->managesUsers->count() === 0)
|
||||
&& (($this->assets_count ?? $this->assets()->count()) === 0)
|
||||
&& (($this->accessories_count ?? $this->accessories()->count()) === 0)
|
||||
&& (($this->licenses_count ?? $this->licenses()->count()) === 0)
|
||||
&& (($this->consumables_count ?? $this->consumables()->count()) === 0)
|
||||
&& (($this->accessories_count ?? $this->accessories()->count()) === 0)
|
||||
&& (($this->manages_users_count ?? $this->managesUsers()->count()) === 0)
|
||||
&& (($this->manages_locations_count ?? $this->managedLocations()->count()) === 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
@@ -880,6 +902,49 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only admins and superusers
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
*/
|
||||
public function scopeOnlySuperAdmins($query)
|
||||
{
|
||||
|
||||
return $query->where('users.permissions', 'LIKE', '%"superuser":"1"%')
|
||||
->orWhere('users.permissions', 'LIKE', '%"superuser":1%')
|
||||
->orWhereHas(
|
||||
'groups', function ($query) {
|
||||
$query->where('permission_groups.permissions', 'LIKE', '%"superuser":"1"%')
|
||||
->orWhere('permission_groups.permissions', 'LIKE', '%"superuser":1%');
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only admins and superusers
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
*/
|
||||
public function scopeOnlyAdminsAndSuperAdmins($query)
|
||||
{
|
||||
|
||||
return $query->where('users.permissions', 'LIKE', '%"superuser":"1"%')
|
||||
->orWhere('users.permissions', 'LIKE', '%"superuser":1%')
|
||||
->orWhere('users.permissions', 'LIKE', '%"admin":1%')
|
||||
->orWhere('users.permissions', 'LIKE', '%"admin":"1"%')
|
||||
->orWhereHas(
|
||||
'groups', function ($query) {
|
||||
$query->where('permission_groups.permissions', 'LIKE', '%"superuser":"1"%')
|
||||
->orWhere('permission_groups.permissions', 'LIKE', '%"superuser":1%')
|
||||
->orWhere('permission_groups.permissions', 'LIKE', '%"admin":1%')
|
||||
->orWhere('permission_groups.permissions', 'LIKE', '%"admin":"1"%');
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope to order on manager
|
||||
@@ -953,7 +1018,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the preferred locale for the user.
|
||||
*
|
||||
@@ -992,7 +1056,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
public function scopeUserLocation($query, $location, $search)
|
||||
{
|
||||
|
||||
|
||||
return $query->where('location_id', '=', $location)
|
||||
->where('users.first_name', 'LIKE', '%' . $search . '%')
|
||||
->orWhere('users.email', 'LIKE', '%' . $search . '%')
|
||||
@@ -1005,9 +1068,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
|
||||
->orWhere('users.username', 'LIKE', '%' . $search . '%')
|
||||
->orwhereRaw('CONCAT(users.first_name," ",users.last_name) LIKE \''.$search.'%\'');
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
165
app/Notifications/CheckinComponentNotification.php
Normal file
165
app/Notifications/CheckinComponentNotification.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Component;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Channels\SlackWebhookChannel;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Str;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
class CheckinComponentNotification extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @param $params
|
||||
*/
|
||||
public function __construct(Component $component, $checkedOutTo, User $checkedInBy, $note)
|
||||
{
|
||||
$this->target = $checkedOutTo;
|
||||
$this->item = $component;
|
||||
$this->admin = $checkedInBy;
|
||||
$this->note = $note;
|
||||
$this->settings = Setting::getSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function via()
|
||||
{
|
||||
$notifyBy = [];
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
$notifyBy[] = SlackWebhookChannel::class;
|
||||
}
|
||||
|
||||
return $notifyBy;
|
||||
}
|
||||
|
||||
public function toSlack()
|
||||
{
|
||||
$target = $this->target;
|
||||
$admin = $this->admin;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
$botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot';
|
||||
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
|
||||
|
||||
if ($admin) {
|
||||
$fields = [
|
||||
trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
|
||||
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
|
||||
];
|
||||
|
||||
if ($item->location) {
|
||||
$fields[trans('general.location')] = $item->location->name;
|
||||
}
|
||||
|
||||
if ($item->company) {
|
||||
$fields[trans('general.company')] = $item->company->name;
|
||||
}
|
||||
|
||||
} else {
|
||||
$fields = [
|
||||
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
|
||||
'By' => 'CLI tool',
|
||||
];
|
||||
}
|
||||
|
||||
return (new SlackMessage)
|
||||
->content(':arrow_down: :package: '.trans('mail.Component_checkin_notification'))
|
||||
->from($botname)
|
||||
->to($channel)
|
||||
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
|
||||
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
|
||||
->fields($fields)
|
||||
->content($note);
|
||||
});
|
||||
}
|
||||
public function toMicrosoftTeams()
|
||||
{
|
||||
$target = $this->target;
|
||||
$admin = $this->admin;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||
return MicrosoftTeamsMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->type('success')
|
||||
->addStartGroupToSection('activityTitle')
|
||||
->title(trans('mail.Component_checkin_notification'))
|
||||
->addStartGroupToSection('activityText')
|
||||
->fact(htmlspecialchars_decode($item->present()->name), '', 'header')
|
||||
->fact(trans('mail.Component_checkin_notification')." by ", $admin->present()->fullName() ?: 'CLI tool')
|
||||
->fact(trans('mail.checkedin_from'), $target->present()->fullName())
|
||||
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
||||
->fact(trans('mail.notes'), $note ?: '');
|
||||
}
|
||||
|
||||
$message = trans('mail.Component_checkin_notification');
|
||||
$details = [
|
||||
trans('mail.checkedin_from')=> $target->present()->fullName(),
|
||||
trans('mail.Component_checkin_notification')." by " => $admin->present()->fullName() ?: 'CLI tool',
|
||||
trans('admin/consumables/general.remaining') => $item->numRemaining(),
|
||||
trans('mail.notes') => $note ?: '',
|
||||
];
|
||||
|
||||
return array($message, $details);
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$target = $this->target;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.Component_checkin_notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.checkedin_from') ?: '',
|
||||
$target->present()->fullName() ?: '',
|
||||
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
|
||||
)
|
||||
->onClick(route('components.show', $item->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
164
app/Notifications/CheckoutComponentNotification.php
Normal file
164
app/Notifications/CheckoutComponentNotification.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Component;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Notifications\Channels\SlackWebhookChannel;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Str;
|
||||
use NotificationChannels\GoogleChat\Card;
|
||||
use NotificationChannels\GoogleChat\GoogleChatChannel;
|
||||
use NotificationChannels\GoogleChat\GoogleChatMessage;
|
||||
use NotificationChannels\GoogleChat\Section;
|
||||
use NotificationChannels\GoogleChat\Widgets\KeyValue;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel;
|
||||
use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage;
|
||||
|
||||
|
||||
class CheckoutComponentNotification extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @param $params
|
||||
*/
|
||||
public function __construct(Component $component, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
|
||||
{
|
||||
$this->item = $component;
|
||||
$this->admin = $checkedOutBy;
|
||||
$this->note = $note;
|
||||
$this->target = $checkedOutTo;
|
||||
$this->acceptance = $acceptance;
|
||||
$this->qty = $component->checkout_qty;
|
||||
|
||||
$this->settings = Setting::getSettings();
|
||||
}
|
||||
|
||||
/**`
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function via()
|
||||
{
|
||||
$notifyBy = [];
|
||||
if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = GoogleChatChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) {
|
||||
|
||||
$notifyBy[] = MicrosoftTeamsChannel::class;
|
||||
}
|
||||
|
||||
if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) {
|
||||
$notifyBy[] = SlackWebhookChannel::class;
|
||||
}
|
||||
|
||||
return $notifyBy;
|
||||
}
|
||||
|
||||
public function toSlack()
|
||||
{
|
||||
$target = $this->target;
|
||||
$admin = $this->admin;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
$botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot';
|
||||
$channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : '';
|
||||
|
||||
$fields = [
|
||||
trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
|
||||
trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
|
||||
];
|
||||
|
||||
if ($item->location) {
|
||||
$fields[trans('general.location')] = $item->location->name;
|
||||
}
|
||||
|
||||
if ($item->company) {
|
||||
$fields[trans('general.company')] = $item->company->name;
|
||||
}
|
||||
|
||||
return (new SlackMessage)
|
||||
->content(':arrow_up: :package: '.trans('mail.Component_checkout_notification'))
|
||||
->from($botname)
|
||||
->to($channel)
|
||||
->attachment(function ($attachment) use ($item, $note, $admin, $fields) {
|
||||
$attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl())
|
||||
->fields($fields)
|
||||
->content($note);
|
||||
});
|
||||
}
|
||||
public function toMicrosoftTeams()
|
||||
{
|
||||
$target = $this->target;
|
||||
$admin = $this->admin;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) {
|
||||
return MicrosoftTeamsMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->type('success')
|
||||
->addStartGroupToSection('activityTitle')
|
||||
->title(trans('mail.Component_checkout_notification'))
|
||||
->addStartGroupToSection('activityText')
|
||||
->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle')
|
||||
->fact(trans('mail.Component_checkout_notification')." by ", $admin->present()->fullName())
|
||||
->fact(trans('mail.assigned_to'), $target->present()->fullName())
|
||||
->fact(trans('admin/consumables/general.remaining'), $item->numRemaining())
|
||||
->fact(trans('mail.notes'), $note ?: '');
|
||||
}
|
||||
|
||||
$message = trans('mail.Component_checkout_notification');
|
||||
$details = [
|
||||
trans('mail.assigned_to') => $target->present()->fullName(),
|
||||
trans('mail.item') => htmlspecialchars_decode($item->present()->name),
|
||||
trans('mail.Component_checkout_notification').' by' => $admin->present()->fullName(),
|
||||
trans('admin/consumables/general.remaining') => $item->numRemaining(),
|
||||
trans('mail.notes') => $note ?: '',
|
||||
];
|
||||
|
||||
return array($message, $details);
|
||||
}
|
||||
public function toGoogleChat()
|
||||
{
|
||||
$target = $this->target;
|
||||
$item = $this->item;
|
||||
$note = $this->note;
|
||||
|
||||
return GoogleChatMessage::create()
|
||||
->to($this->settings->webhook_endpoint)
|
||||
->card(
|
||||
Card::create()
|
||||
->header(
|
||||
'<strong>'.trans('mail.Component_checkout_notification').'</strong>' ?: '',
|
||||
htmlspecialchars_decode($item->present()->name) ?: '',
|
||||
)
|
||||
->section(
|
||||
Section::create(
|
||||
KeyValue::create(
|
||||
trans('mail.assigned_to') ?: '',
|
||||
$target->present()->fullName() ?: '',
|
||||
trans('admin/consumables/general.remaining').': '.$item->numRemaining(),
|
||||
)
|
||||
->onClick(route('api.assets.show', $target->id))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,7 @@ class AssetPresenter extends Presenter
|
||||
'sortable' => true,
|
||||
'title' => trans('admin/users/table.title'),
|
||||
'visible' => false,
|
||||
'formatter' => 'jobtitleFormatter',
|
||||
], [
|
||||
'field' => 'location',
|
||||
'searchable' => true,
|
||||
|
||||
@@ -106,7 +106,7 @@ class HistoryPresenter extends Presenter
|
||||
'switchable' => true,
|
||||
'title' => trans('general.file_name'),
|
||||
'visible' => true,
|
||||
'formatter' => 'fileUploadNameFormatter',
|
||||
'formatter' => 'fileNameFormatter',
|
||||
],
|
||||
[
|
||||
'field' => 'file_download',
|
||||
@@ -115,7 +115,7 @@ class HistoryPresenter extends Presenter
|
||||
'switchable' => true,
|
||||
'title' => trans('general.download'),
|
||||
'visible' => true,
|
||||
'formatter' => 'fileUploadFormatter',
|
||||
'formatter' => 'fileDownloadButtonsFormatter',
|
||||
],
|
||||
[
|
||||
'field' => 'note',
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Presenters;
|
||||
/**
|
||||
* Class AccessoryPresenter
|
||||
*/
|
||||
class UploadsPresenter extends Presenter
|
||||
class UploadedFilesPresenter extends Presenter
|
||||
{
|
||||
/**
|
||||
* Json Column Layout for bootstrap table
|
||||
@@ -18,7 +18,7 @@ class UploadsPresenter extends Presenter
|
||||
$layout = [
|
||||
[
|
||||
'field' => 'id',
|
||||
'searchable' => false,
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'switchable' => true,
|
||||
'title' => trans('general.id'),
|
||||
@@ -30,6 +30,7 @@ class UploadsPresenter extends Presenter
|
||||
'sortable' => false,
|
||||
'switchable' => false,
|
||||
'title' => trans('general.type'),
|
||||
'visible' => true,
|
||||
'formatter' => 'iconFormatter',
|
||||
],
|
||||
[
|
||||
@@ -38,16 +39,17 @@ class UploadsPresenter extends Presenter
|
||||
'sortable' => false,
|
||||
'switchable' => true,
|
||||
'title' => trans('general.image'),
|
||||
'formatter' => 'inlineImageFormatter',
|
||||
'visible' => true,
|
||||
'formatter' => 'filePreviewFormatter',
|
||||
],
|
||||
[
|
||||
'field' => 'filename',
|
||||
'searchable' => false,
|
||||
'sortable' => false,
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'switchable' => true,
|
||||
'title' => trans('general.file_name'),
|
||||
'visible' => true,
|
||||
'formatter' => 'fileUploadNameFormatter',
|
||||
'formatter' => 'fileNameFormatter',
|
||||
],
|
||||
[
|
||||
'field' => 'download',
|
||||
@@ -56,7 +58,7 @@ class UploadsPresenter extends Presenter
|
||||
'switchable' => true,
|
||||
'title' => trans('general.download'),
|
||||
'visible' => true,
|
||||
'formatter' => 'downloadOrOpenInNewWindowFormatter',
|
||||
'formatter' => 'fileDownloadButtonsFormatter',
|
||||
],
|
||||
[
|
||||
'field' => 'note',
|
||||
@@ -68,10 +70,10 @@ class UploadsPresenter extends Presenter
|
||||
],
|
||||
[
|
||||
'field' => 'created_by',
|
||||
'searchable' => false,
|
||||
'searchable' => true,
|
||||
'sortable' => true,
|
||||
'title' => trans('general.created_by'),
|
||||
'visible' => false,
|
||||
'visible' => true,
|
||||
'formatter' => 'usersLinkObjFormatter',
|
||||
],
|
||||
[
|
||||
@@ -80,7 +82,7 @@ class UploadsPresenter extends Presenter
|
||||
'sortable' => true,
|
||||
'switchable' => true,
|
||||
'title' => trans('general.created_at'),
|
||||
'visible' => false,
|
||||
'visible' => true,
|
||||
'formatter' => 'dateDisplayFormatter',
|
||||
], [
|
||||
'field' => 'available_actions',
|
||||
@@ -88,6 +90,7 @@ class UploadsPresenter extends Presenter
|
||||
'sortable' => false,
|
||||
'switchable' => false,
|
||||
'title' => trans('table.actions'),
|
||||
'visible' => true,
|
||||
'formatter' => 'deleteUploadFormatter',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -180,7 +180,7 @@ class UserPresenter extends Presenter
|
||||
'switchable' => false,
|
||||
'title' => trans('admin/users/table.username'),
|
||||
'visible' => true,
|
||||
'formatter' => 'usersLinkFormatter',
|
||||
'formatter' => 'usernameRoleLinkFormatter',
|
||||
],
|
||||
[
|
||||
'field' => 'employee_num',
|
||||
|
||||
@@ -101,13 +101,21 @@ class AuthServiceProvider extends ServiceProvider
|
||||
* This is where we set the superadmin permission to allow superadmins to be able to do everything within the system.
|
||||
*
|
||||
*/
|
||||
Gate::before(function ($user) {
|
||||
Gate::before(function ($user, $ability) {
|
||||
|
||||
// Disallow even superadmins to edit non-editable things when in demo mode.
|
||||
// (We have to do this to prevent jerks from trying to break the demo by editing things they shouldn't.)
|
||||
if (($ability == 'editableOnDemo') && (config('app.lock_passwords'))) {
|
||||
return false;
|
||||
}
|
||||
if ($user->isSuperUser()) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* GENERAL GATES
|
||||
*
|
||||
@@ -115,6 +123,45 @@ class AuthServiceProvider extends ServiceProvider
|
||||
* use in our controllers to determine if a user has access to a certain area.
|
||||
*/
|
||||
|
||||
Gate::define('canEditAuthFields', function ($user, $item) {
|
||||
|
||||
if ($item instanceof User) {
|
||||
|
||||
// if they can only edit users, deny them if the user is admin or superadmin
|
||||
if (($user->hasAccess('users.edit')) && (!$user->isAdmin()) && (!$user->isAdmin())) {
|
||||
|
||||
if ($item->isAdmin() || $item->isSuperUser()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// if they are an admin, deny them only if the user is a superadmin
|
||||
if ($user->hasAccess('admin')) {
|
||||
if ($item->isSuperUser()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Define the demo mode gate so we have an easy way to use @can and Gate::allows()
|
||||
*/
|
||||
Gate::define('editableOnDemo', function () {
|
||||
if (config('app.lock_passwords')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Gate::define('admin', function ($user) {
|
||||
if ($user->hasAccess('admin')) {
|
||||
return true;
|
||||
@@ -249,5 +296,6 @@ class AuthServiceProvider extends ServiceProvider
|
||||
return $user->canEditProfile();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"doctrine/dbal": "^3.1",
|
||||
"doctrine/instantiator": "^1.3",
|
||||
"eduardokum/laravel-mail-auto-embed": "^2.0",
|
||||
"enshrined/svg-sanitize": "^0.15.0",
|
||||
"enshrined/svg-sanitize": "^0.16.0",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"fakerphp/faker": "^1.24",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
|
||||
2295
composer.lock
generated
2295
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ $dump_options = [
|
||||
//'add_extra_option' => '--optionname=optionvalue',
|
||||
];
|
||||
|
||||
// Some versions of mysql do not support the --skip-ssl option and will fail if it is
|
||||
// Some versions of mysql do not support the --skip-ssl option and will fail if it is even set
|
||||
if (env('DB_DUMP_SKIP_SSL') == 'true') {
|
||||
$dump_options['skip_ssl'] = true;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
return array (
|
||||
'app_version' => 'v8.1.18',
|
||||
'full_app_version' => 'v8.1.18 - build 18876-g4c6249eb9',
|
||||
'build_version' => '18876',
|
||||
'app_version' => 'v8.2.1',
|
||||
'full_app_version' => 'v8.2.1 - build 19068-g6ca49a20c',
|
||||
'build_version' => '19068',
|
||||
'prerelease_version' => '',
|
||||
'hash_version' => 'g4c6249eb9',
|
||||
'full_hash' => 'v8.1.18-77-g4c6249eb9',
|
||||
'hash_version' => 'g6ca49a20c',
|
||||
'full_hash' => 'v8.2.1-10-g6ca49a20c',
|
||||
'branch' => 'develop',
|
||||
);
|
||||
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use \App\Helpers\Helper;
|
||||
use \App\Models\Setting;
|
||||
use \App\Models\User;
|
||||
|
||||
2007
package-lock.json
generated
2007
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -17,12 +17,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"all-contributors-cli": "^6.26.1",
|
||||
"axios": "^1.7.2",
|
||||
"babel-preset-latest": "^6.24.1",
|
||||
"axios": "^1.11.0",
|
||||
"jquery": "<3.6.0",
|
||||
"laravel-mix": "^6.0.49",
|
||||
"lodash": "^4.17.20",
|
||||
"postcss": "^8.4.5"
|
||||
"postcss": "^8.5.6",
|
||||
"webpack": "^5.98.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
@@ -35,18 +35,18 @@
|
||||
"bootstrap-colorpicker": "^2.5.3",
|
||||
"bootstrap-datepicker": "^1.10.0",
|
||||
"bootstrap-less": "^3.3.8",
|
||||
"bootstrap-table": "1.24.1",
|
||||
"bootstrap-table": "1.24.2",
|
||||
"canvas-confetti": "^1.9.3",
|
||||
"chart.js": "^2.9.4",
|
||||
"clipboard": "^2.0.11",
|
||||
"css-loader": "^5.0.0",
|
||||
"ekko-lightbox": "^5.1.1",
|
||||
"imagemin": "^8.0.1",
|
||||
"imagemin": "^9.0.1",
|
||||
"jquery-slimscroll": "^1.3.8",
|
||||
"jquery-ui": "^1.14.1",
|
||||
"jquery-validation": "^1.21.0",
|
||||
"jquery.iframe-transport": "^1.0.0",
|
||||
"jspdf-autotable": "^3.8.4",
|
||||
"jspdf-autotable": "^5.0.2",
|
||||
"less": "^4.2.2",
|
||||
"less-loader": "^6.0",
|
||||
"list.js": "^1.5.0",
|
||||
@@ -55,8 +55,7 @@
|
||||
"select2": "4.0.13",
|
||||
"sheetjs": "^2.0.0",
|
||||
"signature_pad": "^4.2.0",
|
||||
"tableexport.jquery.plugin": "1.32.0",
|
||||
"tether": "^1.4.0",
|
||||
"webpack": "^5.98.0"
|
||||
"tableexport.jquery.plugin": "^1.33.0",
|
||||
"tether": "^1.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5418,3 +5418,5 @@ hr {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=AdminLTE.css.map*/
|
||||
File diff suppressed because one or more lines are too long
@@ -1266,7 +1266,6 @@ label.form-control {
|
||||
}
|
||||
label.form-control--disabled {
|
||||
color: #959495;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
/** --------------------------------------- **/
|
||||
@@ -1429,6 +1428,9 @@ input[type="radio"]:checked::before {
|
||||
.bootstrap-table .fixed-table-container .table tbody tr .card-view {
|
||||
display: table-row !important;
|
||||
}
|
||||
.form-control-static {
|
||||
padding-top: 0px;
|
||||
}
|
||||
td.text-right.text-padding-number-cell {
|
||||
padding-right: 30px !important;
|
||||
white-space: nowrap;
|
||||
@@ -1467,3 +1469,5 @@ caption.tableCaption {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=app.css.map*/
|
||||
File diff suppressed because one or more lines are too long
@@ -887,7 +887,6 @@ label.form-control {
|
||||
}
|
||||
label.form-control--disabled {
|
||||
color: #959495;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
/** --------------------------------------- **/
|
||||
@@ -1050,6 +1049,9 @@ input[type="radio"]:checked::before {
|
||||
.bootstrap-table .fixed-table-container .table tbody tr .card-view {
|
||||
display: table-row !important;
|
||||
}
|
||||
.form-control-static {
|
||||
padding-top: 0px;
|
||||
}
|
||||
td.text-right.text-padding-number-cell {
|
||||
padding-right: 30px !important;
|
||||
white-space: nowrap;
|
||||
@@ -1088,3 +1090,5 @@ caption.tableCaption {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=overrides.css.map*/
|
||||
File diff suppressed because one or more lines are too long
14
public/css/dist/all.css
vendored
14
public/css/dist/all.css
vendored
@@ -20167,6 +20167,7 @@ hr {
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=AdminLTE.css.map*/
|
||||
/*!
|
||||
* Datepicker for Bootstrap v1.10.0 (https://github.com/uxsolutions/bootstrap-datepicker)
|
||||
*
|
||||
@@ -21008,7 +21009,7 @@ hr {
|
||||
@charset "UTF-8";
|
||||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
* version: 1.24.1
|
||||
* version: 1.24.2
|
||||
* https://github.com/wenzhixin/bootstrap-table/
|
||||
*/
|
||||
/* stylelint-disable annotation-no-unknown, max-line-length */
|
||||
@@ -22601,7 +22602,6 @@ label.form-control {
|
||||
}
|
||||
label.form-control--disabled {
|
||||
color: #959495;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
/** --------------------------------------- **/
|
||||
@@ -22764,6 +22764,9 @@ input[type="radio"]:checked::before {
|
||||
.bootstrap-table .fixed-table-container .table tbody tr .card-view {
|
||||
display: table-row !important;
|
||||
}
|
||||
.form-control-static {
|
||||
padding-top: 0px;
|
||||
}
|
||||
td.text-right.text-padding-number-cell {
|
||||
padding-right: 30px !important;
|
||||
white-space: nowrap;
|
||||
@@ -22803,6 +22806,7 @@ caption.tableCaption {
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=app.css.map*/
|
||||
.select2-container {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
@@ -24174,7 +24178,6 @@ label.form-control {
|
||||
}
|
||||
label.form-control--disabled {
|
||||
color: #959495;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
/** --------------------------------------- **/
|
||||
@@ -24337,6 +24340,9 @@ input[type="radio"]:checked::before {
|
||||
.bootstrap-table .fixed-table-container .table tbody tr .card-view {
|
||||
display: table-row !important;
|
||||
}
|
||||
.form-control-static {
|
||||
padding-top: 0px;
|
||||
}
|
||||
td.text-right.text-padding-number-cell {
|
||||
padding-right: 30px !important;
|
||||
white-space: nowrap;
|
||||
@@ -24375,3 +24381,5 @@ caption.tableCaption {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=overrides.css.map*/
|
||||
2
public/css/dist/bootstrap-table.css
vendored
2
public/css/dist/bootstrap-table.css
vendored
@@ -1,7 +1,7 @@
|
||||
@charset "UTF-8";
|
||||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
* version: 1.24.1
|
||||
* version: 1.24.2
|
||||
* https://github.com/wenzhixin/bootstrap-table/
|
||||
*/
|
||||
/* stylelint-disable annotation-no-unknown, max-line-length */
|
||||
|
||||
2
public/css/dist/skins/_all-skins.css
vendored
2
public/css/dist/skins/_all-skins.css
vendored
@@ -5471,3 +5471,5 @@ tr th div.th-inner {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=_all-skins.css.map*/
|
||||
2
public/css/dist/skins/_all-skins.css.map
vendored
2
public/css/dist/skins/_all-skins.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/_all-skins.min.css
vendored
2
public/css/dist/skins/_all-skins.min.css
vendored
@@ -5471,3 +5471,5 @@ tr th div.th-inner {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=_all-skins.css.map*/
|
||||
2
public/css/dist/skins/skin-black-dark.css
vendored
2
public/css/dist/skins/skin-black-dark.css
vendored
@@ -557,3 +557,5 @@ div.container.row-new-striped {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-black-dark.css.map*/
|
||||
File diff suppressed because one or more lines are too long
@@ -557,3 +557,5 @@ div.container.row-new-striped {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-black-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-black.css
vendored
2
public/css/dist/skins/skin-black.css
vendored
@@ -217,3 +217,5 @@ a.btn-danger:visited {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-black.css.map*/
|
||||
2
public/css/dist/skins/skin-black.css.map
vendored
2
public/css/dist/skins/skin-black.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-black.min.css
vendored
2
public/css/dist/skins/skin-black.min.css
vendored
@@ -217,3 +217,5 @@ a.btn-danger:visited {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-black.css.map*/
|
||||
2
public/css/dist/skins/skin-blue-dark.css
vendored
2
public/css/dist/skins/skin-blue-dark.css
vendored
@@ -537,3 +537,5 @@ a:visited {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-blue-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-blue-dark.css.map
vendored
2
public/css/dist/skins/skin-blue-dark.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-blue-dark.min.css
vendored
2
public/css/dist/skins/skin-blue-dark.min.css
vendored
@@ -537,3 +537,5 @@ a:visited {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-blue-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-blue.css
vendored
2
public/css/dist/skins/skin-blue.css
vendored
@@ -249,3 +249,5 @@ a.label.label-default:hover {
|
||||
color: #296282;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-blue.css.map*/
|
||||
2
public/css/dist/skins/skin-blue.css.map
vendored
2
public/css/dist/skins/skin-blue.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-blue.min.css
vendored
2
public/css/dist/skins/skin-blue.min.css
vendored
@@ -249,3 +249,5 @@ a.label.label-default:hover {
|
||||
color: #296282;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-blue.css.map*/
|
||||
2
public/css/dist/skins/skin-contrast.css
vendored
2
public/css/dist/skins/skin-contrast.css
vendored
@@ -225,3 +225,5 @@ input::-ms-input-placeholder {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-contrast.css.map*/
|
||||
2
public/css/dist/skins/skin-contrast.css.map
vendored
2
public/css/dist/skins/skin-contrast.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-contrast.min.css
vendored
2
public/css/dist/skins/skin-contrast.min.css
vendored
@@ -225,3 +225,5 @@ input::-ms-input-placeholder {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-contrast.css.map*/
|
||||
2
public/css/dist/skins/skin-green-dark.css
vendored
2
public/css/dist/skins/skin-green-dark.css
vendored
@@ -524,3 +524,5 @@ a:visited {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-green-dark.css.map*/
|
||||
File diff suppressed because one or more lines are too long
@@ -524,3 +524,5 @@ a:visited {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-green-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-green.css
vendored
2
public/css/dist/skins/skin-green.css
vendored
@@ -209,3 +209,5 @@ a:visited {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-green.css.map*/
|
||||
2
public/css/dist/skins/skin-green.css.map
vendored
2
public/css/dist/skins/skin-green.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-green.min.css
vendored
2
public/css/dist/skins/skin-green.min.css
vendored
@@ -209,3 +209,5 @@ a:visited {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-green.css.map*/
|
||||
2
public/css/dist/skins/skin-orange-dark.css
vendored
2
public/css/dist/skins/skin-orange-dark.css
vendored
@@ -525,3 +525,5 @@ input[type=search] {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-orange-dark.css.map*/
|
||||
File diff suppressed because one or more lines are too long
@@ -525,3 +525,5 @@ input[type=search] {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-orange-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-orange.css
vendored
2
public/css/dist/skins/skin-orange.css
vendored
@@ -204,3 +204,5 @@ a.btn-danger:visited {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-orange.css.map*/
|
||||
2
public/css/dist/skins/skin-orange.css.map
vendored
2
public/css/dist/skins/skin-orange.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-orange.min.css
vendored
2
public/css/dist/skins/skin-orange.min.css
vendored
@@ -204,3 +204,5 @@ a.btn-danger:visited {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-orange.css.map*/
|
||||
2
public/css/dist/skins/skin-purple-dark.css
vendored
2
public/css/dist/skins/skin-purple-dark.css
vendored
@@ -535,3 +535,5 @@ a:visited {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-purple-dark.css.map*/
|
||||
File diff suppressed because one or more lines are too long
@@ -535,3 +535,5 @@ a:visited {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-purple-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-purple.css
vendored
2
public/css/dist/skins/skin-purple.css
vendored
@@ -204,3 +204,5 @@ a.btn-danger:visited {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-purple.css.map*/
|
||||
2
public/css/dist/skins/skin-purple.css.map
vendored
2
public/css/dist/skins/skin-purple.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-purple.min.css
vendored
2
public/css/dist/skins/skin-purple.min.css
vendored
@@ -204,3 +204,5 @@ a.btn-danger:visited {
|
||||
background-color: #e9d15b;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-purple.css.map*/
|
||||
2
public/css/dist/skins/skin-red-dark.css
vendored
2
public/css/dist/skins/skin-red-dark.css
vendored
@@ -543,3 +543,5 @@ a:visited {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-red-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-red-dark.css.map
vendored
2
public/css/dist/skins/skin-red-dark.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-red-dark.min.css
vendored
2
public/css/dist/skins/skin-red-dark.min.css
vendored
@@ -543,3 +543,5 @@ a:visited {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-red-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-red.css
vendored
2
public/css/dist/skins/skin-red.css
vendored
@@ -211,3 +211,5 @@ a.btn-danger:visited {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-red.css.map*/
|
||||
2
public/css/dist/skins/skin-red.css.map
vendored
2
public/css/dist/skins/skin-red.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-red.min.css
vendored
2
public/css/dist/skins/skin-red.min.css
vendored
@@ -211,3 +211,5 @@ a.btn-danger:visited {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-red.css.map*/
|
||||
2
public/css/dist/skins/skin-yellow-dark.css
vendored
2
public/css/dist/skins/skin-yellow-dark.css
vendored
@@ -499,3 +499,5 @@ tr th div.th-inner {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-yellow-dark.css.map*/
|
||||
File diff suppressed because one or more lines are too long
@@ -499,3 +499,5 @@ tr th div.th-inner {
|
||||
background-color: var(--back-sub);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-yellow-dark.css.map*/
|
||||
2
public/css/dist/skins/skin-yellow.css
vendored
2
public/css/dist/skins/skin-yellow.css
vendored
@@ -218,3 +218,5 @@ a:visited {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-yellow.css.map*/
|
||||
2
public/css/dist/skins/skin-yellow.css.map
vendored
2
public/css/dist/skins/skin-yellow.css.map
vendored
File diff suppressed because one or more lines are too long
2
public/css/dist/skins/skin-yellow.min.css
vendored
2
public/css/dist/skins/skin-yellow.min.css
vendored
@@ -218,3 +218,5 @@ a:visited {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=skin-yellow.css.map*/
|
||||
2
public/css/skins/dist/skin-black-dark.css
vendored
2
public/css/skins/dist/skin-black-dark.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/css/skins/dist/skin-black.css
vendored
2
public/css/skins/dist/skin-black.css
vendored
@@ -1,2 +0,0 @@
|
||||
.skin-black .main-header .navbar{background-color:#111}.skin-black .main-header .navbar .nav>li>a{color:#fff}.skin-black .main-header .navbar .nav .open>a,.skin-black .main-header .navbar .nav .open>a:focus,.skin-black .main-header .navbar .nav .open>a:hover,.skin-black .main-header .navbar .nav>.active>a,.skin-black .main-header .navbar .nav>li>a:active,.skin-black .main-header .navbar .nav>li>a:focus,.skin-black .main-header .navbar .nav>li>a:hover,.skin-black .main-header .navbar .sidebar-toggle:hover{background:rgba(0,0,0,.1);color:#f6f6f6}.skin-black .main-header .navbar .sidebar-toggle{color:#fff}.skin-black .main-header .navbar .sidebar-toggle:hover{background-color:#040404}@media (max-width:767px){.skin-black .main-header .navbar .dropdown-menu li.divider{background-color:hsla(0,0%,100%,.1)}.skin-black .main-header .navbar .dropdown-menu li a{color:#fff}.skin-black .main-header .navbar .dropdown-menu li a:hover{background:#040404}}.skin-black .main-header li.user-header{background-color:#111}.skin-black .content-header{background:transparent}.skin-black .left-side,.skin-black .main-sidebar,.skin-black .wrapper{background-color:#222d32}.skin-black .user-panel>.info,.skin-black .user-panel>.info>a{color:#fff}.skin-black .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-black .sidebar-menu>li>a{border-left:3px solid transparent}.skin-black .sidebar-menu>li.active>a,.skin-black .sidebar-menu>li.menu-open>a,.skin-black .sidebar-menu>li:hover>a{color:#fff;background:#1e282c}.skin-black .sidebar-menu>li.active>a{border-left-color:#111}.skin-black .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-black .sidebar a{color:#b8c7ce}.skin-black .sidebar a:hover{text-decoration:none}.skin-black .sidebar-menu .treeview-menu>li>a{color:#8aa4af}.skin-black .sidebar-menu .treeview-menu>li.active>a,.skin-black .sidebar-menu .treeview-menu>li>a:hover{color:#fff}.skin-black .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px}.skin-black .sidebar-form .btn,.skin-black .sidebar-form input[type=text]{-webkit-box-shadow:none;box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px}.skin-black .sidebar-form input[type=text]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black .sidebar-form input[type=text]:focus,.skin-black .sidebar-form input[type=text]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black .sidebar-form input[type=text]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-black.layout-top-nav .main-header>.logo .logo-variant{background-color:none}.btn,.btn:hover{text-decoration:none}.btn.btn-primary,.btn .btn-primary:link,.btn:hover.btn-primary,.btn:hover .btn-primary:link{background-color:#505156;border-color:#b5bbc8;color:#fff}.btn:hovera.btn-primary:hover,.btna.btn-primary:hover{background-color:#111;border-color:#1f1f21;color:#fff}.btn.btn-white:hover,.btn.btn-white:link,.btn.btn-white:visited,.btn:hover.btn-white:hover,.btn:hover.btn-white:link,.btn:hover.btn-white:visited{color:#fff}a{color:#111;text-decoration:underline}a:hover{color:#000}a:visited{color:#111}.text-primary{color:#000}.skin-black .main-header .navbar .nav>li>a{text-decoration:none}
|
||||
/*# sourceMappingURL=skin-black.css.map*/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user