Compare commits

..

2 Commits

Author SHA1 Message Date
snipe
d990d753ce Switch to 660 2026-03-09 08:23:03 +00:00
snipe
425bab096d Added fix-permissions script to composer to handle #18601 2026-03-08 14:52:23 +00:00
76 changed files with 545 additions and 480 deletions

View File

@@ -1,121 +0,0 @@
# GitHub Copilot Custom Instructions for Snipe-IT
These instructions guide Copilot to generate code that aligns with modern Laravel 11 standards, PHP 8.2/8.4 features, software engineering principles, and industry best practices to improve software quality, maintainability, and security.
## ✅ General Coding Standards
- Prefer short, expressive, and readable code.
- Use **meaningful, descriptive variable, function, class, and file names**.
- Apply proper PHPDoc blocks for classes, methods, and complex logic.
- Organize code into small, reusable functions or classes with single responsibility.
- Avoid magic numbers or hard-coded strings; use constants or config files.
## ✅ PHP 8.2/8.4 Best Practices
- Use **readonly properties** to enforce immutability where applicable.
- Use **Enums** instead of string or integer constants.
- Utilize **First-class callable syntax** for callbacks.
- Leverage **Constructor Property Promotion**.
- Use **Union Types**, **Intersection Types**, and **true/false return types** for strict typing.
- Apply **Static Return Type** where needed.
- Use the **Nullsafe Operator (?->)** for optional chaining.
- Adopt **final classes** where extension is not intended.
- Use **Named Arguments** for improved clarity when calling functions with multiple parameters.
## ✅ Laravel 11 Project Structure & Conventions
- Follow the official Laravel project structure:
- `app/Http/Controllers` - Controllers
- `app/Models` - Eloquent models
- `app/Http/Requests` - Form request validation
- `app/Http/Resources` - API resource responses
- `app/Enums` - Enums
- `app/Services` - Business logic
- `app/Data` - Data Transfer Objects (DTOs)
- `app/Actions` - Single-responsibility action classes
- `app/Policies` - Authorization logic
- Controllers must:
- Be thin.
- Use dependency injection.
- Use Form Requests for validation.
- Return typed responses (e.g., `JsonResponse`).
- Use Resource classes for API responses.
## ✅ Eloquent ORM & Database
- Use **Eloquent Models** with proper `$fillable` or `$guarded` attributes for mass assignment protection.
- Utilize **casts** for date, boolean, JSON, and custom data types.
- Apply **accessors & mutators** for attribute transformation.
- Avoid direct raw SQL unless absolutely necessary; prefer Eloquent or Query Builder.
- Migrations:
- Always use migrations for schema changes.
- Include proper constraints (foreign keys, unique indexes, etc.).
- Prefer UUIDs or ULIDs as primary keys where applicable.
## ✅ API Development
- Use **API Resource classes** for consistent and structured JSON responses.
- Apply **route model binding** where possible.
- Use Form Requests for input validation.
## ✅ Blade & Frontend (if applicable)
- Keep Blade templates clean and logic-free; use View Composers or dedicated View Models for complex data.
- Use `@props`, `@aware`, `@once` Blade features appropriately.
- Utilize Alpine.js or Livewire for interactive frontend logic (optional).
## ✅ Security Best Practices
- Never trust user input; always validate and sanitize inputs.
- Use prepared statements via Eloquent or Query Builder to prevent SQL injection.
- Use Laravel's built-in CSRF, XSS, and validation mechanisms.
- Store sensitive information in `.env`, never hard-code secrets.
- Apply proper authorization checks using Policies or Gates.
- Follow principle of least privilege for users, roles, and permissions.
## ✅ Testing Standards
- Use **factories** for test data setup.
- Include feature tests for user-facing functionality.
- Include unit tests for business logic, services, and helper classes.
- Mock external services using Laravel's `Http::fake()` or equivalent.
- Maintain high code coverage but focus on meaningful tests over 100% coverage obsession.
## ✅ Software Quality & Maintainability
- Follow **SOLID Principles**:
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
- Follow **DRY** (Don't Repeat Yourself) and **KISS** (Keep It Simple, Stupid) principles.
- Apply **YAGNI** (You Aren't Gonna Need It) to avoid overengineering.
- Document complex logic with PHPDoc and inline comments.
## ✅ Performance & Optimization
- Eager load relationships to avoid N+1 queries.
- Use caching with Laravel's Cache system for frequently accessed data.
- Paginate large datasets using `paginate()` instead of `get()`.
- Queue long-running tasks using Laravel Queues.
- Optimize database indexes for common queries.
## ✅ Modern Laravel Features to Use
- Use **Event Broadcasting** if real-time updates are needed.
- Use **Full-text search** if search functionality is required.
- Use **Rate Limiting** for API routes.
## ✅ Additional Copilot Behavior Preferences
- Generate **strictly typed**, modern PHP code using latest language features.
- Prioritize **readable, clean, maintainable** code over cleverness.
- Avoid legacy or deprecated Laravel patterns (facade overuse, logic-heavy views, etc.).
- Suggest proper class placement based on Laravel directory structure.
- Suggest tests alongside new features where applicable.
- Default to **immutability**, **dependency injection**, and **encapsulation** best practices.
No newline at end of file

View File

@@ -46,13 +46,13 @@ jobs:
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
@@ -73,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.alpine

View File

@@ -46,13 +46,13 @@ jobs:
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v3
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v6
uses: docker/metadata-action@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
@@ -73,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v7
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile

View File

@@ -52,7 +52,6 @@ class ResetDemoSettings extends Command
$settings->header_color = '#3c8dbc';
$settings->link_dark_color = '#5fa4cc';
$settings->link_light_color = '#296282;';
$settings->nav_link_color = '#FFFFFF';
$settings->label2_2d_type = 'QRCODE';
$settings->default_currency = 'USD';
$settings->brand = 2;

View File

@@ -1547,19 +1547,18 @@ class Helper
]) ? 'rtl' : 'ltr';
}
static public function getRedirectOption($request, $id, $table, $item_id = null) : RedirectResponse
{
$redirect_option = session('redirect_option') ?? $request->redirect_option;
$checkout_to_type = session('checkout_to_type') ?? null;
$checkedInFrom = session('checkedInFrom');
$other_redirect = session('other_redirect');
$backUrl = session()->pull('url.intended', 'home');
$redirect_option = Session::get('redirect_option') ?? $request->redirect_option;
$checkout_to_type = Session::get('checkout_to_type') ?? null;
$checkedInFrom = Session::get('checkedInFrom');
$other_redirect = Session::get('other_redirect');
$backUrl = Session::pull('back_url', route('home'));
// return to previous page
if ($redirect_option == 'back') {
return redirect()->intended($backUrl);
if ($redirect_option === 'back') {
return redirect()->to($backUrl);
}
// return to index

View File

@@ -114,8 +114,7 @@ class AccessoriesController extends Controller
*/
public function edit(Accessory $accessory) : View | RedirectResponse
{
$this->authorize('update', $accessory);
session()->put('url.intended', url()->previous());
$this->authorize('update', Accessory::class);
return view('accessories.edit')->with('item', $accessory)->with('category_type', 'accessory');
}
@@ -129,7 +128,7 @@ class AccessoriesController extends Controller
public function getClone(Accessory $accessory) : View | RedirectResponse
{
$this->authorize('create', $accessory);
$this->authorize('create', Accessory::class);
$cloned = clone $accessory;
$accessory_to_clone = $accessory;
$cloned->id = null;
@@ -150,10 +149,10 @@ class AccessoriesController extends Controller
*/
public function update(ImageUploadRequest $request, Accessory $accessory) : RedirectResponse
{
$this->authorize('update', $accessory);
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id)) {
$this->authorize($accessory);
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$accessory->checkouts_count"
]);
@@ -164,6 +163,8 @@ class AccessoriesController extends Controller
->withInput();
}
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
@@ -181,7 +182,7 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
if ($request->input('redirect_option') === 'back'){
if($request->input('redirect_option') === 'back'){
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->input('redirect_option')]);
@@ -204,26 +205,30 @@ class AccessoriesController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
*/
public function destroy(Accessory $accessory) : RedirectResponse
public function destroy($accessoryId) : RedirectResponse
{
$this->authorize('delete', $accessory);
$accessory->loadCount('checkouts as checkouts_count');
if ($accessory->isDeletable()) {
if ($accessory->image) {
try {
Storage::disk('public')->delete('accessories'.'/'.$accessory->image);
} catch (\Exception $e) {
Log::debug($e);
}
}
$accessory->delete();
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
if (is_null($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled'));
$this->authorize($accessory);
if ($accessory->checkouts_count > 0) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled'));
}
if ($accessory->image) {
try {
Storage::disk('public')->delete('accessories'.'/'.$accessory->image);
} catch (\Exception $e) {
Log::debug($e);
}
}
$accessory->delete();
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
}
@@ -238,9 +243,11 @@ class AccessoriesController extends Controller
*/
public function show(Accessory $accessory) : View | RedirectResponse
{
$this->authorize('view', $accessory);
$accessory->loadCount('checkouts as checkouts_count');
$accessory->load(['adminuser' => fn($query) => $query->withTrashed()]);
$this->authorize('view', $accessory);
return view('accessories.view', compact('accessory'));
}
}

View File

@@ -29,7 +29,6 @@ class AccessoryCheckinController extends Controller
}
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
//based on what the accessory is checked out to the target redirect option will be displayed accordingly.
$target_option = match ($accessory_user->assigned_type) {
@@ -37,7 +36,7 @@ class AccessoryCheckinController extends Controller
'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]),
default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]),
};
$this->authorize('checkin', $accessory);
return view('accessories/checkin', compact('accessory', 'target_option'))->with('backto', $backto);
@@ -58,7 +57,6 @@ class AccessoryCheckinController extends Controller
}
$accessory = Accessory::find($accessory_checkout->accessory_id);
$this->authorize('checkin', $accessory);
session()->put('checkedInFrom', $accessory_checkout->assigned_to);
session()->put('checkout_to_type', match ($accessory_checkout->assigned_type) {
@@ -67,7 +65,7 @@ class AccessoryCheckinController extends Controller
'App\Models\Asset' => 'asset',
});
$this->authorize('checkin', $accessory);
$checkin_hours = date('H:i:s');
$checkin_at = date('Y-m-d H:i:s');
if ($request->filled('checkin_at')) {

View File

@@ -27,24 +27,31 @@ class AccessoryCheckoutController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
*/
public function create(Accessory $accessory) : View | RedirectResponse
public function create($id) : View | RedirectResponse
{
$this->authorize('checkout', $accessory);
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) {
if ($accessory->category) {
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
$this->authorize('checkout', $accessory);
if ($accessory->category) {
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
// Return the checkout view
return view('accessories/checkout', compact('accessory'));
}
// Return the checkout view
return view('accessories/checkout', compact('accessory'));
// Invalid category
return redirect()->route('accessories.edit', ['accessory' => $accessory->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.accessory')]));
}
// Invalid category
return redirect()->route('accessories.edit', ['accessory' => $accessory->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.accessory')]));
// Not found
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}

View File

@@ -1149,7 +1149,7 @@ class AssetsController extends Controller
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'note' => e($request->input('note')),
'status_label' => e($asset->assetstatus?->display_name),
'status_label' => e($asset->assetstatus->display_name),
'status_type' => $asset->assetstatus->getStatuslabelType(),
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
];

View File

@@ -37,9 +37,6 @@ class LicenseSeatsController extends Controller
$seats->ByAssigned();
}
if ($request->filled('search')) {
$seats->TextSearch($request->input('search'));
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';

View File

@@ -93,7 +93,7 @@ class UploadedFilesController extends Controller
// Check the permissions to make sure the user can view the object
$object = self::$map_object_type[$object_type]::withTrashed()->find($id);
$this->authorize('update', $object);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));

View File

@@ -312,7 +312,7 @@ class AssetsController extends Controller
public function edit(Asset $asset) : View | RedirectResponse
{
$this->authorize($asset);
session()->put('url.intended', url()->previous());
session()->put('back_url', url()->previous());
return view('hardware/edit')
->with('item', $asset)
->with('statuslabel_list', Helper::statusLabelList())
@@ -508,9 +508,16 @@ class AssetsController extends Controller
* @param int $assetId
* @since [v1.0]
*/
public function destroy(Request $request, Asset $asset) : RedirectResponse
public function destroy(Request $request, $assetId) : RedirectResponse
{
// Check if the asset exists
if (is_null($asset = Asset::find($assetId))) {
// Redirect to the asset management page with error
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('delete', $asset);
if ($asset->assignedTo) {
$target = $asset->assignedTo;

View File

@@ -117,7 +117,7 @@ class ComponentsController extends Controller
{
$this->authorize('update', $component);
session()->put('url.intended', url()->previous());
session()->put('back_url', url()->previous());
return view('components/edit')
->with('item', $component)
->with('category_type', 'component');

View File

@@ -124,7 +124,7 @@ class ConsumablesController extends Controller
public function edit(Consumable $consumable) : View | RedirectResponse
{
$this->authorize($consumable);
session()->put('url.intended', url()->previous());
session()->put('back_url', url()->previous());
return view('consumables/edit')
->with('item', $consumable)
->with('category_type', 'consumable');

View File

@@ -130,7 +130,7 @@ class LicensesController extends Controller
{
$this->authorize('update', $license);
session()->put('url.intended', url()->previous());
session()->put('back_url', url()->previous());
$maintained_list = [
'' => 'Maintained',
'1' => 'Yes',

View File

@@ -326,26 +326,31 @@ class LocationsController extends Controller
* @since [v1.0]
* @param int $id
*/
public function postRestore(Location $location) : RedirectResponse
public function postRestore($id) : RedirectResponse
{
$this->authorize('delete', $location);
$this->authorize('create', Location::class);
if ($location->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.location')]));
if ($location = Location::withTrashed()->find($id)) {
if ($location->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.location')]));
}
if ($location->restore()) {
$logaction = new Actionlog();
$logaction->item_type = Location::class;
$logaction->item_id = $location->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id();
$logaction->logaction('restore');
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
}
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
}
if ($location->restore()) {
$logaction = new Actionlog();
$logaction->item_type = Location::class;
$logaction->item_id = $location->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id();
$logaction->logaction('restore');
return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success'));
}
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()]));
return redirect()->back()->with('error', trans('admin/models/message.does_not_exist'));
}

View File

@@ -191,9 +191,9 @@ class SettingsController extends Controller
$request->validate(['site_name' => 'required']);
}
$setting->header_color = $request->input('header_color', '#3c8dbc');
$setting->header_color = $request->input('header_color');
$setting->link_light_color = $request->input('link_light_color', '#296282');
$setting->link_dark_color = $request->input('link_dark_color', '#5fa4cc');
$setting->link_dark_color = $request->input('link_dark_color', '#296282');
$setting->nav_link_color = $request->input('nav_link_color', '#FFFFFF');
$setting->site_name = $request->input('site_name', 'Snipe-IT');

View File

@@ -203,8 +203,8 @@ class UsersController extends Controller
public function edit(User $user)
{
$this->authorize('update', $user);
session()->put('url.intended', url()->previous());
$this->authorize('update', User::class);
session()->put('back_url', url()->previous());
$user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($user->id);
if ($user) {
@@ -238,7 +238,7 @@ class UsersController extends Controller
*/
public function update(SaveUserRequest $request, User $user)
{
$this->authorize('update', $user);
$this->authorize('update', User::class);
// This is a janky hack to prevent people from changing admin demo user data on the public demo.
// The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
@@ -259,19 +259,12 @@ class UsersController extends Controller
// Figure out of this user was an admin before this edit
$orig_permissions_array = $user->decodePermissions();
$orig_superuser = '0';
$orig_admin = '0';
if (is_array($orig_permissions_array)) {
if (array_key_exists('superuser', $orig_permissions_array)) {
$orig_superuser = $orig_permissions_array['superuser'];
}
}
if (is_array($orig_permissions_array)) {
if (array_key_exists('admin', $orig_permissions_array)) {
$orig_admin = $orig_permissions_array['admin'];
}
}
// Update the user fields
@@ -330,11 +323,6 @@ class UsersController extends Controller
$permissions_array['superuser'] = $orig_superuser;
}
if ((! auth()->user()->isSuperUser()) && (! auth()->user()->isAdmin())) {
unset($permissions_array['admin']);
$permissions_array['admin'] = $orig_admin;
}
$user->permissions = json_encode($permissions_array);
// Only save groups if the user is a superuser
@@ -399,35 +387,37 @@ class UsersController extends Controller
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function getRestore(User $user)
public function getRestore($id = null)
{
if ($user = User::withTrashed()->find($id)) {
$this->authorize('delete', $user);
$this->authorize('delete', $user);
if ($user->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.user')]));
}
if ($user->restore()) {
$logaction = new Actionlog();
$logaction->item_type = User::class;
$logaction->item_id = $user->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id();
$logaction->logaction('restore');
// Redirect them to the deleted page if there are more, otherwise the section index
$deleted_users = User::onlyTrashed()->count();
if ($deleted_users > 0) {
return redirect()->back()->with('success', trans('admin/users/message.success.restored'));
if ($user->deleted_at == '') {
return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.user')]));
}
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
if ($user->restore()) {
$logaction = new Actionlog();
$logaction->item_type = User::class;
$logaction->item_id = $user->id;
$logaction->created_at = date('Y-m-d H:i:s');
$logaction->created_by = auth()->id();
$logaction->logaction('restore');
// Redirect them to the deleted page if there are more, otherwise the section index
$deleted_users = User::onlyTrashed()->count();
if ($deleted_users > 0) {
return redirect()->back()->with('success', trans('admin/users/message.success.restored'));
}
return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored'));
}
// Check validation to make sure we're not restoring a user with the same username as an existing user
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()]));
}
// Check validation to make sure we're not restoring a user with the same username as an existing user
return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()]));
return redirect()->route('users.index')->with('error', trans('admin/users/message.does_not_exist'));
}
/**
@@ -442,7 +432,7 @@ class UsersController extends Controller
public function show(User $user)
{
// Make sure the user can view users at all
$this->authorize('view', $user);
$this->authorize('view', User::class);
$user = User::with([
'consumables',
@@ -475,7 +465,7 @@ class UsersController extends Controller
*/
public function getClone(Request $request, User $user)
{
$this->authorize('create', $user);
$this->authorize('create', User::class);
// We need to reverse the UI specific logic for our
// permissions here before we update the user.
@@ -644,9 +634,9 @@ class UsersController extends Controller
* @since [v1.8]
* @author Aladin Alaily
*/
public function printInventory(User $user, $id)
public function printInventory($id)
{
$this->authorize('view', $user);
$this->authorize('view', User::class);
$user = User::where('id', $id)
->with([

View File

@@ -111,11 +111,11 @@ class AssetModelsTransformer
$array = [
'id' => (int) $file->id,
'filename' => e($file->filename),
'note' => $file->note ? e($file->note) : null,
'note' => $file->note,
'url' => route('show/modelfile', [$assetmodel->id, $file->id]),
'created_by' => ($file->adminuser) ? [
'id' => (int) $file->adminuser->id,
'name'=> e($file->adminuser->display_name),
'name'=> e($file->adminuser->present()->fullName),
] : null,
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($file->updated_at, 'datetime'),

View File

@@ -322,14 +322,14 @@ class AssetsTransformer
'id' => $accessory_checkout->id,
'accessory' => [
'id' => $accessory_checkout->accessory->id,
'name' => e($accessory_checkout->accessory->display_name),
'name' => $accessory_checkout->accessory->name,
],
'assigned_to' => $accessory_checkout->assigned_to,
'image' => ($accessory_checkout->accessory->image) ? Storage::disk('public')->url('accessories/' . e($accessory_checkout->accessory->image)) : null,
'note' => $accessory_checkout->note ? e($accessory_checkout->note) : null,
'created_by' => $accessory_checkout->adminuser ? [
'id' => (int)$accessory_checkout->adminuser->id,
'name' => e($accessory_checkout->display_name),
'name' => e($accessory_checkout->adminuser->present()->fullName),
] : null,
'created_at' => Helper::getFormattedDateObject($accessory_checkout->created_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($accessory_checkout->deleted_at, 'datetime'),

View File

@@ -91,7 +91,7 @@ class ComponentsTransformer
'id' => (int) $asset->id,
'name' => e($asset->model->display_name).' '.e($asset->display_name),
'qty' => $asset->pivot->assigned_qty,
'note' => e($asset->pivot->note),
'note' => $asset->pivot->note,
'type' => 'asset',
'created_at' => Helper::getFormattedDateObject($asset->pivot->created_at, 'datetime'),
'available_actions' => ['checkin' => true],

View File

@@ -44,7 +44,7 @@ class CustomFieldsTransformer
'db_column_name' => e($field->db_column_name()),
'format' => e($field->format),
'field_values' => ($field->field_values) ? e($field->field_values) : null,
'field_encrypted' => ($field->field_encrypted =='1') ? true : false,
'field_encrypted' => $field->field_encrypted,
'field_values_array' => ($field->field_values) ? explode("\r\n", e($field->field_values)) : null,
'type' => e($field->element),
'required' => (($field->pivot) && ($field->pivot->required=='1')) ? true : false,

View File

@@ -30,7 +30,7 @@ class LocationsTransformer
foreach ($location->children as $child) {
$children_arr[] = [
'id' => (int) $child->id,
'name' => e($child->display_name),
'name' => $child->name,
];
}
}
@@ -157,7 +157,7 @@ class LocationsTransformer
'name' => e($location->name),
'created_by' => $location->adminuser ? [
'id' => (int) $location->adminuser->id,
'name'=> e($location->adminuser->display_name),
'name'=> e($location->adminuser->present()->fullName),
]: null,
'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'),
];
@@ -171,7 +171,7 @@ class LocationsTransformer
if ($accessory) {
return [
'id' => $accessory->id,
'name' => e($accessory->display_name),
'name' => $accessory->name,
];
}

View File

@@ -41,7 +41,7 @@ class UploadedFilesTransformer
'note' => ($file->note) ? e($file->note) : null,
'created_by' => ($file->adminuser) ? [
'id' => (int) $file->adminuser->id,
'name'=> e($file->adminuser->display_name),
'name'=> e($file->adminuser->present()->fullName),
] : null,
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),

View File

@@ -5,7 +5,6 @@ namespace App\Models;
use App\Models\Traits\Acceptable;
use App\Models\Traits\CompanyableChildTrait;
use App\Models\Traits\Loggable;
use App\Models\Traits\Searchable;
use App\Notifications\CheckinLicenseNotification;
use App\Notifications\CheckoutLicenseNotification;
use App\Presenters\Presentable;
@@ -15,15 +14,13 @@ use Illuminate\Database\Eloquent\SoftDeletes;
class LicenseSeat extends SnipeModel implements ICompanyableChild
{
use Acceptable;
use CompanyableChildTrait;
use HasFactory;
use Loggable;
use Presentable;
use Searchable;
use SoftDeletes;
protected $presenter = \App\Presenters\LicenseSeatPresenter::class;
use Presentable;
protected $guarded = 'id';
protected $table = 'license_seats';
@@ -42,25 +39,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild
'notes',
];
/**
* The attributes that should be included when searching the model.
*
* @var array
*/
protected $searchableAttributes = [
'notes',
];
/**
* The relations and their attributes that should be included when searching the model.
*
* @var array
*/
protected $searchableRelations = [
'user' => ['first_name', 'last_name', 'display_name', 'username', 'email'],
'asset' => ['name', 'asset_tag'],
];
use Acceptable;
public function getCompanyableParents()
{

View File

@@ -5,7 +5,6 @@ namespace App\Policies;
use App\Models\Company;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Database\Eloquent\Model;
/**
* SnipePermissionsPolicy provides methods for handling the granular permissions used throughout Snipe-IT.

View File

@@ -265,7 +265,7 @@ class AccessoryPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Accessory', $this])) {
return '<a href="' . route('accessories.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('accessories.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -22,6 +22,9 @@ class ActionlogPresenter extends Presenter
public function item()
{
if ($this->action_type == 'uploaded') {
return (string) link_to_route('show/userfile', $this->model->filename, [$this->model->item->id, $this->model->id]);
}
if ($item = $this->model->item) {
if (empty($item->deleted_at)) {
return $this->model->item->present()->nameUrl();

View File

@@ -266,7 +266,7 @@ class AssetModelPresenter extends Presenter
*/
public function nameUrl()
{
return '<a href="' . route('models.show', $this->id) . '">' . e($this->name) . '</a>';
return (string) link_to_route('models.show', $this->name, $this->id);
}
/**

View File

@@ -452,7 +452,7 @@ class AssetPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Asset', $this])) {
return '<a href="' . route('hardware.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('hardware.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -143,7 +143,7 @@ class CategoryPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Category', $this])) {
return '<a href="' . route('categories.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('categories.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -166,7 +166,7 @@ class CompanyPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Company', $this])) {
return '<a href="' . route('companies.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('companies.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -257,7 +257,7 @@ class ComponentPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Component', $this])) {
return '<a href="' . route('components.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('components.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -255,6 +255,6 @@ class ConsumablePresenter extends Presenter
*/
public function nameUrl()
{
return '<a href="' . route('consumables.show', $this->id) . '">' . e($this->name) . '</a>';
return (string) link_to_route('consumables.show', e($this->name), $this->id);
}
}

View File

@@ -12,7 +12,7 @@ class CustomFieldsetPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\CustomFieldset', $this])) {
return '<a href="' . route('fieldsets.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('fieldsets.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -125,8 +125,8 @@ class DepartmentPresenter extends Presenter
*/
public function viewUrl()
{
if (auth()->user()->can('view', ['\App\Models\Department', $this])) {
return '<a href="' . route('departments.show', $this->id) . '">' . e($this->display_name) . '</a>';
if (auth()->user()->can('view', ['\App\Models\Location', $this])) {
return (string)link_to_route('locations.show', $this->display_name, $this->id);
} else {
return $this->display_name;
}

View File

@@ -111,7 +111,7 @@ class DepreciationPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Depreciation', $this])) {
return '<a href="' . route('depreciations.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('depreciations.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -181,7 +181,7 @@ class DepreciationReportPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Depreciation', $this])) {
return '<a href="' . route('depreciations.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('depreciations.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -323,7 +323,7 @@ class LicensePresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\License', $this])) {
return '<a href="' . route('licenses.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('licenses.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}
@@ -339,6 +339,15 @@ class LicensePresenter extends Presenter
return $this->name;
}
/**
* Link to this licenses serial
* @return string
*/
public function serialUrl()
{
return (string) link_to('/licenses/'.$this->id, mb_strimwidth($this->serial, 0, 50, '...'));
}
/**
* Url to view this item.
* @return string

View File

@@ -351,7 +351,7 @@ class LocationPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Location', $this])) {
return '<a href="' . route('locations.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('locations.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -184,7 +184,7 @@ class ManufacturerPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Manufacturer', $this])) {
return '<a href="' . route('manufacturers.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('manufacturers.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -296,7 +296,7 @@ class PredefinedKitPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\PredefinedKit', $this])) {
return '<a href="' . route('kits.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('kits.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}

View File

@@ -210,7 +210,7 @@ class SupplierPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\Supplier', $this])) {
return '<a href="' . route('suppliers.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('suppliers.show', e($this->display_name), $this->id);
} else {
return e($this->display_name);
}
@@ -232,7 +232,7 @@ class SupplierPresenter extends Presenter
public function viewUrl()
{
if (auth()->user()->can('view', ['\App\Models\Supplier', $this])) {
return '<a href="' . route('suppliers.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('suppliers.show', $this->display_name, $this->id);
} else {
return e($this->display_name);
}

View File

@@ -529,7 +529,7 @@ class UserPresenter extends Presenter
public function nameUrl()
{
if (auth()->user()->can('view', ['\App\Models\User', $this])) {
return '<a href="' . route('users.show', $this->id) . '">' . e($this->display_name) . '</a>';
return (string)link_to_route('users.show', $this->display_name, $this->id);
} else {
return e($this->display_name);
}

View File

@@ -80,7 +80,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('hardware.edit', fn (Trail $trail, Asset $asset) =>
$trail->parent('hardware.index', route('hardware.index'))
->push($asset->display_name, route('hardware.show', $asset))
->push(trans('general.update'))
->push(trans('admin/hardware/general.edit'))
);
@@ -114,8 +114,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('models.edit', fn (Trail $trail, AssetModel $model) =>
$trail->parent('models.index', route('models.index'))
->push($model->display_name, route('models.show', $model))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $model->name]), route('models.edit', $model))
);
@@ -139,8 +138,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('accessories.edit', fn (Trail $trail, Accessory $accessory) =>
$trail->parent('accessories.index', route('accessories.index'))
->push($accessory->display_name, route('accessories.show', $accessory))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $accessory->name]), route('accessories.edit', $accessory))
);
@@ -164,8 +162,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('categories.edit', fn (Trail $trail, Category $category) =>
$trail->parent('categories.index', route('categories.index'))
->push($category->display_name, route('categories.show', $category))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $category->name]), route('categories.edit', $category))
);
@@ -190,8 +187,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('companies.edit', fn (Trail $trail, Company $company) =>
$trail->parent('companies.index', route('companies.index'))
->push($company->display_name, route('companies.show', $company))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $company->name]), route('companies.edit', $company))
);
@@ -215,8 +211,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('components.edit', fn (Trail $trail, Component $component) =>
$trail->parent('components.index', route('components.index'))
->push($component->display_name, route('components.show', $component))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $component->name]), route('components.edit', $component))
);
Breadcrumbs::for('components.clone.create', fn (Trail $trail, Component $component) =>
@@ -246,8 +241,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('consumables.edit', fn (Trail $trail, Consumable $consumable) =>
$trail->parent('consumables.index', route('consumables.index'))
->push($consumable->display_name, route('consumables.show', $consumable))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $consumable->name]), route('consumables.edit', $consumable))
);
/**
@@ -265,7 +259,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('fields.edit', fn (Trail $trail, CustomField $field) =>
$trail->parent('fields.index', route('fields.index'))
->push(trans('general.update')) // We skip the show section here since there isn't really a concept of fields.show
->push($field->name, route('fields.edit', $field))
);
/**
@@ -285,8 +279,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('fieldsets.edit', fn (Trail $trail, CustomFieldset $fieldset) =>
$trail->parent('fields.index', route('fields.index'))
->push($fieldset->display_name, route('fieldsets.show', $fieldset))
->push(trans('general.update'))
->push($fieldset->name, route('fieldsets.edit', $fieldset))
);
/**
@@ -309,8 +302,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('departments.edit', fn (Trail $trail, Department $department) =>
$trail->parent('departments.index', route('departments.index'))
->push($department->display_name, route('departments.show', $department))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $department->name]), route('departments.edit', $department))
);
@@ -334,8 +326,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('depreciations.edit', fn (Trail $trail, Depreciation $depreciation) =>
$trail->parent('depreciations.index', route('depreciations.index'))
->push($depreciation->display_name, route('depreciations.show', $depreciation))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $depreciation->name]), route('depreciations.edit', $depreciation))
);
/**
@@ -358,8 +349,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('groups.edit', fn (Trail $trail, Group $group) =>
$trail->parent('groups.index', route('groups.index'))
->push($group->display_name, route('groups.show', $group))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $group->name]), route('groups.edit', $group))
);
@@ -384,6 +374,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
);
}
Breadcrumbs::for('licenses.create', fn (Trail $trail) =>
$trail->parent('licenses.index', route('licenses.index'))
->push(trans('general.create'), route('licenses.create'))
@@ -396,8 +387,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('licenses.edit', fn (Trail $trail, License $license) =>
$trail->parent('licenses.index', route('licenses.index'))
->push($license->display_name, route('licenses.show', $license))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $license->name]), route('licenses.edit', $license))
);
@@ -427,8 +417,8 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('locations.edit', fn (Trail $trail, Location $location) =>
$trail->parent('locations.index', route('locations.index'))
->push($location->display_name, route('locations.show', $location))
->push(trans('general.update'))
->push($location->name, route('locations.show', $location))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $location->name]), route('locations.edit', $location))
);
/**
@@ -451,8 +441,8 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('maintenances.edit', fn (Trail $trail, Maintenance $maintenance) =>
$trail->parent('maintenances.index', route('maintenances.index'))
->push($maintenance->display_name, route('maintenances.show', $maintenance))
->push(trans('general.update'))
->push($maintenance->name, route('maintenances.show', $maintenance))
->push(trans('general.update', ['name' => $maintenance->name]), route('maintenances.edit', $maintenance))
);
@@ -477,7 +467,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('manufacturers.edit', fn (Trail $trail, Manufacturer $manufacturer) =>
$trail->parent('manufacturers.index', route('manufacturers.index'))
->push($manufacturer->name, route('manufacturers.show', $manufacturer))
->push(trans('general.update'))
->push(trans('general.update', ['name' => $manufacturer->name]), route('manufacturers.edit', $manufacturer))
);
@@ -501,8 +491,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('kits.edit', fn (Trail $trail, PredefinedKit $kit) =>
$trail->parent('kits.index', route('kits.index'))
->push($kit->display_name, route('kits.show', $kit))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $kit->name]), route('kits.edit', $kit))
);
@@ -526,8 +515,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('statuslabels.edit', fn (Trail $trail, Statuslabel $statuslabel) =>
$trail->parent('statuslabels.index', route('statuslabels.index'))
->push($statuslabel->display_name, route('statuslabels.show', $statuslabel))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $statuslabel->name]), route('statuslabels.edit', $statuslabel))
);
@@ -561,8 +549,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('suppliers.edit', fn (Trail $trail, Supplier $supplier) =>
$trail->parent('suppliers.index', route('suppliers.index'))
->push($supplier->display_name, route('suppliers.show', $supplier))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $supplier->name]), route('suppliers.edit', $supplier))
);
@@ -620,8 +607,7 @@ class BreadcrumbsServiceProvider extends ServiceProvider
Breadcrumbs::for('users.edit', fn (Trail $trail, User $user) =>
$trail->parent('users.index', route('users.index'))
->push($user->display_name, route('users.show', $user))
->push(trans('general.update'))
->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $user->name]), route('users.edit', $user))
);

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class MacroServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
// require base_path() . '/resources/macros/community_types.php';
foreach (glob(base_path('resources/macros/*.php')) as $filename) {
require_once $filename;
}
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}

View File

@@ -14,6 +14,10 @@
{
"type": "vcs",
"url": "https://github.com/grokability/laravel-scim-server"
},
{
"type": "vcs",
"url": "https://github.com/grokability/html"
}
],
"require": {
@@ -47,6 +51,7 @@
"laravel/socialite": "^5.6",
"laravel/tinker": "^2.6",
"laravel/ui": "^4.0",
"laravelcollective/html": "6.x-dev",
"league/csv": "^9.7",
"league/flysystem-aws-s3-v3": "^3.0",
"livewire/livewire": "^4.0",
@@ -120,7 +125,8 @@
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi",
"@php artisan vendor:publish --force --tag=livewire:assets --ansi"
"@php artisan vendor:publish --force --tag=livewire:assets --ansi",
"@php fix-permissions.php"
],
"post-create-project-cmd": [
"php artisan key:generate"

78
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3eb217f961e55b3e6d0a1fdd6b812fda",
"content-hash": "58603818d33285d78c0c8655385e7c0e",
"packages": [
{
"name": "alek13/slack",
@@ -3310,6 +3310,79 @@
},
"time": "2025-01-28T15:15:29+00:00"
},
{
"name": "laravelcollective/html",
"version": "6.x-dev",
"source": {
"type": "git",
"url": "https://github.com/grokability/html.git",
"reference": "944b7029c207914ecec437c0141b7a0e32d9e1a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/grokability/html/zipball/944b7029c207914ecec437c0141b7a0e32d9e1a1",
"reference": "944b7029c207914ecec437c0141b7a0e32d9e1a1",
"shasum": ""
},
"require": {
"illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/session": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"illuminate/view": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"php": ">=7.2.5"
},
"require-dev": {
"illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0",
"mockery/mockery": "~1.0",
"phpunit/phpunit": "~8.5|^9.5.10"
},
"default-branch": true,
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.x-dev"
},
"laravel": {
"providers": [
"Collective\\Html\\HtmlServiceProvider"
],
"aliases": {
"Form": "Collective\\Html\\FormFacade",
"Html": "Collective\\Html\\HtmlFacade"
}
}
},
"autoload": {
"psr-4": {
"Collective\\Html\\": "src/"
},
"files": [
"src/helpers.php"
]
},
"license": [
"MIT"
],
"authors": [
{
"name": "Adam Engebretson",
"email": "adam@laravelcollective.com"
},
{
"name": "Taylor Otwell",
"email": "taylorotwell@gmail.com"
}
],
"description": "HTML and Form Builders for the Laravel Framework",
"homepage": "https://laravelcollective.com",
"support": {
"issues": "https://github.com/LaravelCollective/html/issues",
"source": "https://github.com/LaravelCollective/html"
},
"abandoned": "spatie/laravel-html",
"time": "2025-01-20T14:37:14+00:00"
},
{
"name": "lcobucci/jwt",
"version": "5.5.0",
@@ -16557,7 +16630,8 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"arietimmerman/laravel-scim-server": 20
"arietimmerman/laravel-scim-server": 20,
"laravelcollective/html": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View File

@@ -306,6 +306,7 @@ return [
*/
Intervention\Image\ImageServiceProvider::class,
Collective\Html\HtmlServiceProvider::class,
Spatie\Backup\BackupServiceProvider::class,
PragmaRX\Google2FALaravel\ServiceProvider::class,
Laravel\Passport\PassportServiceProvider::class,
@@ -331,6 +332,7 @@ return [
*/
App\Providers\BladeServiceProvider::class,
App\Providers\LivewireServiceProvider::class,
App\Providers\MacroServiceProvider::class,
App\Providers\SamlServiceProvider::class,
App\Providers\BreadcrumbsServiceProvider::class,
@@ -383,6 +385,8 @@ return [
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'Form' => Collective\Html\FormFacade::class,
'Html' => Collective\Html\HtmlFacade::class,
'Google2FA' => PragmaRX\Google2FALaravel\Facade::class,
'Image' => Intervention\Image\ImageServiceProvider::class,
'Carbon' => Carbon\Carbon::class,

1
database/.gitignore vendored
View File

@@ -1,3 +1,2 @@
*.sqlite
*.journal
*.sqlite-journal

View File

@@ -28,7 +28,7 @@ class MaintenanceFactory extends Factory
'asset_id' => Asset::factory()->laptopZenbook(),
'supplier_id' => Supplier::factory(),
'asset_maintenance_type' => $this->faker->randomElement(['maintenance', 'repair', 'upgrade']),
'name' => $this->faker->sentence(3),
'name' => $this->faker->name(),
'start_date' => $this->faker->date(),
'is_warranty' => $this->faker->boolean(),
'notes' => $this->faker->paragraph(),

View File

@@ -32,10 +32,6 @@ class SettingFactory extends Factory
'locale' => 'en-US',
'pwd_secure_min' => 10, // Match web setup
'email_domain' => 'example.org',
'header_color' => '#3c8dbc',
'link_dark_color' => '#5fa4cc',
'link_light_color' => '#296282;',
'nav_link_color' => '#FFFFFF',
];
}
}

54
fix-permissions.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
(PHP_SAPI !== 'cli' || isset($_SERVER['HTTP_USER_AGENT'])) && die('Access denied.');
$icon = '';
$files = [
'storage/oauth-private.key' => '660',
'storage/oauth-public.key' => '660',
];
echo "\n";
// Normalize key permissions for Passport 13 (covers both fresh installs and upgrades)
if (PHP_OS_FAMILY !== 'Windows') {
foreach ($files as $file => $permission) {
if (file_exists($file)) {
try {
@chmod($file, $permission);
$messages[]['success'] = "Permissions updated to ".$permission." on ".$file." \n";
} catch (Exception $e) {
$messages[]['error'] = "Could not change permissions for ".$file.". Please manually change the permissions on this file to ".$permission.". See the documentation: https://snipe-it.readme.io/docs/debugging-permissions#linuxosx \n";
}
} else {
$messages[]['info'] = "The file ".$file." was not found and may not have been created yet. \n";
}
}
if (count($messages) > 0) {
for($x = 0; $x < count($messages); $x++) {
foreach ($messages[$x] as $type => $message) {
if ($type === 'error') {
echo " \e[0;97;41m ERROR \e[0m ";
} elseif ($type === 'info') {
echo " \e[0;97;44m INFO \e[0m ";
} elseif ($type === 'success') {
echo " \e[0;97;42m SUCCESS \e[0m ";
}
echo $message;
}
}
}
echo "\n";
exit();
}
echo " \e[0;97;44m INFO \e[0m Windows OS detected, so OAuth key permissions could not be set. If you have problems with API calls or tables loading in your Snipe-IT application, see the documentation on how to correct them: https://snipe-it.readme.io/docs/debugging-permissions#windows \n";
exit();

View File

@@ -687,6 +687,7 @@ return [
],
'breadcrumb_button_actions' => [
'edit_item' => 'Edit :name',
'checkout_item' => 'Checkout :name',
'checkin_item' => 'Checkin :name',
],

113
resources/macros/macros.php Normal file
View File

@@ -0,0 +1,113 @@
<?php
/**
* Macro helpers
*/
/**
* Country macro
* Generates the dropdown menu of countries for the profile form
*/
Form::macro('countries', function ($name = 'country', $selected = null, $class = null, $id = null) {
$idclause = (!is_null($id)) ? $id : '';
// Pull the autoglossonym array from the localizations translation file
$countries_array = trans('localizations.countries');
$select = '<select name="'.$name.'" class="'.$class.'" style="width:100%" '.$idclause.' aria-label="'.$name.'" data-placeholder="'.trans('localizations.select_country').'" data-allow-clear="true" data-tags="true">';
$select .= '<option value="" role="option">'.trans('localizations.select_country').'</option>';
foreach ($countries_array as $abbr => $country) {
// We have to handle it this way to handle deprecation warnings since you can't strtoupper on null
if ($abbr!='') {
$abbr = strtoupper($abbr);
}
// Loop through the countries configured in the localization file
$select .= '<option value="' . $abbr . '" role="option" ' . (($selected == $abbr) ? ' selected="selected" aria-selected="true"' : ' aria-selected="false"') . '>' . $country . '</option> ';
}
// If the country value doesn't exist in the array, add it as a new option and select it so we don't drop that data
if (!array_key_exists($selected, $countries_array)) {
$select .= '<option value="' . e($selected) . '" selected="selected" role="option" aria-selected="true">' . e($selected) .' *</option> ';
}
$select .= '</select>';
return $select;
});
/**
* Barcode macro
* Generates the dropdown menu of available 1D barcodes
*/
Form::macro('alt_barcode_types', function ($name = 'alt_barcode', $selected = null, $class = null) {
$barcode_types = [
'C128',
'C39',
'PDF417',
'EAN5',
'EAN13',
'UPCA',
'UPCE',
];
$select = '<select name="'.$name.'" class="'.$class.'" aria-label="'.$name.'">';
foreach ($barcode_types as $barcode_type) {
$select .= '<option value="'.$barcode_type.'"'.($selected == $barcode_type ? ' selected="selected" role="option" aria-selected="true"' : ' aria-selected="false"').'>'.$barcode_type.'</option> ';
}
$select .= '</select>';
return $select;
});
/**
* Barcode macro
* Generates the dropdown menu of available 2D barcodes
*/
Form::macro('barcode_types', function ($name = 'barcode_type', $selected = null, $class = null) {
$barcode_types = [
'QRCODE',
'DATAMATRIX',
];
$select = '<select name="'.$name.'" class="'.$class.'" aria-label="'.$name.'">';
foreach ($barcode_types as $barcode_type) {
$select .= '<option value="'.$barcode_type.'"'.($selected == $barcode_type ? ' selected="selected" role="option" aria-selected="true"' : ' aria-selected="false"').'>'.$barcode_type.'</option> ';
}
$select .= '</select>';
return $select;
});
Form::macro('username_format', function ($name = 'username_format', $selected = null, $class = null) {
$formats = [
'firstname.lastname' => trans('admin/settings/general.username_formats.firstname_lastname_format'),
'firstname' => trans('admin/settings/general.username_formats.first_name_format'),
'lastname' => trans('admin/settings/general.username_formats.last_name_format'),
'filastname' => trans('admin/settings/general.username_formats.filastname_format'),
'lastnamefirstinitial' => trans('admin/settings/general.username_formats.lastnamefirstinitial_format'),
'firstname_lastname' => trans('admin/settings/general.username_formats.firstname_lastname_underscore_format'),
'firstinitial.lastname' => trans('admin/settings/general.username_formats.firstinitial_lastname'),
'lastname_firstinitial' => trans('admin/settings/general.username_formats.lastname_firstinitial'),
'lastname.firstinitial' => trans('admin/settings/general.username_formats.lastname_dot_firstinitial_format'),
'firstnamelastname' => trans('admin/settings/general.username_formats.firstnamelastname'),
'firstnamelastinitial' => trans('admin/settings/general.username_formats.firstnamelastinitial'),
'lastname.firstname' => trans('admin/settings/general.username_formats.lastnamefirstname'),
];
$select = '<select name="'.$name.'" class="'.$class.'" style="width: 100%" aria-label="'.$name.'">';
foreach ($formats as $format => $label) {
$select .= '<option value="'.$format.'"'.($selected == $format ? ' selected="selected" role="option" aria-selected="true"' : ' aria-selected="false"').'>'.$label.'</option> '."\n";
}
$select .= '</select>';
return $select;
});

View File

@@ -288,9 +288,9 @@
@if ((isset($infoPanelObj->parent)) && ($infoPanelObj->parent))
@if ((isset($infoPanelObj->parent)) && $infoPanelObj->parent))
<x-info-element icon_type="parent" title="{{ trans('admin/locations/table.parent') }}">
<a href="{{ route('locations.show', $infoPanelObj->parent->id) }}">{{ $infoPanelObj->parent->display_name }}</a>
{{ $infoPanelObj->parent->display_name }}
</x-info-element>
@endif

View File

@@ -6,7 +6,7 @@
])
@if (!$slot->isEmpty())
<li {{ $attributes->merge(['class' => 'list-group-item']) }} id="{{ strtolower(str_slug($title)) }}">
<li {{ $attributes->merge(['class' => 'list-group-item']) }}>
@if ($icon_type)
<x-icon type="{{ $icon_type }}" :title="$title" class="fa-fw" style="{{ 'color: '.$icon_color.' !important' ?? '' }}" />

View File

@@ -1,35 +0,0 @@
@props([
'name' => 'country',
'selected' => null,
])
@php
$countries_array = trans('localizations.countries');
@endphp
<select
name="{{ $name }}"
{{ $attributes->merge(['class' => 'select2']) }}
aria-label="{{ $name }}"
data-placeholder="{{ trans('localizations.select_country') }}"
data-allow-clear="true"
>
@foreach($countries_array as $abbreviation => $country)
@php
// We have to handle it this way to handle deprecation warnings since you can't strtoupper on null
if ($abbreviation!='') {
$abbreviation = strtoupper($abbreviation);
}
@endphp
<option value="{{ $abbreviation }}" {{ $selected === $abbreviation ? 'selected' : '' }}>
{{ $country }}
</option>
@endforeach
{{-- If the country value doesn't exist in the array, add it as a new option and select it so we don't drop that data --}}
@if (!array_key_exists($selected, $countries_array)) {
<option value="{{ e($selected) }}" selected="selected" role="option" aria-selected="true"> {{ e($selected) }} *</option> ';
@endif
</select>

View File

@@ -1,27 +0,0 @@
@php
$formats = [
'firstname.lastname' => trans('admin/settings/general.username_formats.firstname_lastname_format'),
'firstname' => trans('admin/settings/general.username_formats.first_name_format'),
'lastname' => trans('admin/settings/general.username_formats.last_name_format'),
'filastname' => trans('admin/settings/general.username_formats.filastname_format'),
'lastnamefirstinitial' => trans('admin/settings/general.username_formats.lastnamefirstinitial_format'),
'firstname_lastname' => trans('admin/settings/general.username_formats.firstname_lastname_underscore_format'),
'firstinitial.lastname' => trans('admin/settings/general.username_formats.firstinitial_lastname'),
'lastname_firstinitial' => trans('admin/settings/general.username_formats.lastname_firstinitial'),
'lastname.firstinitial' => trans('admin/settings/general.username_formats.lastname_dot_firstinitial_format'),
'firstnamelastname' => trans('admin/settings/general.username_formats.firstnamelastname'),
'firstnamelastinitial' => trans('admin/settings/general.username_formats.firstnamelastinitial'),
'lastname.firstname' => trans('admin/settings/general.username_formats.lastnamefirstname'),
];
@endphp
<x-input.select {{ $attributes }}>
@foreach($formats as $format => $label)
<option
value="{{ $format }}"
@selected($selected == $format)
>
{{ $label }}
</option>
@endforeach
</x-input.select>

View File

@@ -20,7 +20,7 @@
@if (($options) && (count($options) > 0))
<select class="redirect-options form-control select2" data-minimum-results-for-search="Infinity" name="redirect_option" style="min-width: 250px"{{ ($disabled_select ? ' disabled' : '') }}>
@foreach ($options as $key => $value)
<option value="{{ $key }}"{{ session('redirect_option') == $key ? ' selected' : ''}}>
<option value="{{ $key }}"{{ Session::get('redirect_option') == $key ? ' selected' : ''}}>
{{ $value }}
</option>
@endforeach

View File

@@ -5,7 +5,6 @@
'api_url' => null,
'show_column_search' => false,
'show_advanced_search' => false,
'show_search' => true,
'fixed_number' => false,
'fixed_right_number' => false,
'sort_order' => 'asc',
@@ -27,7 +26,6 @@
id="{{ $name }}ListingTable"
data-show-columns-search="{{ $show_column_search }}"
data-show-advanced-search="{{ $show_advanced_search }}"
data-search="{{ $show_search }}"
data-footer-style="footerStyle"
data-show-footer="true"

View File

@@ -51,7 +51,7 @@
@foreach($custom_fieldsets AS $fieldset)
<tr>
<td>
<a href="{{ route('fieldsets.show', ['fieldset' => $fieldset->id]) }}">{{ $fieldset->name }}</a>
{{ link_to_route("fieldsets.show",$fieldset->name,['fieldset' => $fieldset->id]) }}
</td>
<td>
{{ $fieldset->fields->count() }}

View File

@@ -287,7 +287,7 @@
$("#selected_status_status").removeClass('text-danger');
$("#selected_status_status").addClass('text-success');
$("#selected_status_status").html('<x-icon type="checkmark" /> {{ trans_choice('admin/hardware/form.asset_deployable', 1)}}');
$("#selected_status_status").html('<x-icon type="checkmark" /> {{ trans('admin/hardware/form.asset_deployable')}}');
} else {

View File

@@ -158,7 +158,7 @@
if (data.status == 'success') {
$('#audited tbody').prepend("<tr class='success'><td>" + data.payload.asset_tag + "</td><td>" + data.messages + "</td><td>" + data.payload.status_label + " (" + data.payload.status_type + ")</td><td>" + data.payload.note + "</td><td><i class='fas fa-check text-success' style='font-size:18px;'></i></td></tr>");
@if ($user?->enable_sounds)
@if ($user->enable_sounds)
var audio = new Audio('{{ config('app.url') }}/sounds/success.mp3');
audio.play()
@endif
@@ -182,7 +182,7 @@
});
function handleAuditFail (data, asset_tag) {
@if ($user?->enable_sounds)
@if ($user->enable_sounds)
var audio = new Audio('{{ config('app.url') }}/sounds/error.mp3');
audio.play()
@endif

View File

@@ -553,7 +553,7 @@
{!! $asset->checkInvalidNextAuditDate() ? '<i class="fas fa-exclamation-triangle text-orange" aria-hidden="true"></i>' : '' !!}
{{ Helper::getFormattedDateObject($audit_log->created_at, 'datetime', false) }}
@if ($audit_log->user)
(<a href="{{ route('users.show', $audit_log->user->id) }}">{{ $audit_log->user->display_name }}</a>)
({{ link_to_route('users.show', $audit_log->user->display_name, [$audit_log->user->id]) }})
@endif
</div>

View File

@@ -154,9 +154,6 @@
color: var(--link-hover) !important;
}
label.form-control {
color: var(--color-fg) !important;
}
.footer-links a {
color: var(--link-color) !important;
@@ -945,11 +942,9 @@
input[type="email"]:required,
input[type="password"]:required,
input[type="tel"]:required,
select:required,
input:required,
textarea:required
{
border-right: 5px solid orange !important;
border-right: 5px solid var(--text-warning) !important;
}
.bootstrap-table .fixed-table-container .table tbody tr.selected td {
@@ -1133,15 +1128,6 @@
</li>
@endcan
@can('index', \App\Models\User::class)
<li aria-hidden="true"{!! (request()->is('users*') ? ' class="active"' : '') !!}>
<a href="{{ route('users.index') }}" {{$snipeSettings->shortcuts_enabled == 1 ? "accesskey=6" : ''}} tabindex="-1" data-tooltip="true" data-placement="bottom" data-title="{{ trans('general.users') }}">
<x-icon type="users" class="fa-fw" />
<span class="sr-only">{{ trans('general.users') }}</span>
</a>
</li>
@endcan
@can('index', \App\Models\Asset::class)
<li>
<form class="navbar-form navbar-left form-inline" role="search" action="{{ route('findbytag/hardware') }}" method="get">
@@ -1248,7 +1234,7 @@
<ul class="dropdown-menu">
<!-- User image -->
@can('self.profile')
<li {!! (request()->is('account/view-assets') ? ' class="active"' : '') !!}>
<li {!! (request()->is('account/profile') ? ' class="active"' : '') !!}>
<a href="{{ route('view-assets') }}">
<x-icon type="checkmark" class="fa-fw" />
{{ trans('general.viewassets') }}
@@ -1272,7 +1258,7 @@
</li>
@endcan
<li {!! (request()->is('account/password') ? ' class="active"' : '') !!}>
<li>
<a href="{{ route('profile') }}">
<x-icon type="user" class="fa-fw" />
{{ trans('general.editprofile') }}
@@ -1281,7 +1267,7 @@
@can('self.profile')
@if (Auth::user()->ldap_import!='1')
<li {!! (request()->is('account/profile') ? ' class="active"' : '') !!}>
<li>
<a href="{{ route('account.password.index') }}">
<x-icon type="password" class="fa-fw" />
{{ trans('general.changepassword') }}
@@ -1297,7 +1283,7 @@
</li>
@can('self.api')
<li {!! (request()->is('account/api') ? ' class="active"' : '') !!}>
<li>
<a href="{{ route('user.api') }}">
<x-icon type="api-key" class="fa-fw" />
{{ trans('general.manage_api_keys') }}
@@ -1345,7 +1331,7 @@
<!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu" data-widget="tree" {{ \App\Helpers\Helper::determineLanguageDirection() == 'rtl' ? 'style="margin-right:12px' : '' }}>
@can('admin')
<li {!! (\request()->route()->getName()=='home' ? ' class="active"' : '') !!} class="firstnav">
<li {!! (\Request::route()->getName()=='home' ? ' class="active"' : '') !!} class="firstnav">
<a href="{{ route('home') }}">
<x-icon type="dashboard" class="fa-fw" />
<span>{{ trans('general.dashboard') }}</span>
@@ -1360,7 +1346,7 @@
<x-icon type="angle-left" class="pull-right fa-fw"/>
</a>
<ul class="treeview-menu">
<li {!! (!request()->query('status') && (request()->is('hardware')) ? ' class="active"' : '') !!}>
<li>
<a href="{{ url('hardware') }}">
<x-icon type="circle" class="text-grey fa-fw"/>
{{ trans('general.list_all') }}
@@ -1383,48 +1369,48 @@
@endif
<li id="deployed-sidenav-option" {!! (request()->query('status') == 'Deployed' ? ' class="active"' : '') !!}>
<li id="deployed-sidenav-option" {!! (Request::query('status') == 'Deployed' ? ' class="active"' : '') !!}>
<a href="{{ url('hardware?status=Deployed') }}">
<x-icon type="circle" class="text-blue fa-fw" />
{{ trans('general.deployed') }}
<span class="badge">{{ (isset($total_deployed_sidebar)) ? $total_deployed_sidebar : '' }}</span>
</a>
</li>
<li id="rtd-sidenav-option"{!! (request()->query('status') == 'RTD' ? ' class="active"' : '') !!}>
<li id="rtd-sidenav-option"{!! (Request::query('status') == 'RTD' ? ' class="active"' : '') !!}>
<a href="{{ url('hardware?status=RTD') }}">
<x-icon type="circle" class="text-green fa-fw" />
{{ trans('general.ready_to_deploy') }}
<span class="badge">{{ (isset($total_rtd_sidebar)) ? $total_rtd_sidebar : '' }}</span>
</a>
</li>
<li id="pending-sidenav-option"{!! (request()->query('status') == 'Pending' ? ' class="active"' : '') !!}><a href="{{ url('hardware?status=Pending') }}">
<li id="pending-sidenav-option"{!! (Request::query('status') == 'Pending' ? ' class="active"' : '') !!}><a href="{{ url('hardware?status=Pending') }}">
<x-icon type="circle" class="text-orange fa-fw" />
{{ trans('general.pending') }}
<span class="badge">{{ (isset($total_pending_sidebar)) ? $total_pending_sidebar : '' }}</span>
</a>
</li>
<li id="undeployable-sidenav-option"{!! (request()->query('status') == 'Undeployable' ? ' class="active"' : '') !!} ><a
<li id="undeployable-sidenav-option"{!! (Request::query('status') == 'Undeployable' ? ' class="active"' : '') !!} ><a
href="{{ url('hardware?status=Undeployable') }}">
<x-icon type="x" class="text-red fa-fw" />
{{ trans('general.undeployable') }}
<span class="badge">{{ (isset($total_undeployable_sidebar)) ? $total_undeployable_sidebar : '' }}</span>
</a>
</li>
<li id="byod-sidenav-option"{!! (request()->query('status') == 'byod' ? ' class="active"' : '') !!}><a
<li id="byod-sidenav-option"{!! (Request::query('status') == 'byod' ? ' class="active"' : '') !!}><a
href="{{ url('hardware?status=byod') }}">
<x-icon type="x" class="text-red fa-fw" />
{{ trans('general.byod') }}
<span class="badge">{{ (isset($total_byod_sidebar)) ? $total_byod_sidebar : '' }}</span>
</a>
</li>
<li id="archived-sidenav-option"{!! (request()->query('status') == 'Archived' ? ' class="active"' : '') !!}><a
<li id="archived-sidenav-option"{!! (Request::query('status') == 'Archived' ? ' class="active"' : '') !!}><a
href="{{ url('hardware?status=Archived') }}">
<x-icon type="x" class="text-red fa-fw" />
{{ trans('admin/hardware/general.archived') }}
<span class="badge">{{ (isset($total_archived_sidebar)) ? $total_archived_sidebar : '' }}</span>
</a>
</li>
<li id="requestable-sidenav-option"{!! (request()->query('status') == 'Requestable' ? ' class="active"' : '') !!}><a
<li id="requestable-sidenav-option"{!! (Request::query('status') == 'Requestable' ? ' class="active"' : '') !!}><a
href="{{ url('hardware?status=Requestable') }}">
<x-icon type="checkmark" class="text-blue fa-fw" />
{{ trans('admin/hardware/general.requestable') }}

View File

@@ -79,7 +79,6 @@
<x-slot:content>
<x-table
show_search="false"
api_url="{{ route('api.licenses.seats.index', [$license->id, 'status' => 'available']) }}"
:presenter="\App\Presenters\LicensePresenter::dataTableLayoutSeats()"
export_filename="export-{{ str_slug($license->name) }}-available-{{ date('Y-m-d') }}"
@@ -129,7 +128,7 @@
<x-slot:buttons>
<x-button.checkout permission="checkout" :item="$license" :route="route('licenses.checkout', $license->id)" />
<x-button.checkout permission="checkout" :item="$license" :route="route('licenses.freecheckout', $license->id)" />
<x-button.edit :item="$license" :route="route('licenses.edit', $license->id)" />
<x-button.clone :item="$license" :route="route('clone/license', $license->id)" />
<x-button.delete :item="$license" />

View File

@@ -32,13 +32,7 @@
<div class="dynamic-form-row">
<div class="col-md-3 col-xs-12 country"><label for="modal-country">{{ trans('general.country') }}:</label></div>
<div class="col-md-9 col-xs-12">
<x-input.country-select
name="country"
:selected="old('country')"
id="modal-country"
/>
</div>
<div class="col-md-9 col-xs-12">{!! Form::countries('country', old('country'), 'select2 country',"modal-country") !!}</div>
</div>
</form>
</div>

View File

@@ -46,7 +46,7 @@
</div> <!-- /.modal-body-->
<div class="modal-footer">
<a href="#" class="pull-left" data-dismiss="modal">{{ trans('button.cancel') }}</a>
<button type="submit" class="btn btn-theme" formnovalidate>{{ trans('button.upload') }}</button>
<button type="submit" class="btn btn-theme">{{ trans('button.upload') }}</button>
</div>
</form>
</div>

View File

@@ -34,10 +34,7 @@
<div class="form-group {{ $errors->has('country') ? ' has-error' : '' }}">
<label for="country" class="col-md-3 control-label">{{ trans('general.country') }}</label>
<div class="col-md-7">
<x-input.country-select
name="country"
:selected="old('country', $item->country)"
/>
{!! Form::countries('country', old('country', $item->country), 'select2') !!}
<p class="help-block">{{ trans('general.countries_manually_entered_help') }}</p>
{!! $errors->first('country', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>

View File

@@ -102,12 +102,7 @@
<label for="username_format" class="col-md-3 control-label">{{ trans('admin/settings/general.username_formats.username_format') }}</label>
<div class="col-md-8">
<x-input.username-select
name="username_format"
:selected="old('username_format', $setting->username_format)"
style="width: 100%"
aria-label="username_format"
/>
{!! Form::username_format('username_format', old('username_format', $setting->username_format), 'select2') !!}
{!! $errors->first('username_format', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
<p class="help-block">

View File

@@ -80,7 +80,19 @@
<!-- start components tab pane -->
@can('view', \App\Models\Component::class)
<x-tabs.pane name="components" class="{{ $supplier->components->count() == 0 ? 'hidden-print' : '' }}">
<x-table.components name="components" :route="route('api.components.index', ['supplier_id' => $supplier->id])" />
<x-slot:header>
{{ trans('general.components') }}
</x-slot:header>
<x-table
show_advanced_search="true"
buttons="componentButtons"
api_url="{{ route('api.components.index', ['supplier_id' => $supplier->id]) }}"
:presenter="\App\Presenters\ComponentPresenter::dataTableLayout()"
export_filename="export-{{ str_slug($supplier->name) }}-components-{{ date('Y-m-d') }}"
/>
</x-tabs.pane>
@endcan
<!-- end components tab pane -->

View File

@@ -330,7 +330,7 @@
<!-- Company -->
@if ((Gate::allows('canEditAuthFields', $user)) && (\App\Models\Company::canManageUsersCompanies()))
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.company'), 'fieldname' => 'company_id'])
@include ('partials.forms.edit.company-select', ['translated_name' => trans('general.select_company'), 'fieldname' => 'company_id'])
@else
@if ($user->company)
<div class="form-group">
@@ -502,11 +502,7 @@
<div class="form-group{{ $errors->has('country') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="country">{{ trans('general.country') }}</label>
<div class="col-md-6">
<x-input.country-select
name="country"
:selected="old('country', $user->country)"
class="col-md-12"
/>
{!! Form::countries('country', old('country', $user->country), 'col-md-12 select2') !!}
<p class="help-block">{{ trans('general.countries_manually_entered_help') }}</p>
{!! $errors->first('country', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}

View File

@@ -8,7 +8,7 @@ use Illuminate\Support\Facades\Route;
*/
Route::group(['prefix' => 'accessories', 'middleware' => ['auth']], function () {
Route::get(
'{accessory}/checkout',
'{accessoryID}/checkout',
[Accessories\AccessoryCheckoutController::class, 'create']
)->name('accessories.checkout.show');
@@ -31,7 +31,7 @@ Route::group(['prefix' => 'accessories', 'middleware' => ['auth']], function ()
[Accessories\AccessoriesController::class, 'getClone']
)->name('clone/accessories');
Route::post('{accessory}/clone',
Route::post('{accessoryId}/clone',
[Accessories\AccessoriesController::class, 'postCreate']
);

View File

@@ -18,7 +18,7 @@ Route::group(['prefix' => 'locations', 'middleware' => ['auth']], function () {
Route::post(
'{location}/restore',
[LocationsController::class, 'postRestore']
)->name('locations.restore')->withTrashed();
)->name('locations.restore');
Route::get('{locationId}/clone',
[LocationsController::class, 'getClone']

View File

@@ -52,12 +52,20 @@ Route::group(['prefix' => 'users', 'middleware' => ['auth']], function () {
)->name('users.clone.store')->withTrashed();
Route::post(
'{user}/restore',
'{userId}/restore',
[
Users\UsersController::class,
'getRestore'
]
)->name('users.restore.store')->withTrashed();
)->name('users.restore.store');
Route::get(
'{userId}/unsuspend',
[
Users\UsersController::class,
'getUnsuspend'
]
)->name('unsuspend/user');
Route::post(
'{userId}/password',