Compare commits

..

11 Commits

Author SHA1 Message Date
Jeremy Price
e1c7cc38f1 buildx action docs say we don't need the checkout action, let's test that 2025-05-08 10:50:24 +02:00
Jeremy Price
85a5b9a872 replace repo because we fixed other formatting error, i think 2025-05-08 10:04:36 +02:00
Jeremy Price
2ebe2080c8 fix source image names 2025-05-07 17:50:33 +02:00
Jeremy Price
747f780604 try master 2025-05-07 17:10:57 +02:00
Jeremy Price
afeefbee38 try it w/o a version? 2025-05-07 17:06:02 +02:00
Jeremy Price
4fef2b5926 fix docker-manifest-action version because it's wrong in their f-ing readme 2025-05-07 17:00:04 +02:00
Jeremy Price
c79fd12842 actually set branch name 2025-05-07 16:46:23 +02:00
Jeremy Price
5a875afe12 removing wonky outputs and trying different tag format... seems wheird? 2025-05-07 16:19:52 +02:00
Jeremy Price
4134bd5fc6 fix indentation 2025-05-07 16:12:30 +02:00
Jeremy Price
1188d2fcba lets try to combine arches with a manifest, first the simple way 2025-05-07 16:10:57 +02:00
Jeremy Price
8e7b2a0678 swap around docker builds to see if we can coolate tags & architectures 2025-05-06 17:00:05 +02:00
579 changed files with 1363 additions and 11937 deletions

View File

@@ -10,10 +10,10 @@ name: Codacy Security Scan
on:
push:
branches: [ develop ]
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
branches: [ master ]
schedule:
- cron: '36 23 * * 3'

View File

@@ -1,5 +1,5 @@
# Snipe-IT (Alpine) Docker image build for hub.docker.com
name: Docker images (Alpine)
# Snipe-IT Docker image build for hub.docker.com
name: Docker Alpine images
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
@@ -8,6 +8,7 @@ on:
branches:
- master
- develop
- dockerhub-testing
tags:
- 'v**'
# Allows you to run this workflow manually from the Actions tab
@@ -19,7 +20,7 @@ permissions:
contents: read
jobs:
docker:
docker-alpine-intel:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
runs-on: ubuntu-latest
@@ -77,7 +78,72 @@ jobs:
with:
context: .
file: ./Dockerfile.alpine
platforms: linux/amd64,linux/arm64
platforms: linux/amd64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
docker-alpine-arm:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
runs-on: ubuntu-24.04-arm
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine
type=ref,event=tag,suffix=-alpine
type=semver,pattern=v{{major}}-latest-alpine
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
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@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'snipe/snipe-it' image
###############################################
# https://github.com/docker/metadata-action
# 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@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.alpine
platforms: linux/arm64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}

View File

@@ -1,5 +1,5 @@
# Snipe-IT Docker image build for hub.docker.com
name: Docker images (Ubuntu)
name: Docker Ubuntu images
# Run this Build for all pushes to 'master' or develop branch, or tagged releases.
# Also run for PRs to ensure PR doesn't break Docker build process
@@ -8,6 +8,7 @@ on:
branches:
- master
- develop
- dockerhub-testing
tags:
- 'v**'
# Allows you to run this workflow manually from the Actions tab
@@ -19,10 +20,10 @@ permissions:
contents: read
jobs:
docker:
docker-ubuntu-arm:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
@@ -31,6 +32,7 @@ jobs:
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=raw,value={{branch}}-ubuntu-arm
type=ref,event=tag
type=semver,pattern=v{{major}}-latest
# Define default tag "flavor" for docker/metadata-action per
@@ -40,10 +42,6 @@ jobs:
latest=false
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -77,10 +75,96 @@ jobs:
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
platforms: linux/arm64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
docker-ubuntu-intel:
# Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it'
if: github.repository == 'grokability/snipe-it'
runs-on: ubuntu-latest
env:
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
# For a new commit on default branch (master), use the literal tag 'latest' on Docker image.
# For a new commit on other branches, use the branch name as the tag for Docker image.
# For a new tag, copy that tag name as the tag for Docker image.
IMAGE_TAGS: |
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
type=raw,value={{branch}}-ubuntu-intel
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
type=ref,event=tag
type=semver,pattern=v{{major}}-latest
# Define default tag "flavor" for docker/metadata-action per
# https://github.com/docker/metadata-action#flavor-input
# We turn off 'latest' tag by default.
TAGS_FLAVOR: |
latest=false
steps:
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
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@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
###############################################
# Build/Push the 'snipe/snipe-it' image
###############################################
# https://github.com/docker/metadata-action
# 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@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
flavor: ${{ env.TAGS_FLAVOR }}
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
# but we ONLY do an image push to DockerHub if it's NOT a PR
push: ${{ github.event_name != 'pull_request' }}
# Use tags / labels provided by 'docker/metadata-action' above
tags: ${{ steps.meta_build.outputs.tags }}
labels: ${{ steps.meta_build.outputs.labels }}
combinator:
name: combine multiple arches into a single "image"
env:
BRANCH: ${{github.ref_name }}
needs: [docker-ubuntu-intel, docker-ubuntu-arm]
runs-on: ubuntu-latest
steps:
# 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@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: combine manifests
uses: Noelware/docker-manifest-action@master
with:
tags: snipe/snipe-it:${{ env.BRANCH }}-combined
inputs: snipe/snipe-it:${{ env.BRANCH }}-ubuntu-intel,snipe/snipe-it:${{ env.BRANCH }}-ubuntu-arm
push: true

View File

@@ -1,6 +1,6 @@
![snipe-it-by-grok](https://github.com/grokability/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/804dd1beb14a41f38810ab77d64fc4fc)](https://app.codacy.com/gh/grokability/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests.yml)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests.yml)
[![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk)
## Snipe-IT - Open Source Asset Management System

View File

@@ -249,7 +249,6 @@ class RestoreFromBackup extends Command
'storage/private_uploads/consumables',
'storage/private_uploads/eula-pdfs',
'storage/private_uploads/imports',
'storage/private_uploads/locations',
'storage/private_uploads/licenses',
'storage/private_uploads/signatures',
'storage/private_uploads/users',

View File

@@ -61,12 +61,14 @@ class Handler extends ExceptionHandler
public function render($request, Throwable $e)
{
// CSRF token mismatch error
if ($e instanceof \Illuminate\Session\TokenMismatchException) {
return redirect()->back()->with('error', trans('general.token_expired'));
}
// Invalid JSON exception
// TODO: don't understand why we have to do this when we have the invalidJson() method, below, but, well, whatever
if ($e instanceof JsonException) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Invalid JSON'), 422);
}
@@ -86,9 +88,8 @@ class Handler extends ExceptionHandler
return redirect()->back()->withInput()->with('error', trans('validation.date', ['attribute' => 'date']));
}
// Handle API requests that fail
if ($request->ajax() || $request->wantsJson() || ($request->route() && $request->route()->named('api.files.*'))) {
if ($request->ajax() || $request->wantsJson()) {
// Handle API requests that fail because Carbon cannot parse the date on validation (when a submitted date value is definitely not a date)
if ($e instanceof InvalidFormatException) {
@@ -101,7 +102,6 @@ class Handler extends ExceptionHandler
return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200);
}
// Handle API requests that fail because of an HTTP status code and return a useful error message
if ($this->isHttpException($e)) {
@@ -116,24 +116,12 @@ class Handler extends ExceptionHandler
return response()->json(Helper::formatStandardApiResponse('error', null, 'Method not allowed'), 405);
default:
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode), $statusCode);
}
}
// This handles API validation exceptions that happen at the Form Request level, so they
// never even get to the controller where we normally nicely format JSON responses
if ($e instanceof ValidationException) {
$response = $this->invalidJson($request, $e);
return response()->json(Helper::formatStandardApiResponse('error', null, $e->errors()), 200);
}
// return response()->json(Helper::formatStandardApiResponse('error', null, 'Undefined exception'), 200);
}
// 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) {

View File

@@ -722,8 +722,8 @@ class Helper
// The check and message that the user is still using the deprecated version
$deprecations = [
'ms_teams_deprecated' => array(
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows') && (Setting::getSettings()->webhook_selected === 'microsoft'),
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Dec 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'),
'message' => 'The Microsoft Teams webhook URL being used will be deprecated Jan 31st, 2025. <a class="btn btn-primary" href="' . route('settings.slack.index') . '">Change webhook endpoint</a>'),
];
// if item of concern is being used and its being used with the deprecated values return the notification array.
@@ -1487,7 +1487,6 @@ class Helper
$redirect_option = Session::get('redirect_option');
$checkout_to_type = Session::get('checkout_to_type');
$checkedInFrom = Session::get('checkedInFrom');
$other_redirect = Session::get('other_redirect');
// return to index
if ($redirect_option == 'index') {
@@ -1536,16 +1535,6 @@ class Helper
return route('hardware.show', $request->assigned_asset ?? $checkedInFrom);
}
}
// return to somewhere else
if ($redirect_option == 'other_redirect') {
switch ($other_redirect) {
case 'audit':
return route('assets.audit.due');
}
}
return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'));
}

View File

@@ -48,29 +48,17 @@ class StorageHelper
'avif',
'webp',
'png',
'gif',
];
// The file exists and is allowed to be displayed inline
if (Storage::exists($file_with_path) && (in_array(pathinfo($file_with_path, PATHINFO_EXTENSION), $allowed_inline))) {
return true;
}
return false;
}
public static function getFiletype($file_with_path) {
// The file exists and is allowed to be displayed inline
if (Storage::exists($file_with_path)) {
return pathinfo($file_with_path, PATHINFO_EXTENSION);
}
return null;
}
/**
* Decide whether to show the file inline or download it.
*/

View File

@@ -0,0 +1,200 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use App\Http\Transformers\UploadedFilesTransformer;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Illuminate\Http\Request;
/**
* This class controls file related actions related
* to assets for the Snipe-IT Asset Management application.
*
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
*
* @version v1.0
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
class AssetFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function store(UploadFileRequest $request, $assetId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $asset = Asset::find($assetId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $asset);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assets')) {
Storage::makeDirectory('private_uploads/assets', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assets/','hardware-'.$asset->id, $file);
$asset->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.upload.success')));
}
// We only reach here if no files were included in the POST, so tell the user this
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function list(Asset $asset, Request $request) : JsonResponse | array
{
$this->authorize('view', $asset);
$allowed_columns =
[
'id',
'filename',
'eol',
'notes',
'created_at',
'updated_at',
];
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')->where('item_type', '=', Asset::class)->where('item_id', '=', $asset->id);
if ($request->filled('search')) {
$files = $files->TextSearch($request->input('search'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $files->count()) ? $files->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') : 'created_at';
$files = $files->orderBy($sort, $order);
$files = $files->skip($offset)->take($limit)->get();
return (new UploadedFilesTransformer())->transformFiles($files, $files->count());
}
/**
* Check for permissions and display the file.
*
* @param int $assetId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function show(Asset $asset, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// the asset is valid
if (isset($asset->id)) {
$this->authorize('view', $asset);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assets/'.$log->filename;
Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
// Check the file actually exists on the filesystem
if (! Storage::exists($file)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.does_not_exist', ['id' => $fileId])), 404);
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetId
* @param int $fileId
* @since [v6.0]
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
public function destroy(Asset $asset, $fileId = null) : JsonResponse
{
$rel_path = 'private_uploads/assets';
// the asset is valid
if (isset($asset->id)) {
$this->authorize('update', $asset);
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.deletefile.success')), 200);
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.deletefile.error')), 500);
}
}

View File

@@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\StorageHelper;
use Illuminate\Support\Facades\Storage;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\AssetModel;
use App\Models\Actionlog;
use App\Http\Requests\UploadFileRequest;
use App\Http\Transformers\AssetModelsTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
/**
* This class controls file related actions related
* to assets for the Snipe-IT Asset Management application.
*
* Based on the Assets/AssetFilesController by A. Gianotto <snipe@snipe.net>
*
* @version v1.0
* @author [T. Scarsbrook] [<snipe@scarzybrook.co.uk>]
*/
class AssetModelFilesController extends Controller
{
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetModelId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// Make sure we are allowed to update this asset
$this->authorize('update', $assetModel);
if ($request->hasFile('file')) {
// If the file storage directory doesn't exist; create it
if (! Storage::exists('private_uploads/assetmodels')) {
Storage::makeDirectory('private_uploads/assetmodels', 775);
}
// Loop over the attached files and add them to the asset
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file);
$assetModel->logUpload($file_name, e($request->get('notes')));
}
// All done - report success
return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success')));
}
// We only reach here if no files were included in the POST, so tell the user this
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500);
}
/**
* List the files for an asset.
*
* @param int $assetmodel
* @since [v7.0.12]
* @author [r-xyz]
*/
public function list($assetmodel_id) : JsonResponse | array
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetmodel_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
$assetmodel = AssetModel::with('uploads')->find($assetmodel_id);
$this->authorize('view', $assetmodel);
return (new AssetModelsTransformer)->transformAssetModelFiles($assetmodel, $assetmodel->uploads()->count());
}
/**
* Check for permissions and display the file.
*
* @param int $assetModelId
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v7.0.12]
* @author [r-xyz]
*/
public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('view', $assetModel);
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404);
}
// Form the full filename with path
$file = 'private_uploads/assetmodels/'.$log->filename;
Log::debug('Checking for '.$file);
if ($log->action_type == 'audit') {
$file = 'private_uploads/audits/'.$log->filename;
}
// Check the file actually exists on the filesystem
if (! Storage::exists($file)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404);
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download($file, $log->filename, $headers);
}
return StorageHelper::downloader($file);
}
// Send back an error message
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500);
}
/**
* Delete the associated file
*
* @param int $assetModelId
* @param int $fileId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function destroy($assetModelId = null, $fileId = null) : JsonResponse
{
// Start by checking if the asset being acted upon exists
if (! $assetModel = AssetModel::find($assetModelId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404);
}
$rel_path = 'private_uploads/assetmodels';
// the asset is valid
if (isset($assetModel->id)) {
$this->authorize('update', $assetModel);
// Check for the file
$log = Actionlog::find($fileId);
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists($rel_path.'/'.$log->filename)) {
Storage::delete($rel_path.'/'.$log->filename);
}
// Delete the record of the file
$log->delete();
// All deleting done - notify the user of success
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200);
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500);
}
}

View File

@@ -56,7 +56,7 @@ class CategoriesController extends Controller
'notes',
])
->with('adminuser')
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count');
/*
@@ -212,7 +212,7 @@ class CategoriesController extends Controller
public function destroy($id) : JsonResponse
{
$this->authorize('delete', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
if (! $category->isDeletable()) {
return response()->json(

View File

@@ -34,7 +34,7 @@ class LicenseSeatsController extends Controller
if ($request->input('sort') == 'department') {
$seats->OrderDepartments($order);
} else {
$seats->orderBy('updated_at', $order);
$seats->orderBy('id', $order);
}
$total = $seats->count();

View File

@@ -1,242 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\UploadFileRequest;
use App\Http\Transformers\UploadedFilesTransformer;
use App\Models\Accessory;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\License;
use App\Models\Location;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\StreamedResponse;
class UploadedFilesController extends Controller
{
static $map_object_type = [
'accessories' => Accessory::class,
'assets' => Asset::class,
'components' => Component::class,
'consumables' => Consumable::class,
'locations' => Location::class,
'models' => AssetModel::class,
'users' => User::class,
];
static $map_storage_path = [
'accessories' => 'private_uploads/accessories/',
'assets' => 'private_uploads/assets/',
'components' => 'private_uploads/components/',
'consumables' => 'private_uploads/consumables/',
'locations' => 'private_uploads/locations/',
'models' => 'private_uploads/assetmodels/',
'users' => 'private_uploads/users/',
];
static $map_file_prefix= [
'accessories' => 'accessory',
'assets' => 'asset',
'components' => 'component',
'consumables' => 'consumable',
'locations' => 'location',
'models' => 'model',
'users' => 'user',
];
/**
* List the files for an object.
*
* @since [v7.0.12]
* @author [r-xyz]
*/
public function index(Request $request, $object_type, $id) : JsonResponse | array
{
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Columns allowed for sorting
$allowed_columns =
[
'id',
'filename',
'action_type',
'note',
'created_at',
];
$uploads = $object->uploads();
$offset = ($request->input('offset') > $object->count()) ? $object->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';
// 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
if ($request->filled('search')) {
$uploads->where(function ($query) use ($request) {
$query->where('filename', 'LIKE', '%' . $request->input('search') . '%')
->orWhere('note', 'LIKE', '%' . $request->input('search') . '%');
});
}
$uploads = $uploads->skip($offset)->take($limit)->orderBy($sort, $order)->get();
return (new UploadedFilesTransformer())->transformFiles($uploads, $uploads->count());
}
/**
* Accepts a POST to upload a file to the server.
*
* @param \App\Http\Requests\UploadFileRequest $request
* @param int $assetModelId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function store(UploadFileRequest $request, $object_type, $id) : JsonResponse
{
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// If the file storage directory doesn't exist, create it
if (! Storage::exists(self::$map_storage_path[$object_type])) {
Storage::makeDirectory(self::$map_storage_path[$object_type], 775);
}
if ($request->hasFile('file')) {
// Loop over the attached files and add them to the object
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile(self::$map_storage_path[$object_type],self::$map_file_prefix[$object_type].'-'.$object->id, $file);
$files[] = $file_name;
$object->logUpload($file_name, $request->get('notes'));
}
$files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')
->where('item_type', '=', self::$map_object_type[$object_type])
->where('item_id', '=', $id)->whereIn('filename', $files)
->get();
return response()->json(Helper::formatStandardApiResponse('success', (new UploadedFilesTransformer())->transformFiles($files, count($files)), trans_choice('general.file_upload_status.upload.success', count($files))));
}
// No files were submitted
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.nofiles')));
}
/**
* Check for permissions and display the file.
*
* @param AssetModel $model
* @param int $fileId
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @since [v7.0.12]
* @author [r-xyz]
*/
public function show($object_type, $id, $file_id) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse
{
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('view', $object);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Check that the file being requested exists for the asset
if (! $log = Actionlog::whereNotNull('filename')
->where('item_type', AssetModel::class)
->where('item_id', $object->id)->find($file_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_id')), 404);
}
if (! Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.file_not_found'), 200));
}
if (request('inline') == 'true') {
$headers = [
'Content-Disposition' => 'inline',
];
return Storage::download(self::$map_storage_path[$object_type].'/'.$log->filename, $log->filename, $headers);
}
return StorageHelper::downloader(self::$map_storage_path[$object_type].'/'.$log->filename);
}
/**
* Delete the associated file
*
* @param AssetModel $model
* @param int $fileId
* @since [v7.0.12]
* @author [r-xyz]
*/
public function destroy($object_type, $id, $file_id) : JsonResponse
{
\Log::error('destroy called for '.$object_type.' with id '.$id.' and file_id '.$file_id);
$object = self::$map_object_type[$object_type]::find($id);
$this->authorize('update', self::$map_object_type[$object_type]);
if (!$object) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_upload_status.invalid_object')));
}
// Check for the file
$log = Actionlog::find($file_id)->where('item_type', self::$map_object_type[$object_type])
->where('item_id', $object->id)->first();
if ($log) {
// Check the file actually exists, and delete it
if (Storage::exists(self::$map_storage_path[$object_type].'/'.$log->filename)) {
Storage::delete(self::$map_storage_path[$object_type].'/'.$log->filename);
}
// Delete the record of the file
if ($log->delete()) {
return response()->json(Helper::formatStandardApiResponse('success', null, trans_choice('general.file_upload_status.delete.success', 1)), 200);
}
}
// The file doesn't seem to really exist, so report an error
return response()->json(Helper::formatStandardApiResponse('error', null, trans_choice('general.file_upload_status.delete.error', 1)), 500);
}
}

View File

@@ -42,11 +42,12 @@ class AssetCheckinController extends Controller
return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
// Validate custom fields on existing asset
$validator = Validator::make($asset->toArray(), $asset->customFieldValidationRules());
if ($asset->isInvalid()) {
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
if ($validator->fails()) {
return redirect()->route('hardware.edit', $asset)
->withErrors($validator);
}
$target_option = match ($asset->assigned_type) {

View File

@@ -37,13 +37,13 @@ class AssetCheckoutController extends Controller
->with('error', trans('admin/hardware/general.model_invalid_fix'));
}
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
// Validate custom fields on existing asset
$validator = Validator::make($asset->toArray(), $asset->customFieldValidationRules());
if ($asset->isInvalid()) {
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
if ($validator->fails()) {
return redirect()->route('hardware.edit', $asset)
->withErrors($validator);
}
if ($asset->availableForCheckout()) {
return view('hardware/checkout', compact('asset'))

View File

@@ -45,7 +45,7 @@ class AssetFilesController extends Controller
return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.upload.success'));
}
return redirect()->back()->with('error', trans('general.file_upload_status.nofiles'));
return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));
}
/**

View File

@@ -519,7 +519,7 @@ class AssetsController extends Controller
{
$settings = Setting::getSettings();
if ($settings->label2_2d_type !== 'none') {
if (($settings->qr_code == '1') && ($settings->label2_2d_type !== 'none')) {
if ($asset) {
$size = Helper::barcodeDimensions($settings->label2_2d_type);
@@ -882,12 +882,12 @@ class AssetsController extends Controller
$this->authorize('audit', Asset::class);
$settings = Setting::getSettings();
// Validate custom fields on existing asset
$validator = Validator::make($asset->toArray(), $asset->customFieldValidationRules());
// Invoke the validation to see if the audit will complete successfully
$asset->setRules($asset->getRules() + $asset->customFieldValidationRules());
if ($asset->isInvalid()) {
return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors());
if ($validator->fails()) {
return redirect()->route('hardware.edit', $asset)
->withErrors($validator);
}
$dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
@@ -899,10 +899,6 @@ class AssetsController extends Controller
$this->authorize('audit', Asset::class);
session()->put('redirect_option', $request->get('redirect_option'));
session()->put('other_redirect', 'audit');
$originalValues = $asset->getRawOriginal();
$asset->next_audit_date = $request->input('next_audit_date');
@@ -978,7 +974,7 @@ class AssetsController extends Controller
}
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues);
return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.audit.success'));
return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success'));
}
return redirect()->back()->withInput()->withErrors($asset->getErrors());

View File

@@ -484,7 +484,6 @@ class LoginController extends Controller
}
$request->session()->regenerate(true);
$request->session()->forget('2fa_authed');
if ($request->session()->has('password_hash_'.Auth::getDefaultDriver())){
$request->session()->remove('password_hash_'.Auth::getDefaultDriver());

View File

@@ -145,7 +145,7 @@ class CategoriesController extends Controller
{
$this->authorize('delete', Category::class);
// Check if the category exists
if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) {
if (is_null($category = Category::findOrFail($categoryId))) {
return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found'));
}
@@ -155,6 +155,7 @@ class CategoriesController extends Controller
Storage::disk('public')->delete('categories'.'/'.$category->image);
$category->delete();
// Redirect to the locations management page
return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success'));
}

View File

@@ -737,11 +737,6 @@ class ReportsController extends Controller
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
$assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]);
}
if (($request->filled('last_updated_start')) && ($request->filled('last_updated_end'))) {
$assets->whereBetween('assets.updated_at', [$request->input('last_updated_start'), $request->input('last_updated_end')]);
}
if ($request->filled('exclude_archived')) {
$assets->notArchived();
}

View File

@@ -25,24 +25,31 @@ class UserFilesController extends Controller
public function store(UploadFileRequest $request, User $user)
{
$this->authorize('update', $user);
$files = $request->file('file');
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/users')) {
Storage::makeDirectory('private_uploads/users', 775);
}
$file_count = 0;
foreach ($request->file('file') as $file) {
$file_name = $request->handleFile('private_uploads/users/','hardware-'.$user->id, $file);
$user->logUpload($file_name, $request->get('notes'));
$file_count++;
}
return redirect()->back()->withFragment('files')->with('success', trans_choice('general.file_upload_status.upload.success', $file_count));
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);
return redirect()->back()->with('error', trans('general.file_upload_status.nofiles'));
//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);
}
return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success'));
}
}

View File

@@ -19,7 +19,6 @@ class StoreAssetModelRequest extends ImageUploadRequest
public function prepareForValidation(): void
{
parent::prepareForValidation();
if ($this->category_id) {
if ($category = Category::find($this->category_id)) {

View File

@@ -6,7 +6,6 @@ use App\Http\Traits\ConvertsBase64ToFiles;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use \App\Helpers\Helper;
class UploadFileRequest extends Request
{
@@ -28,7 +27,7 @@ class UploadFileRequest extends Request
*/
public function rules()
{
$max_file_size = Helper::file_upload_max_size();
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
return [
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,json,webp,avif|max:'.$max_file_size,
@@ -38,52 +37,34 @@ class UploadFileRequest extends Request
/**
* Sanitizes (if needed) and Saves a file to the appropriate location
* Returns the 'short' (storage-relative) filename
*
* TODO - this has a lot of similarities to UploadImageRequest's handleImage; is there
* a way to merge them or extend one into the other?
*/
public function handleFile(string $dirname, string $name_prefix, $file): string
{
$file_name = $name_prefix.'-'.str_random(8).'-'.str_replace(' ', '-', $file->getClientOriginalName());
$extension = $file->getClientOriginalExtension();
$file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$file->guessExtension();
// Check for SVG and sanitize it
if ($file->getMimeType() === 'image/svg+xml') {
$uploaded_file = $this->handleSVG($file);
Log::debug('This is an SVG');
Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put($dirname.$file_name, $cleanSVG);
} catch (\Exception $e) {
Log::debug('Upload no workie :( ');
Log::debug($e);
}
} else {
$uploaded_file = file_get_contents($file);
$put_results = Storage::put($dirname.$file_name, file_get_contents($file));
}
try {
Storage::put($dirname.$file_name, $uploaded_file);
} catch (\Exception $e) {
Log::debug($e);
}
return $file_name;
}
public function handleSVG($file) {
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
return $sanitizer->sanitize($dirtySVG);
}
/**
* Get the validation error messages that apply to the request, but
* replace the attribute name with the name of the file that was attempted and failed
* to make it clearer to the user which file is the bad one.
* @return array
*/
public function attributes(): array
{
$attributes = [];
if ($this->file) {
for ($i = 0; $i < count($this->file); $i++) {
$attributes['file.'.$i] = $this->file[$i]->getClientOriginalName();
}
}
return $attributes;
}
}

View File

@@ -4,10 +4,6 @@ namespace App\Http\Transformers;
class DatatablesTransformer
{
/**
* Transform data for bootstrap tables and API responses for lists of things
**/
public function transformDatatables($objects, $total = null)
{
(isset($total)) ? $objects_array['total'] = $total : $objects_array['total'] = count($objects);
@@ -15,15 +11,4 @@ class DatatablesTransformer
return $objects_array;
}
/**
* Transform data for returning the status of items within a bulk action
**/
public function transformBulkResponseWithStatusAndObjects($objects, $total)
{
(isset($total)) ? $objects_array['total'] = $total : $objects_array['total'] = count($objects);
$objects_array['rows'] = $objects;
return $objects_array;
}
}

View File

@@ -13,15 +13,16 @@ class LicenseSeatsTransformer
public function transformLicenseSeats(Collection $seats, $total)
{
$array = [];
$seat_count = 0;
foreach ($seats as $seat) {
$array[] = self::transformLicenseSeat($seat);
$seat_count++;
$array[] = self::transformLicenseSeat($seat, $seat_count);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformLicenseSeat(LicenseSeat $seat)
public function transformLicenseSeat(LicenseSeat $seat, $seat_count = 0)
{
$array = [
'id' => (int) $seat->id,
@@ -54,6 +55,10 @@ class LicenseSeatsTransformer
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
];
if ($seat_count != 0) {
$array['name'] = trans('admin/licenses/general.seat_count', ['count' => $seat_count]);
}
$permissions_array['available_actions'] = [
'checkout' => Gate::allows('checkout', License::class),
'checkin' => Gate::allows('checkin', License::class),

View File

@@ -3,10 +3,10 @@
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Helpers\StorageHelper;
use App\Models\Actionlog;
use Illuminate\Database\Eloquent\Collection;
use App\Models\Asset;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Storage;
class UploadedFilesTransformer
@@ -26,26 +26,23 @@ class UploadedFilesTransformer
{
$snipeModel = $file->item_type;
// This will be used later as we extend out this transformer to handle more types of uploads
if ($file->item_type == Asset::class) {
$file_url = route('show/assetfile', [$file->item_id, $file->id]);
}
$array = [
'id' => (int) $file->id,
'icon' => Helper::filetype_icon($file->filename),
'name' => e($file->filename),
'item' => ($file->item_type) ? [
'id' => (int) $file->item_id,
'type' => strtolower(class_basename($file->item_type)),
] : null,
'filename' => e($file->filename),
'filetype' => StorageHelper::getFiletype($file->uploads_file_path()),
'url' => $file->uploads_file_url(),
'note' => ($file->note) ? e($file->note) : null,
'url' => $file_url,
'created_by' => ($file->adminuser) ? [
'id' => (int) $file->adminuser->id,
'name'=> e($file->adminuser->present()->fullName),
] : null,
'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($file->updated_at, 'datetime'),
'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'),
'inline' => StorageHelper::allowSafeInline($file->uploads_file_path()),
'exists_on_disk' => (Storage::exists($file->uploads_file_path()) ? true : false),
];
$permissions_array['available_actions'] = [
@@ -56,5 +53,4 @@ class UploadedFilesTransformer
return $array;
}
}

View File

@@ -71,12 +71,12 @@ class SlackSettingsForm extends Component
$this->setting = Setting::getSettings();
$this->save_button = trans('general.save');
$this->webhook_selected = $this->setting->webhook_selected ?? 'slack';
$this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"] ?? $this->webhook_text['slack']["name"];
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"] ?? $this->webhook_text['slack']["icon"];
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"] ?? $this->webhook_text['slack']["placeholder"];
$this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"] ?? $this->webhook_text['slack']["link"];
$this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"] ?? $this->webhook_text['slack']["test"];
$this->webhook_selected = $this->setting->webhook_selected;
$this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"];
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"];
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"];
$this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"];
$this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"];
$this->webhook_endpoint = $this->setting->webhook_endpoint;
$this->webhook_channel = $this->setting->webhook_channel;
$this->webhook_botname = $this->setting->webhook_botname;
@@ -90,7 +90,7 @@ class SlackSettingsForm extends Component
$this->isDisabled= '';
}
if($this->webhook_selected === 'microsoft' && $this->teams_webhook_deprecated) {
session()->flash('warning', trans('admin/settings/message.webhook.ms_teams_deprecation'));
session()->flash('warning', 'The selected Microsoft Teams webhook URL will be deprecated Jan 31st, 2025. Please use a workflow URL. Microsofts Documentation on creating a workflow can be found <a href="https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498" target="_blank"> here.</a>');
}
}
public function updated($field) {
@@ -191,7 +191,6 @@ class SlackSettingsForm extends Component
$this->setting->webhook_endpoint = '';
$this->setting->webhook_channel = '';
$this->setting->webhook_botname = '';
$this->setting->webhook_selected = '';
$this->setting->save();

View File

@@ -444,62 +444,6 @@ class Actionlog extends SnipeModel
}
public function uploads_file_url()
{
switch ($this->item_type) {
case Accessory::class:
return route('show/accessoryfile', [$this->item_id, $this->id]);
case Asset::class:
return route('show/assetfile', [$this->item_id, $this->id]);
case AssetModel::class:
return route('show/modelfile', [$this->item_id, $this->id]);
case Consumable::class:
return route('show/locationsfile', [$this->item_id, $this->id]);
case Component::class:
return route('show/componentsfile', [$this->item_id, $this->id]);
case License::class:
return route('show/licensesfile', [$this->item_id, $this->id]);
case Location::class:
return route('show/locationsfile', [$this->item_id, $this->id]);
case User::class:
return route('show/userfile', [$this->item_id, $this->id]);
default:
return null;
}
}
public function uploads_file_path()
{
switch ($this->item_type) {
case Accessory::class:
return 'private_uploads/accessories/'.$this->filename;
case Asset::class:
return 'private_uploads/assets/'.$this->filename;
case AssetModel::class:
return 'private_uploads/assetmodels/'.$this->filename;
case Consumable::class:
return 'private_uploads/consumables/'.$this->filename;
case Component::class:
return 'private_uploads/components/'.$this->filename;
case License::class:
return 'private_uploads/licenses/'.$this->filename;
case Location::class:
return 'private_uploads/locations/'.$this->filename;
case User::class:
return 'private_uploads/users/'.$this->filename;
default:
return null;
}
}
// Manually sets $this->source for determineActionSource()
public function setActionSource($source = null): void
{

View File

@@ -656,8 +656,6 @@ class Asset extends Depreciable
return Storage::disk('public')->url(app('assets_upload_path').e($this->image));
} elseif ($this->model && ! empty($this->model->image)) {
return Storage::disk('public')->url(app('models_upload_path').e($this->model->image));
} elseif ($this->model->category && ! empty($this->model->category->image)) {
return Storage::disk('public')->url(app('categories_upload_path').e($this->model->category->image));
}
return false;

View File

@@ -100,14 +100,6 @@ class Category extends SnipeModel
public function isDeletable()
{
// We have to check for models as well if the category type is asset
if ($this->category_type == 'asset') {
return Gate::allows('delete', $this)
&& ($this->itemCount() == 0)
&& ($this->models_count == 0)
&& ($this->deleted_at == '');
}
return Gate::allows('delete', $this)
&& ($this->itemCount() == 0)
&& ($this->deleted_at == '');

View File

@@ -113,10 +113,7 @@ class CustomFieldset extends Model
$rule[] = 'unique_undeleted';
}
if ($field->attributes['format']!='') {
array_push($rule, $field->attributes['format']);
}
array_push($rule, $field->attributes['format']);
$rules[$field->db_column_name()] = $rule;

View File

@@ -34,7 +34,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
use Notifiable;
use Presentable;
use Searchable;
use Loggable;
protected $hidden = ['password', 'remember_token', 'permissions', 'reset_password_code', 'persist_code'];
protected $table = 'users';
@@ -321,7 +320,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
{
$setting = Setting::getSettings();
if ($setting?->name_display_format == 'last_first') {
if ($setting->name_display_format=='last_first') {
return ($this->last_name) ? $this->last_name.' '.$this->first_name : $this->first_name;
}
return $this->last_name ? $this->first_name.' '.$this->last_name : $this->first_name;

View File

@@ -298,7 +298,6 @@ class AssetPresenter extends Presenter
'sortable' => true,
'visible' => false,
'title' => trans('general.byod'),
'class' => 'byod',
'formatter' => 'trueFalseFormatter',
],

View File

@@ -230,7 +230,16 @@ class LicensePresenter extends Presenter
'switchable' => true,
'title' => trans('general.id'),
'visible' => false,
],[
],
[
'field' => 'name',
'searchable' => false,
'sortable' => false,
'sorter' => 'numericOnly',
'switchable' => true,
'title' => trans('admin/licenses/general.seat'),
'visible' => true,
], [
'field' => 'assigned_user',
'searchable' => false,
'sortable' => false,
@@ -276,7 +285,7 @@ class LicensePresenter extends Presenter
'searchable' => false,
'sortable' => true,
'visible' => false,
'title' => trans('general.updated_at'),
'title' => trans('general.date'),
'formatter' => 'dateDisplayFormatter',
],
[

View File

@@ -1,101 +0,0 @@
<?php
namespace App\Presenters;
/**
* Class AccessoryPresenter
*/
class UploadsPresenter extends Presenter
{
/**
* Json Column Layout for bootstrap table
* @return string
*/
public static function dataTableLayout($object)
{
if ($object =='assets') {
$object = 'hardware';
}
$layout = [
[
'field' => 'id',
'searchable' => false,
'sortable' => true,
'switchable' => true,
'title' => trans('general.id'),
'visible' => false,
],
[
'field' => 'icon',
'searchable' => false,
'sortable' => false,
'switchable' => false,
'title' => trans('general.type'),
'formatter' => 'iconFormatter',
],
[
'field' => 'image',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('general.image'),
'formatter' => 'inlineImageFormatter',
],
[
'field' => 'filename',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('general.file_name'),
'visible' => true,
'formatter' => 'fileUploadNameFormatter',
],
[
'field' => 'download',
'searchable' => false,
'sortable' => false,
'switchable' => true,
'title' => trans('general.download'),
'visible' => true,
'formatter' => 'downloadOrOpenInNewWindowFormatter',
],
[
'field' => 'note',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.notes'),
'visible' => true,
],
[
'field' => 'created_by',
'searchable' => false,
'sortable' => true,
'title' => trans('general.created_by'),
'visible' => false,
'formatter' => 'usersLinkObjFormatter',
],
[
'field' => 'created_at',
'searchable' => true,
'sortable' => true,
'switchable' => true,
'title' => trans('general.created_at'),
'visible' => false,
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'available_actions',
'searchable' => false,
'sortable' => false,
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'deleteUploadFormatter',
],
];
return json_encode($layout);
}
}

View File

@@ -67,11 +67,6 @@ class BreadcrumbsServiceProvider extends ServiceProvider
->push(trans('general.create'), route('hardware.create'))
);
Breadcrumbs::for('clone/hardware', fn (Trail $trail) =>
$trail->parent('hardware.index', route('hardware.index'))
->push(trans('admin/hardware/general.clone'), route('hardware.create'))
);
Breadcrumbs::for('hardware.show', fn (Trail $trail, Asset $asset) =>
$trail->parent('hardware.index', route('hardware.index'))
->push($asset->present()->fullName(), route('hardware.show', $asset))
@@ -383,12 +378,6 @@ class BreadcrumbsServiceProvider extends ServiceProvider
->push(trans('general.create'), route('locations.create'))
);
Breadcrumbs::for('clone/location', fn (Trail $trail) =>
$trail->parent('locations.index', route('locations.index'))
->push(trans('admin/locations/table.clone'), route('locations.create'))
);
Breadcrumbs::for('locations.show', fn (Trail $trail, Location $location) =>
$trail->parent('locations.index', route('locations.index'))
->push($location->name, route('locations.show', $location))
@@ -550,13 +539,6 @@ class BreadcrumbsServiceProvider extends ServiceProvider
->push(trans('general.create'), route('users.create'))
);
Breadcrumbs::for('users.clone.show', fn (Trail $trail) =>
$trail->parent('users.index', route('users.index'))
->push(trans('admin/users/general.clone'), route('users.create'))
);
Breadcrumbs::for('users.show', fn (Trail $trail, User $user) =>
$trail->parent('users.index', route('users.index'))
->push($user->getFullNameAttribute() ?? 'Missing Username!', route('users.show', $user))

12
composer.lock generated
View File

@@ -3558,16 +3558,16 @@
},
{
"name": "league/commonmark",
"version": "2.7.0",
"version": "2.6.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405"
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
"reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad",
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad",
"shasum": ""
},
"require": {
@@ -3604,7 +3604,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.8-dev"
"dev-main": "2.7-dev"
}
},
"autoload": {
@@ -3661,7 +3661,7 @@
"type": "tidelift"
}
],
"time": "2025-05-05T12:20:28+00:00"
"time": "2024-12-29T14:10:59+00:00"
},
{
"name": "league/config",

View File

@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v8.1.3',
'full_app_version' => 'v8.1.3 - build 18054-gd67933ab4',
'build_version' => '18054',
'app_version' => 'v8.1.2',
'full_app_version' => 'v8.1.2 - build 17914-g251851ec6',
'build_version' => '17914',
'prerelease_version' => '',
'hash_version' => 'gd67933ab4',
'full_hash' => 'v8.1.3-133-gd67933ab4',
'hash_version' => 'g251851ec6',
'full_hash' => 'v8.1.2-32-g251851ec6',
'branch' => 'develop',
);

View File

@@ -614,6 +614,11 @@ input[type=search] {
.search-highlight:hover {
background-color: var(--back-sub) !important;
}
.input-group,
.input-group-addon {
background-color: var(--back-sub);
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -1403,6 +1408,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -2368,6 +2378,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -2903,6 +2918,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -3634,6 +3654,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -4381,6 +4406,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -5327,6 +5357,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -614,6 +614,11 @@ input[type=search] {
.search-highlight:hover {
background-color: var(--back-sub) !important;
}
.input-group,
.input-group-addon {
background-color: var(--back-sub);
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -1403,6 +1408,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -2368,6 +2378,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -2903,6 +2918,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -3634,6 +3654,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -4381,6 +4406,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}
@@ -5327,6 +5357,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -396,6 +396,11 @@ input[type=search] {
.search-highlight:hover {
background-color: var(--back-sub) !important;
}
.input-group,
.input-group-addon {
background-color: var(--back-sub);
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -396,6 +396,11 @@ input[type=search] {
.search-highlight:hover {
background-color: var(--back-sub) !important;
}
.input-group,
.input-group-addon {
background-color: var(--back-sub);
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -377,6 +377,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -377,6 +377,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -368,6 +368,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -368,6 +368,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -378,6 +378,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -378,6 +378,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -378,6 +378,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -378,6 +378,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -377,6 +377,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -377,6 +377,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -355,6 +355,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

View File

@@ -355,6 +355,11 @@ input[type=search] {
background-color: var(--back-sub);
color: var(--text-main);
}
.input-group,
.input-group-addon {
background-color: var(--back-sub) !important;
color: var(--text-main);
}
#licensesTable > tbody > tr > td > nobr > a > i.fa {
color: var(--text-main);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,23 +1,23 @@
{
"/js/build/app.js": "/js/build/app.js?id=19253af36b58ed3fb6770c7bb944f079",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=78bfb1c7b5782df4fb0ac7e36f80f847",
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=503d0b09e157a22f555e3670d1ec9bb5",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=06c13e817cc022028b3f4a33c0ca303a",
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=3955411f214d1aa011fd509b6c81fbea",
"/css/build/overrides.css": "/css/build/overrides.css?id=2bfc7b71d951c5ac026dbc034f7373b1",
"/css/build/app.css": "/css/build/app.css?id=4b4c2f1225d59efa7a22b76f7bbe39d8",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=4ea0068716c1bb2434d87a16d51b98c9",
"/css/dist/skins/skin-yellow.css": "/css/dist/skins/skin-yellow.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=f6b2e7fa795596ac4754500c9c30eacc",
"/css/dist/skins/skin-yellow-dark.css": "/css/dist/skins/skin-yellow-dark.css?id=5bbc74fa36ad7e85114e95c99f244421",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=bcba8a437f59c2334f9bd32f668ff9bb",
"/css/dist/skins/skin-red-dark.css": "/css/dist/skins/skin-red-dark.css?id=b70f0d0cf76a643b3fbf47a1dfdb1529",
"/css/dist/skins/skin-purple.css": "/css/dist/skins/skin-purple.css?id=6fe68325d5356197672c27bc77cedcb4",
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=093bd9d22c19a903b826bb4a1c819263",
"/css/dist/skins/skin-purple-dark.css": "/css/dist/skins/skin-purple-dark.css?id=25f255bc860b5472b201ef700fd638a8",
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=723ac4a390d628c9253d5416494cdaf4",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=07e257b423e6a8f984e1d9bf0bb04fe0",
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=624d1d9b9bf141a593cfe835fdfc85cc",
"/css/dist/skins/skin-green-dark.css": "/css/dist/skins/skin-green-dark.css?id=bb648ddce4bc923ac79205a78882248d",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=a82b065847bf3cd5d713c04ee8dc86c6",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=7aacfabbafd138c5af6420609f97820d",
"/css/dist/skins/skin-blue-dark.css": "/css/dist/skins/skin-blue-dark.css?id=e4facb3da382b3c6f040f7d0b738643d",
"/css/dist/skins/skin-black.css": "/css/dist/skins/skin-black.css?id=76482123f6c70e866d6b971ba91de7bb",
"/css/dist/all.css": "/css/dist/all.css?id=12e96a25d0dc3ee39e9d3ce97044fbbf",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
@@ -92,24 +92,24 @@
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=331c85bd61ffa93af09273d1bc2add5a",
"/js/dist/bootstrap-table-locale-all.min.js": "/js/dist/bootstrap-table-locale-all.min.js?id=5e93ef0a1889bed3f92a705dc1e92c9b",
"/js/dist/bootstrap-table-en-US.min.js": "/js/dist/bootstrap-table-en-US.min.js?id=c0f21fb7e62d6f0a0153f1cdbf26782a",
"/css/dist/skins/_all-skins.min.css": "/css/dist/skins/_all-skins.min.css?id=503d0b09e157a22f555e3670d1ec9bb5",
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=78bfb1c7b5782df4fb0ac7e36f80f847",
"/css/dist/skins/_all-skins.min.css": "/css/dist/skins/_all-skins.min.css?id=3955411f214d1aa011fd509b6c81fbea",
"/css/dist/skins/skin-black-dark.min.css": "/css/dist/skins/skin-black-dark.min.css?id=06c13e817cc022028b3f4a33c0ca303a",
"/css/dist/skins/skin-black.min.css": "/css/dist/skins/skin-black.min.css?id=76482123f6c70e866d6b971ba91de7bb",
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=7aacfabbafd138c5af6420609f97820d",
"/css/dist/skins/skin-blue-dark.min.css": "/css/dist/skins/skin-blue-dark.min.css?id=e4facb3da382b3c6f040f7d0b738643d",
"/css/dist/skins/skin-blue.min.css": "/css/dist/skins/skin-blue.min.css?id=a82b065847bf3cd5d713c04ee8dc86c6",
"/css/dist/skins/skin-contrast.min.css": "/css/dist/skins/skin-contrast.min.css?id=da6c7997d9de2f8329142399f0ce50da",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=624d1d9b9bf141a593cfe835fdfc85cc",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=bb648ddce4bc923ac79205a78882248d",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=0a82a6ae6bb4e58fe62d162c4fb50397",
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=723ac4a390d628c9253d5416494cdaf4",
"/css/dist/skins/skin-orange-dark.min.css": "/css/dist/skins/skin-orange-dark.min.css?id=07e257b423e6a8f984e1d9bf0bb04fe0",
"/css/dist/skins/skin-orange.min.css": "/css/dist/skins/skin-orange.min.css?id=6f0563e726c2fe4fab4026daaa5bfdf2",
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=093bd9d22c19a903b826bb4a1c819263",
"/css/dist/skins/skin-purple-dark.min.css": "/css/dist/skins/skin-purple-dark.min.css?id=25f255bc860b5472b201ef700fd638a8",
"/css/dist/skins/skin-purple.min.css": "/css/dist/skins/skin-purple.min.css?id=6fe68325d5356197672c27bc77cedcb4",
"/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=bcba8a437f59c2334f9bd32f668ff9bb",
"/css/dist/skins/skin-red-dark.min.css": "/css/dist/skins/skin-red-dark.min.css?id=b70f0d0cf76a643b3fbf47a1dfdb1529",
"/css/dist/skins/skin-red.min.css": "/css/dist/skins/skin-red.min.css?id=44bf834f2110504a793dadec132a5898",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=f6b2e7fa795596ac4754500c9c30eacc",
"/css/dist/skins/skin-yellow-dark.min.css": "/css/dist/skins/skin-yellow-dark.min.css?id=5bbc74fa36ad7e85114e95c99f244421",
"/css/dist/skins/skin-yellow.min.css": "/css/dist/skins/skin-yellow.min.css?id=7b315b9612b8fde8f9c5b0ddb6bba690",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=54d676a6ea8677dd48f6c4b3041292cf",
"/js/build/vendor.js": "/js/build/vendor.js?id=89dffa552c6e3abe3a2aac6c9c7b466b",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=03bcd02b9ca5e53e1390ee840cd0a561",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=783d3a8076337744f0176d60e1041ea4",
"/js/dist/all.js": "/js/dist/all.js?id=8c6d7286f667eeb62a0a28a09851a6c3"
}

View File

@@ -5,7 +5,7 @@ return [
'manage' => 'crwdns6501:0crwdne6501:0',
'field' => 'crwdns1487:0crwdne1487:0',
'about_fieldsets_title' => 'crwdns1488:0crwdne1488:0',
'about_fieldsets_text' => 'crwdns13226:0crwdne13226:0',
'about_fieldsets_text' => 'crwdns13176:0crwdne13176:0',
'custom_format' => 'crwdns6505:0crwdne6505:0',
'encrypt_field' => 'crwdns1792:0crwdne1792:0',
'encrypt_field_help' => 'crwdns1683:0crwdne1683:0',

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'crwdns6693:0crwdne6693:0',
'signed_by_location_manager' => 'crwdns6695:0crwdne6695:0',
'signed_by' => 'crwdns6697:0crwdne6697:0',
'clone' => 'crwdns13240:0crwdne13240:0',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'crwdns6337:0crwdne6337:0',
'eula_settings' => 'crwdns1296:0crwdne1296:0',
'eula_markdown' => 'crwdns1261:0crwdne1261:0',
'empty_row_count' => 'crwdns13236:0crwdne13236:0',
'empty_row_count_help' => 'crwdns13238:0crwdne13238:0',
'favicon' => 'crwdns5858:0crwdne5858:0',
'favicon_format' => 'crwdns5860:0crwdne5860:0',
'favicon_size' => 'crwdns5862:0crwdne5862:0',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'crwdns1465:0crwdne1465:0',
'scope_locations_fmcs_support_text' => 'crwdns13049:0crwdne13049:0',
'scope_locations_fmcs_support_help_text' => 'crwdns13188:0crwdne13188:0',
'scope_locations_fmcs_check_button' => 'crwdns13234:0crwdne13234:0',
'scope_locations_fmcs_support_disabled_text' => 'crwdns13190:0crwdne13190:0',
'show_in_model_list' => 'crwdns1990:0crwdne1990:0',
'optional' => 'crwdns1298:0crwdne1298:0',

View File

@@ -389,8 +389,6 @@ return [
'new_license' => 'crwdns6249:0crwdne6249:0',
'new_accessory' => 'crwdns6251:0crwdne6251:0',
'new_consumable' => 'crwdns6253:0crwdne6253:0',
'new_component' => 'crwdns13228:0crwdne13228:0',
'new_user' => 'crwdns13230:0crwdne13230:0',
'collapse' => 'crwdns6255:0crwdne6255:0',
'assigned' => 'crwdns6257:0crwdne6257:0',
'asset_count' => 'crwdns6259:0crwdne6259:0',

View File

@@ -172,7 +172,6 @@ return [
'url' => 'crwdns12550:0crwdne12550:0',
'ulid' => 'crwdns12552:0crwdne12552:0',
'uuid' => 'crwdns12554:0crwdne12554:0',
'fmcs_location' => 'crwdns13232:0crwdne13232:0',
/*

View File

@@ -5,7 +5,7 @@ return [
'manage' => 'Manage',
'field' => 'veld',
'about_fieldsets_title' => 'Oor Fieldsets',
'about_fieldsets_text' => 'Veldstelle stel jou in staat om groepe van persoonlike velde te skep wat gereeld hergebruik word vir spesifieke tipe bates.',
'about_fieldsets_text' => 'Fieldsets allow you to create groups of custom fields that are frequently re-used for specific asset model types.',
'custom_format' => 'Custom Regex format...',
'encrypt_field' => 'Enkripteer die waarde van hierdie veld in die databasis',
'encrypt_field_help' => 'WAARSKUWING: Om \'n veld te enkripteer, maak dit onondersoekbaar.',

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'Signed By (Finance Auditor):',
'signed_by_location_manager' => 'Signed By (Location Manager):',
'signed_by' => 'Signed Off By:',
'clone' => 'Clone Location',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'Enabled',
'eula_settings' => 'EULA-instellings',
'eula_markdown' => 'Hierdie EULA laat <a href="https://help.github.com/articles/github-flavored-markdown/">Github-geurde markdown</a> toe.',
'empty_row_count' => 'Field Start Offset (Empty Rows)',
'empty_row_count_help' => 'Fields will begin populating after this many empty rows are skipped at the top of the label.',
'favicon' => 'Favicon',
'favicon_format' => 'Accepted filetypes are ico, png, and gif. Other image formats may not work in all browsers.',
'favicon_size' => 'Favicons should be square images, 16x16 pixels.',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'Volledige Veelvuldige Maatskappye Ondersteuning',
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
'scope_locations_fmcs_support_help_text' => 'Restrict locations to their selected company.',
'scope_locations_fmcs_check_button' => 'Check Compatibility',
'scope_locations_fmcs_support_disabled_text' => 'This option is disabled because you have conflicting locations set for :count or more items.',
'show_in_model_list' => 'Show in Model Dropdowns',
'optional' => 'opsioneel',

View File

@@ -390,8 +390,6 @@ return [
'new_license' => 'New License',
'new_accessory' => 'New Accessory',
'new_consumable' => 'New Consumable',
'new_component' => 'New Component',
'new_user' => 'New User',
'collapse' => 'Collapse',
'assigned' => 'Assigned',
'asset_count' => 'Asset Count',

View File

@@ -172,7 +172,6 @@ return [
'url' => 'The :attribute field must be a valid URL.',
'ulid' => 'The :attribute field must be a valid ULID.',
'uuid' => 'The :attribute field must be a valid UUID.',
'fmcs_location' => 'Full multiple company support and location scoping is enabled in the Admin Settings, and the selected location and selected company are not compatible.',
/*

View File

@@ -5,7 +5,7 @@ return [
'manage' => 'Manage',
'field' => 'Field',
'about_fieldsets_title' => 'About Fieldsets',
'about_fieldsets_text' => 'Fieldsets allow you to create groups of custom fields that are frequently re-used used for specific asset model types.',
'about_fieldsets_text' => 'Fieldsets allow you to create groups of custom fields that are frequently re-used for specific asset model types.',
'custom_format' => 'Custom Regex format...',
'encrypt_field' => 'Encrypt the value of this field in the database',
'encrypt_field_help' => 'WARNING: Encrypting a field makes it unsearchable.',

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'Signed By (Finance Auditor):',
'signed_by_location_manager' => 'Signed By (Location Manager):',
'signed_by' => 'Signed Off By:',
'clone' => 'Clone Location',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'Enabled',
'eula_settings' => 'EULA Settings',
'eula_markdown' => 'This EULA allows <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>.',
'empty_row_count' => 'Field Start Offset (Empty Rows)',
'empty_row_count_help' => 'Fields will begin populating after this many empty rows are skipped at the top of the label.',
'favicon' => 'Favicon',
'favicon_format' => 'Accepted filetypes are ico, png, and gif. Other image formats may not work in all browsers.',
'favicon_size' => 'Favicons should be square images, 16x16 pixels.',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'Full Multiple Companies Support',
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
'scope_locations_fmcs_support_help_text' => 'Restrict locations to their selected company.',
'scope_locations_fmcs_check_button' => 'Check Compatibility',
'scope_locations_fmcs_support_disabled_text' => 'This option is disabled because you have conflicting locations set for :count or more items.',
'show_in_model_list' => 'Show in Model Dropdowns',
'optional' => 'optional',

View File

@@ -390,8 +390,6 @@ return [
'new_license' => 'New License',
'new_accessory' => 'New Accessory',
'new_consumable' => 'New Consumable',
'new_component' => 'New Component',
'new_user' => 'New User',
'collapse' => 'Collapse',
'assigned' => 'Assigned',
'asset_count' => 'Asset Count',

View File

@@ -172,7 +172,6 @@ return [
'url' => 'The :attribute field must be a valid URL.',
'ulid' => 'The :attribute field must be a valid ULID.',
'uuid' => 'The :attribute field must be a valid UUID.',
'fmcs_location' => 'Full multiple company support and location scoping is enabled in the Admin Settings, and the selected location and selected company are not compatible.',
/*

View File

@@ -5,7 +5,7 @@ return [
'manage' => 'إدارة',
'field' => 'حقل',
'about_fieldsets_title' => 'حول مجموعة الحقول',
'about_fieldsets_text' => '(مجموعات الحقول) تسمح لك بإنشاء مجموعات من الحقول اللتي يمكن إعادة إستخدامها مع موديل محدد.',
'about_fieldsets_text' => 'مجموعات الحقول تسمح لك بإنشاء مجموعات من الحقول المخصصة التي يعاد استخدامها في كثير من الأحيان لأنواع معينة من نماذج الأصول.',
'custom_format' => 'تنسيق Regex المخصص...',
'encrypt_field' => 'تشفير قيمة هذا الحقل في قاعدة البيانات',
'encrypt_field_help' => 'تحذير: تشفير الحقل يجعله غير قابل للبحث.',

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'موقعة من قبل (مراجع الحسابات المالي):',
'signed_by_location_manager' => 'توقيع بواسطة (مدير الموقع):',
'signed_by' => 'تم توقيعه من قبل:',
'clone' => 'Clone Location',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'تمكين',
'eula_settings' => 'إعدادات اتفاقية ترخيص المستخدم النهائي',
'eula_markdown' => 'تسمح اتفاقية ترخيص المستخدم هذه <a href="https://help.github.com/articles/github-flavored-markdown/">بتطبيق نمط الكتابة من Github</a>.',
'empty_row_count' => 'Field Start Offset (Empty Rows)',
'empty_row_count_help' => 'Fields will begin populating after this many empty rows are skipped at the top of the label.',
'favicon' => 'Favicon',
'favicon_format' => 'أنواع الملفات المقبولة هي رمز و png و gif. قد لا تعمل تنسيقات الصور الأخرى في كافة المستعرضات.',
'favicon_size' => 'وينبغي أن تكون Favicons صور مربعة ، 16x16 بكسل.',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'كامل دعم الشركات المتعددة',
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
'scope_locations_fmcs_support_help_text' => 'Restrict locations to their selected company.',
'scope_locations_fmcs_check_button' => 'Check Compatibility',
'scope_locations_fmcs_support_disabled_text' => 'This option is disabled because you have conflicting locations set for :count or more items.',
'show_in_model_list' => 'إظهار في القوائم المنسدلة للنماذج',
'optional' => 'اختياري',

View File

@@ -390,8 +390,6 @@ return [
'new_license' => 'ترخيص جديد',
'new_accessory' => 'ملحق الجودة',
'new_consumable' => 'مادة إستهلاكية جديدة',
'new_component' => 'New Component',
'new_user' => 'New User',
'collapse' => 'اخفاء',
'assigned' => 'مسندة',
'asset_count' => 'عدد الأصول',

View File

@@ -172,7 +172,6 @@ return [
'url' => 'The :attribute field must be a valid URL.',
'ulid' => 'The :attribute field must be a valid ULID.',
'uuid' => 'The :attribute field must be a valid UUID.',
'fmcs_location' => 'Full multiple company support and location scoping is enabled in the Admin Settings, and the selected location and selected company are not compatible.',
/*

View File

@@ -5,7 +5,7 @@ return [
'manage' => 'Управление',
'field' => 'Поле',
'about_fieldsets_title' => 'Относно Fieldsets',
'about_fieldsets_text' => 'Fieldsets позволяват създаването на групи от персонализирани полета, които се използват и преизползват често за специфични типове модели на активи.',
'about_fieldsets_text' => '"Група от полета" позволяват създаването на групи от персонализирани полета, които се използват и преизползват често за специфични типове модели на активи.',
'custom_format' => 'Персонализиран формат...',
'encrypt_field' => 'Шифроване на стойността на това поле в базата данни',
'encrypt_field_help' => 'ВНИМАНИЕ: Шифроване на поле го прави невалидно за търсене.',

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'Подписан от (счетоводител):',
'signed_by_location_manager' => 'Подписан от (мениджър):',
'signed_by' => 'Подписано от:',
'clone' => 'Clone Location',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'Активно',
'eula_settings' => 'Настройки на EULA',
'eula_markdown' => 'Съдържанието на EULA може да бъде форматирано с <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>.',
'empty_row_count' => 'Field Start Offset (Empty Rows)',
'empty_row_count_help' => 'Fields will begin populating after this many empty rows are skipped at the top of the label.',
'favicon' => 'Favicon',
'favicon_format' => 'Приетите файлови формати са ico, png, и gif. Другите формати на снимки може да не работят в всъчки браузъри.',
'favicon_size' => 'Favicons трябва да бъдат квадратна снимка с размери, 16х16 пиксела.',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'Поддръжка на множество компании',
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
'scope_locations_fmcs_support_help_text' => 'Restrict locations to their selected company.',
'scope_locations_fmcs_check_button' => 'Check Compatibility',
'scope_locations_fmcs_support_disabled_text' => 'This option is disabled because you have conflicting locations set for :count or more items.',
'show_in_model_list' => 'Показване в падащите менюта на моделите',
'optional' => 'незадължително',

View File

@@ -390,8 +390,6 @@ return [
'new_license' => 'Нов Лиценз',
'new_accessory' => 'Нов аксесоар',
'new_consumable' => 'Нов Консуматив',
'new_component' => 'New Component',
'new_user' => 'New User',
'collapse' => 'Свий',
'assigned' => 'Възложен',
'asset_count' => 'Брой Активи',

View File

@@ -172,7 +172,6 @@ return [
'url' => 'The :attribute field must be a valid URL.',
'ulid' => 'The :attribute field must be a valid ULID.',
'uuid' => 'The :attribute field must be a valid UUID.',
'fmcs_location' => 'Full multiple company support and location scoping is enabled in the Admin Settings, and the selected location and selected company are not compatible.',
/*

View File

@@ -5,7 +5,7 @@ return [
'manage' => 'Manage',
'field' => 'Field',
'about_fieldsets_title' => 'About Fieldsets',
'about_fieldsets_text' => 'Fieldsets allow you to create groups of custom fields that are frequently re-used used for specific asset model types.',
'about_fieldsets_text' => 'Fieldsets allow you to create groups of custom fields that are frequently re-used for specific asset model types.',
'custom_format' => 'Custom Regex format...',
'encrypt_field' => 'Encrypt the value of this field in the database',
'encrypt_field_help' => 'WARNING: Encrypting a field makes it unsearchable.',

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'Signed By (Finance Auditor):',
'signed_by_location_manager' => 'Signed By (Location Manager):',
'signed_by' => 'Signed Off By:',
'clone' => 'Clone Location',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'Enabled',
'eula_settings' => 'EULA Settings',
'eula_markdown' => 'This EULA allows <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>.',
'empty_row_count' => 'Field Start Offset (Empty Rows)',
'empty_row_count_help' => 'Fields will begin populating after this many empty rows are skipped at the top of the label.',
'favicon' => 'Favicon',
'favicon_format' => 'Accepted filetypes are ico, png, and gif. Other image formats may not work in all browsers.',
'favicon_size' => 'Favicons should be square images, 16x16 pixels.',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'Full Multiple Companies Support',
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
'scope_locations_fmcs_support_help_text' => 'Restrict locations to their selected company.',
'scope_locations_fmcs_check_button' => 'Check Compatibility',
'scope_locations_fmcs_support_disabled_text' => 'This option is disabled because you have conflicting locations set for :count or more items.',
'show_in_model_list' => 'Show in Model Dropdowns',
'optional' => 'optional',

View File

@@ -390,8 +390,6 @@ return [
'new_license' => 'New License',
'new_accessory' => 'New Accessory',
'new_consumable' => 'New Consumable',
'new_component' => 'New Component',
'new_user' => 'New User',
'collapse' => 'Collapse',
'assigned' => 'Assigned',
'asset_count' => 'Asset Count',

View File

@@ -172,7 +172,6 @@ return [
'url' => 'The :attribute field must be a valid URL.',
'ulid' => 'The :attribute field must be a valid ULID.',
'uuid' => 'The :attribute field must be a valid UUID.',
'fmcs_location' => 'Full multiple company support and location scoping is enabled in the Admin Settings, and the selected location and selected company are not compatible.',
/*

View File

@@ -5,7 +5,7 @@ return [
'manage' => 'Manage',
'field' => 'Field',
'about_fieldsets_title' => 'About Fieldsets',
'about_fieldsets_text' => 'Fieldsets allow you to create groups of custom fields that are frequently re-used used for specific asset model types.',
'about_fieldsets_text' => 'Fieldsets allow you to create groups of custom fields that are frequently re-used for specific asset model types.',
'custom_format' => 'Custom Regex format...',
'encrypt_field' => 'Encrypt the value of this field in the database',
'encrypt_field_help' => 'WARNING: Encrypting a field makes it unsearchable.',

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'Signed By (Finance Auditor):',
'signed_by_location_manager' => 'Signed By (Location Manager):',
'signed_by' => 'Signed Off By:',
'clone' => 'Clone Location',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'Enabled',
'eula_settings' => 'EULA Settings',
'eula_markdown' => 'This EULA allows <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>.',
'empty_row_count' => 'Field Start Offset (Empty Rows)',
'empty_row_count_help' => 'Fields will begin populating after this many empty rows are skipped at the top of the label.',
'favicon' => 'Favicon',
'favicon_format' => 'Accepted filetypes are ico, png, and gif. Other image formats may not work in all browsers.',
'favicon_size' => 'Favicons should be square images, 16x16 pixels.',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'Full Multiple Companies Support',
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
'scope_locations_fmcs_support_help_text' => 'Restrict locations to their selected company.',
'scope_locations_fmcs_check_button' => 'Check Compatibility',
'scope_locations_fmcs_support_disabled_text' => 'This option is disabled because you have conflicting locations set for :count or more items.',
'show_in_model_list' => 'Show in Model Dropdowns',
'optional' => 'optional',

View File

@@ -390,8 +390,6 @@ return [
'new_license' => 'New License',
'new_accessory' => 'New Accessory',
'new_consumable' => 'New Consumable',
'new_component' => 'New Component',
'new_user' => 'New User',
'collapse' => 'Collapse',
'assigned' => 'Assigned',
'asset_count' => 'Asset Count',

View File

@@ -172,7 +172,6 @@ return [
'url' => 'The :attribute field must be a valid URL.',
'ulid' => 'The :attribute field must be a valid ULID.',
'uuid' => 'The :attribute field must be a valid UUID.',
'fmcs_location' => 'Full multiple company support and location scoping is enabled in the Admin Settings, and the selected location and selected company are not compatible.',
/*

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'Podepsáno (Finanční auditor):',
'signed_by_location_manager' => 'Podepsáno (Manager):',
'signed_by' => 'Odepsal:',
'clone' => 'Clone Location',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'Povoleno',
'eula_settings' => 'Nastavení EULA',
'eula_markdown' => 'Tato EULA umožňuje <a href="https://help.github.com/articles/github-flavored-markdown/">Github markdown</a>.',
'empty_row_count' => 'Field Start Offset (Empty Rows)',
'empty_row_count_help' => 'Fields will begin populating after this many empty rows are skipped at the top of the label.',
'favicon' => 'Favicona',
'favicon_format' => 'Povolené typy souborů jsou ico, png a gif. Ostatní formáty obrázků nemusí fungovat ve všech prohlížečích.',
'favicon_size' => 'Favikony by měly být čtvercové obrázky, 16 x 16 pixelů.',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'Plná podpora více společností',
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
'scope_locations_fmcs_support_help_text' => 'Restrict locations to their selected company.',
'scope_locations_fmcs_check_button' => 'Check Compatibility',
'scope_locations_fmcs_support_disabled_text' => 'This option is disabled because you have conflicting locations set for :count or more items.',
'show_in_model_list' => 'Zobrazit v rozbalovacích nabídkách modelu',
'optional' => 'volitelný',

View File

@@ -390,8 +390,6 @@ return [
'new_license' => 'Nová licence',
'new_accessory' => 'Nové příslušenství',
'new_consumable' => 'Nový spotřební materiál',
'new_component' => 'New Component',
'new_user' => 'New User',
'collapse' => 'Sbalit',
'assigned' => 'Přiřazené',
'asset_count' => 'Počet aktiv',

View File

@@ -172,7 +172,6 @@ return [
'url' => ':attribute není platnou URL.',
'ulid' => 'The :attribute field must be a valid ULID.',
'uuid' => 'The :attribute field must be a valid UUID.',
'fmcs_location' => 'Full multiple company support and location scoping is enabled in the Admin Settings, and the selected location and selected company are not compatible.',
/*

View File

@@ -5,7 +5,7 @@ return [
'manage' => 'Manage',
'field' => 'Meysydd',
'about_fieldsets_title' => 'Amdan grwpiau meysydd',
'about_fieldsets_text' => 'Mae grwpiau meysydd yn caniatau i chi creu grwpiau o meysydd addasedig sydd yn cael ei defnyddio yn amal ar gyfer mathau penodol o asedau.',
'about_fieldsets_text' => 'Fieldsets allow you to create groups of custom fields that are frequently re-used for specific asset model types.',
'custom_format' => 'Custom Regex format...',
'encrypt_field' => 'Hamcryptio gwerth y maes yma yn y basdata',
'encrypt_field_help' => 'RHYBUDD: Mae hamcryptio maes yn feddwl nid oes modd chwilio amdano.',

View File

@@ -39,5 +39,4 @@ return [
'signed_by_finance_auditor' => 'Signed By (Finance Auditor):',
'signed_by_location_manager' => 'Signed By (Location Manager):',
'signed_by' => 'Signed Off By:',
'clone' => 'Clone Location',
];

View File

@@ -64,8 +64,6 @@ return [
'enabled' => 'Enabled',
'eula_settings' => 'Gosodiadau CTDT',
'eula_markdown' => 'Mae\'r CTDT yma yn caniatau <a href="https://help.github.com/articles/github-flavored-markdown/">markdown GitHub</a>.',
'empty_row_count' => 'Field Start Offset (Empty Rows)',
'empty_row_count_help' => 'Fields will begin populating after this many empty rows are skipped at the top of the label.',
'favicon' => 'Favicon',
'favicon_format' => 'Mathau o ffeiliau a dderbynnir yw ico, png, a gif. Mae\'n bosib cewch trafferthion hefo rhai gwahanol mewn rhai porrwyr.',
'favicon_size' => 'Dylith favicons bod yn delweddau sgwar 16x16 pixels.',
@@ -154,7 +152,6 @@ return [
'full_multiple_companies_support_text' => 'Cefnogaeth Llawn ar gyfer Nifer o Cwmniau',
'scope_locations_fmcs_support_text' => 'Scope Locations with Full Multiple Companies Support',
'scope_locations_fmcs_support_help_text' => 'Restrict locations to their selected company.',
'scope_locations_fmcs_check_button' => 'Check Compatibility',
'scope_locations_fmcs_support_disabled_text' => 'This option is disabled because you have conflicting locations set for :count or more items.',
'show_in_model_list' => 'Dangos mewn dewislen modelau',
'optional' => 'dewisol',

View File

@@ -390,8 +390,6 @@ return [
'new_license' => 'New License',
'new_accessory' => 'New Accessory',
'new_consumable' => 'New Consumable',
'new_component' => 'New Component',
'new_user' => 'New User',
'collapse' => 'Collapse',
'assigned' => 'Assigned',
'asset_count' => 'Asset Count',

View File

@@ -172,7 +172,6 @@ return [
'url' => 'The :attribute field must be a valid URL.',
'ulid' => 'The :attribute field must be a valid ULID.',
'uuid' => 'The :attribute field must be a valid UUID.',
'fmcs_location' => 'Full multiple company support and location scoping is enabled in the Admin Settings, and the selected location and selected company are not compatible.',
/*

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