Merge branch 'develop' into snipeit_v7_laravel10

This commit is contained in:
Brady Wetherington 2023-10-03 20:37:17 +01:00
commit 7a5714cc85
1209 changed files with 15323 additions and 3114 deletions

View file

@ -1,44 +0,0 @@
version: 1
environment:
php: 8.0
node: 12
services:
- mysql: 5.7
on:
push:
branches:
- master
- develop
pull_request:
branches: .*
pipeline:
- name: Setup
cmd: |
cp -v .env.testing.example .env
cp -v .env.testing.example .env.testing
composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Generate Key
cmd: |
php artisan key:generate --force
- name: Passport Keys
cmd: |
php artisan passport:keys
- name: Run Migrations
cmd: |
php artisan migrate --force
- name: PHPUnit Unit Tests
cmd: |
php artisan test --testsuite Unit
- name: PHPUnit Feature Tests
cmd: |
php artisan test --testsuite Feature

View file

@ -18,5 +18,5 @@ importer: ["/app/Importer/*","/app/Http/Livewire/Importer.php", "resources/views
cli / artisan: ["/app/Console/*"]
LDAP: ["*Ldap*", "/app/Console/Commands/Ldap*","/app/Models/Ldap.php"]
docker: ["*docker/*", "Dockerfile", "Dockerfile.alpine", "Dockerfile.fpm-alpine", ".dockerignore", ".env.docker"]
tests: ["/tests/*", "/stubs"]
tests: ["/tests/*", "/database/factories/*", "/stubs"]
config: .github

View file

@ -2,5 +2,6 @@ version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "develop"
schedule:
interval: "weekly"

View file

@ -26,7 +26,7 @@ jobs:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3.3.0
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View file

@ -32,7 +32,7 @@ jobs:
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout code
uses: actions/checkout@v3.3.0
uses: actions/checkout@v4
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI

View file

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Crowdin push
uses: crowdin/github-action@v1

View file

@ -32,6 +32,7 @@ jobs:
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.
@ -41,17 +42,17 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v3.3.0
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2
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@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@ -63,7 +64,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
@ -72,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.alpine

View file

@ -32,6 +32,7 @@ jobs:
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=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.
@ -41,17 +42,17 @@ jobs:
steps:
# https://github.com/actions/checkout
- name: Checkout codebase
uses: actions/checkout@v3.3.0
uses: actions/checkout@v4
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2
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@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@ -63,7 +64,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
@ -72,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile

View file

@ -37,7 +37,7 @@ jobs:
php-version: "${{ matrix.php-version }}"
coverage: none
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get Composer Cache Directory
id: composer-cache

View file

@ -1,4 +1,4 @@
![Build Status](https://app.chipperci.com/projects/0e5f8979-31eb-4ee6-9abf-050b76ab0383/status/master) [![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/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
[![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/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-326-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev)
## Snipe-IT - Open Source Asset Management System

View file

@ -180,10 +180,6 @@ class LdapSync extends Command
}
}
/* Create user account entries in Snipe-IT */
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 20);
$pass = bcrypt($tmp_pass);
$manager_cache = [];
if($ldap_default_group != null) {
@ -229,7 +225,7 @@ class LdapSync extends Command
} else {
// Creating a new user.
$user = new User;
$user->password = $pass;
$user->password = $user->noPassword();
$user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below)
$item['createorupdate'] = 'created';
}

View file

@ -46,6 +46,7 @@ class AssetModelsController extends Controller
'requestable',
'assets_count',
'category',
'fieldset',
];
$assetmodels = AssetModel::select([
@ -94,6 +95,9 @@ class AssetModelsController extends Controller
case 'category':
$assetmodels->OrderCategory($order);
break;
case 'fieldset':
$assetmodels->OrderFieldset($order);
break;
default:
$assetmodels->orderBy($sort, $order);
break;

View file

@ -27,7 +27,7 @@ class DepartmentsController extends Controller
$this->authorize('view', Department::class);
$allowed_columns = ['id', 'name', 'image', 'users_count'];
$departments = Company::scopeCompanyables(Department::select(
$departments = Department::select(
'departments.id',
'departments.name',
'departments.phone',
@ -37,8 +37,8 @@ class DepartmentsController extends Controller
'departments.manager_id',
'departments.created_at',
'departments.updated_at',
'departments.image'),
"company_id", "departments")->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
'departments.image'
)->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count');
if ($request->filled('search')) {
$departments = $departments->TextSearch($request->input('search'));

View file

@ -363,8 +363,12 @@ class UsersController extends Controller
$user->permissions = $permissions_array;
}
$tmp_pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 40);
$user->password = bcrypt($request->get('password', $tmp_pass));
//
if ($request->filled('password')) {
$user->password = bcrypt($request->get('password'));
} else {
$user->password = $user->noPassword();
}
app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar');

View file

@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Models\AssetModel;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Validator;
@ -174,7 +175,14 @@ class AssetModelsController extends Controller
}
}
if ($model->save()) {
if ($model->wasChanged('eol')) {
$newEol = $model->eol;
$model->assets()->whereNotNull('purchase_date')->where('eol_explicit', false)
->update(['asset_eol_date' => DB::raw('DATE_ADD(purchase_date, INTERVAL ' . $newEol . ' MONTH)')]);
}
return redirect()->route('models.index')->with('success', trans('admin/models/message.update.success'));
}

View file

@ -91,7 +91,8 @@ class AssetCheckoutController extends Controller
$settings = \App\Models\Setting::getSettings();
if ($settings->full_multiple_companies_support){
// We have to check whether $target->company_id is null here since locations don't have a company yet
if (($settings->full_multiple_companies_support) && ((!is_null($target->company_id)) && (!is_null($asset->company_id)))) {
if ($target->company_id != $asset->company_id){
return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company'));
}

View file

@ -6,6 +6,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Actionlog;
use Illuminate\Support\Facades\Log;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\CheckoutRequest;
@ -14,26 +15,18 @@ use App\Models\Location;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use App\View\Label;
use Auth;
use Carbon\Carbon;
use DB;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Cookie;
use Input;
use Intervention\Image\Facades\Image;
use Illuminate\Support\Facades\Validator;
use League\Csv\Reader;
use League\Csv\Statement;
use Paginator;
use Redirect;
use Response;
use Slack;
use Str;
use TCPDF;
use View;
use Illuminate\Support\Facades\Redirect;
/**
* This class controls all actions related to assets for
@ -173,9 +166,9 @@ class AssetsController extends Controller
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
@ -298,10 +291,10 @@ class AssetsController extends Controller
/**
* Validate and process asset edit form.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v1.0]
* @return Redirect
* @return \Illuminate\Http\RedirectResponse|Redirect
*@since [v1.0]
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function update(ImageUploadRequest $request, $assetId = null)
{
@ -315,9 +308,23 @@ class AssetsController extends Controller
$asset->status_id = $request->input('status_id', null);
$asset->warranty_months = $request->input('warranty_months', null);
$asset->purchase_cost = $request->input('purchase_cost', null);
$asset->asset_eol_date = request('asset_eol_date', null);
$asset->purchase_date = $request->input('purchase_date', null);
if ($request->filled('purchase_date') && !$request->filled('asset_eol_date') && $asset->model->eol) {
$asset->purchase_date = $request->input('purchase_date', null);
$asset->asset_eol_date = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d');
} elseif ($request->filled('asset_eol_date')) {
$asset->asset_eol_date = $request->input('asset_eol_date', null);
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
if($asset->model->eol) {
if($months != $asset->model->eol) {
$asset->eol_explicit = true;
} else {
$asset->eol_explicit = false;
}
} else {
$asset->eol_explicit = true;
}
}
$asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null);
@ -342,7 +349,7 @@ class AssetsController extends Controller
unlink(public_path().'/uploads/assets/'.$asset->image);
$asset->image = '';
} catch (\Exception $e) {
\Log::info($e);
Log::info($e);
}
}
@ -370,9 +377,9 @@ class AssetsController extends Controller
if ($field->field_encrypted == '1') {
if (Gate::allows('admin')) {
if (is_array($request->input($field->db_column))) {
$asset->{$field->db_column} = \Crypt::encrypt(implode(', ', $request->input($field->db_column)));
$asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column)));
} else {
$asset->{$field->db_column} = \Crypt::encrypt($request->input($field->db_column));
$asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column));
}
}
} else {
@ -420,7 +427,7 @@ class AssetsController extends Controller
try {
Storage::disk('public')->delete('assets'.'/'.$asset->image);
} catch (\Exception $e) {
\Log::debug($e);
Log::debug($e);
}
}
@ -535,7 +542,7 @@ class AssetsController extends Controller
return response($barcode_obj->getPngData())->header('Content-type', 'image/png');
} catch (\Exception $e) {
\Log::debug('The barcode format is invalid.');
Log::debug('The barcode format is invalid.');
return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif');
}
@ -856,7 +863,7 @@ class AssetsController extends Controller
'next_audit_date' => 'date|nullable',
];
$validator = \Validator::make($request->all(), $rules);
$validator = Validator::make($request->all(), $rules);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all()));
@ -873,7 +880,7 @@ class AssetsController extends Controller
// Check to see if they checked the box to update the physical location,
// not just note it in the audit notes
if ($request->input('update_location') == '1') {
\Log::debug('update location in audit');
Log::debug('update location in audit');
$asset->location_id = $request->input('location_id');
}

View file

@ -191,9 +191,11 @@ class LoginController extends Controller
$ldap_attr = Ldap::parseAndMapLdapAttributes($ldap_user);
$user->password = $user->noPassword();
if (Setting::getSettings()->ldap_pw_sync=='1') {
$user->password = bcrypt($request->input('password'));
}
$user->email = $ldap_attr['email'];
$user->first_name = $ldap_attr['firstname'];
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.

View file

@ -31,7 +31,7 @@ class LabelsController extends Controller
$exampleAsset->id = 999999;
$exampleAsset->name = 'JEN-867-5309';
$exampleAsset->asset_tag = 'TCA-00001';
$exampleAsset->asset_tag = '100001';
$exampleAsset->serial = 'SN9876543210';
$exampleAsset->company = new Company();

View file

@ -76,7 +76,7 @@ class LicenseCheckinController extends Controller
// Declare the rules for the form validation
$rules = [
'note' => 'string|nullable',
'notes' => 'string|nullable',
];
// Create a new validator instance from our validation rules
@ -97,6 +97,7 @@ class LicenseCheckinController extends Controller
// Update the asset data
$licenseSeat->assigned_to = null;
$licenseSeat->asset_id = null;
$licenseSeat->notes = $request->input('notes');
// Was the asset updated?
if ($licenseSeat->save()) {
@ -128,6 +129,13 @@ class LicenseCheckinController extends Controller
$license = License::findOrFail($licenseId);
$this->authorize('checkin', $license);
if (! $license->reassignable) {
// Not allowed to checkin
Session::flash('error', 'License not reassignable.');
return redirect()->back()->withInput();
}
$licenseSeatsByUser = LicenseSeat::where('license_id', '=', $licenseId)
->whereNotNull('assigned_to')
->with('user')

View file

@ -63,6 +63,7 @@ class LicenseCheckoutController extends Controller
$licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId);
$licenseSeat->user_id = Auth::id();
$licenseSeat->notes = $request->input('notes');
$checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type'));

View file

@ -275,6 +275,7 @@ class Importer extends Component
'license_email' => trans('admin/licenses/form.to_email'),
'license_name' => trans('admin/licenses/form.to_name'),
'purchase_order' => trans('admin/licenses/form.purchase_order'),
'order_number' => trans('general.order_number'),
'reassignable' => trans('admin/licenses/form.reassignable'),
'seats' => trans('admin/licenses/form.seats'),
'notes' => trans('general.notes'),
@ -484,8 +485,17 @@ class Importer extends Component
public function selectFile($id)
{
$this->clearMessage();
$this->activeFile = Import::find($id);
if (!$this->activeFile) {
$this->message = trans('admin/hardware/message.import.file_missing');
$this->message_type = 'danger';
return;
}
$this->field_map = null;
foreach($this->activeFile->header_row as $element) {
if(isset($this->activeFile->field_map[$element])) {
@ -520,6 +530,12 @@ class Importer extends Component
}
}
public function clearMessage()
{
$this->message = null;
$this->message_type = null;
}
public function render()
{
$this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders.

View file

@ -12,7 +12,7 @@ class SlackSettingsForm extends Component
public $webhook_endpoint;
public $webhook_channel;
public $webhook_botname;
public $isDisabled ='' ;
public $isDisabled ='disabled' ;
public $webhook_name;
public $webhook_link;
public $webhook_placeholder;
@ -22,11 +22,17 @@ class SlackSettingsForm extends Component
public Setting $setting;
public $webhook_endpoint_rules;
protected $rules = [
'webhook_endpoint' => 'url|required_with:webhook_channel|starts_with:https://hooks.slack.com/services|nullable',
'webhook_endpoint' => 'required_with:webhook_channel|starts_with:http://,https://,ftp://,irc://,https://hooks.slack.com/services/|url|nullable',
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
'webhook_botname' => 'string|nullable',
];
public $messages = [
'webhook_endpoint.starts_with' => 'your webhook endpoint should begin with http://, https:// or other protocol.',
];
public function mount() {
$this->webhook_text= [
@ -55,9 +61,7 @@ class SlackSettingsForm extends Component
$this->webhook_botname = $this->setting->webhook_botname;
$this->webhook_options = $this->setting->webhook_selected;
if($this->setting->webhook_selected == 'general'){
$this->isDisabled='';
}
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
$this->isDisabled= '';
}
@ -65,9 +69,8 @@ class SlackSettingsForm extends Component
}
public function updated($field) {
if($this->webhook_selected != 'general') {
$this->validateOnly($field, $this->rules);
}
}
public function updatedWebhookSelected() {
@ -82,7 +85,6 @@ class SlackSettingsForm extends Component
}
private function isButtonDisabled() {
if($this->webhook_selected == 'slack') {
if (empty($this->webhook_endpoint)) {
$this->isDisabled = 'disabled';
$this->save_button = trans('admin/settings/general.webhook_presave');
@ -93,8 +95,6 @@ class SlackSettingsForm extends Component
}
}
}
public function render()
{
$this->isButtonDisabled();
@ -108,6 +108,7 @@ class SlackSettingsForm extends Component
'defaults' => [
'exceptions' => false,
],
'allow_redirects' => false,
]);
$payload = json_encode(
@ -116,18 +117,23 @@ class SlackSettingsForm extends Component
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
'username' => e($this->webhook_botname),
'icon_emoji' => ':heart:',
]);
try {
$test = $webhook->post($this->webhook_endpoint, ['body' => $payload]);
$webhook->post($this->webhook_endpoint, ['body' => $payload]);
if(($test->getStatusCode() == 302)||($test->getStatusCode() == 301)){
return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
}
$this->isDisabled='';
$this->save_button = trans('general.save');
return session()->flash('success' , 'Your '.$this->webhook_name.' Integration works!');
return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
} catch (\Exception $e) {
$this->isDisabled= 'disabled';
$this->isDisabled='disabled';
$this->save_button = trans('admin/settings/general.webhook_presave');
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
}
@ -158,9 +164,7 @@ class SlackSettingsForm extends Component
if (Helper::isDemoMode()) {
session()->flash('error',trans('general.feature_disabled'));
} else {
if ($this->webhook_selected != 'general') {
$this->validate($this->rules);
}
$this->setting->webhook_selected = $this->webhook_selected;
$this->setting->webhook_endpoint = $this->webhook_endpoint;

View file

@ -43,9 +43,10 @@ class ActionlogsTransformer
public function transformActionlog (Actionlog $actionlog, $settings = null)
{
$icon = $actionlog->present()->icon();
$custom_field = CustomField::all();
$custom_fields = CustomField::all();
if ($actionlog->filename!='') {
$icon = e(\App\Helpers\Helper::filetype_icon($actionlog->filename));
$icon = Helper::filetype_icon($actionlog->filename);
}
// This is necessary since we can't escape special characters within a JSON object
@ -55,17 +56,29 @@ class ActionlogsTransformer
$clean_meta = [];
if ($meta_array) {
foreach ($meta_array as $fieldname => $fieldata) {
if( str_starts_with($fieldname, '_snipeit_')){
if( $custom_field->where('db_column', '=', $fieldname)->where('field_encrypted', true)){
$clean_meta[$fieldname]['old'] = "encrypted";
$clean_meta[$fieldname]['new'] = "encrypted";
}
}
else {
$clean_meta[$fieldname]['old'] = $this->clean_field($fieldata->old);
$clean_meta[$fieldname]['new'] = $this->clean_field($fieldata->new);
// this is a custom field
if (str_starts_with($fieldname, '_snipeit_')) {
foreach ($custom_fields as $custom_field) {
if ($custom_field->db_column == $fieldname) {
if ($custom_field->field_encrypted == '1') {
$clean_meta[$fieldname]['old'] = "************";
$clean_meta[$fieldname]['new'] = "************";
}
}
}
}
}
}

View file

@ -45,6 +45,7 @@ class LicenseSeatsTransformer
'name'=> e($seat->location()->name),
] : null,
'reassignable' => (bool) $seat->license->reassignable,
'notes' => e($seat->notes),
'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')),
];

View file

@ -3,7 +3,9 @@
namespace App\Importer;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Statuslabel;
use Carbon\Carbon;
class AssetImporter extends ItemImporter
{
@ -63,6 +65,7 @@ class AssetImporter extends ItemImporter
$asset_tag = Asset::autoincrement_asset();
}
$asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first();
if ($asset) {
if (! $this->updating) {
@ -117,11 +120,6 @@ class AssetImporter extends ItemImporter
$item['next_audit_date'] = $this->item['next_audit_date'];
}
$item['asset_eol_date'] = null;
if (isset($this->item['asset_eol_date'])) {
$item['asset_eol_date'] = $this->item['asset_eol_date'];
}
if ($editingAsset) {
$asset->update($item);
} else {
@ -135,8 +133,8 @@ class AssetImporter extends ItemImporter
}
}
if ($asset->save()) {
$asset->logCreate(trans('general.importer.import_note'));
$this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created');

View file

@ -28,7 +28,7 @@ abstract class Importer
/**
* Default Map of item fields->csv names
*
* This has been moved into Livewire/Importer.php to be more granular.
* This has been moved into app/Http/Livewire/Importer.php to be more granular.
* @todo - remove references to this property since we don't use it anymore.
*
* @var array

View file

@ -10,6 +10,8 @@ use App\Models\Manufacturer;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Log;
class ItemImporter extends Importer
{
@ -88,8 +90,14 @@ class ItemImporter extends Importer
}
$this->item['asset_eol_date'] = null;
if ($this->findCsvMatch($row, 'asset_eol_date') != '') {
$this->item['asset_eol_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'asset_eol_date')));
if($this->findCsvMatch($row, 'asset_eol_date') != '') {
$csvMatch = $this->findCsvMatch($row, 'asset_eol_date');
try {
$this->item['asset_eol_date'] = CarbonImmutable::parse($csvMatch)->format('Y-m-d');
} catch (\Exception $e) {
Log::info($e->getMessage());
$this->log('Unable to parse date: '.$csvMatch);
}
}
$this->item['qty'] = $this->findCsvMatch($row, 'quantity');

View file

@ -65,6 +65,7 @@ class LicenseImporter extends ItemImporter
$this->item['license_name'] = $this->findCsvMatch($row, 'license_name');
$this->item['maintained'] = $this->findCsvMatch($row, 'maintained');
$this->item['purchase_order'] = $this->findCsvMatch($row, 'purchase_order');
$this->item['order_number'] = $this->findCsvMatch($row, 'order_number');
$this->item['reassignable'] = $this->findCsvMatch($row, 'reassignable');
$this->item['manufacturer'] = $this->createOrFetchManufacturer($this->findCsvMatch($row, 'manufacturer'));

View file

@ -79,6 +79,7 @@ class CheckoutableListener
/**
* Send the appropriate notification
*/
if ($event->checkedOutTo && $event->checkoutable){
$acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
->where('assigned_to_id', $event->checkedOutTo->id)
->get();
@ -88,6 +89,7 @@ class CheckoutableListener
$acceptance->delete();
}
}
}
try {
if ($this->shouldSendWebhookNotification()) {
@ -142,9 +144,11 @@ class CheckoutableListener
$notifiables = collect();
/**
* Notify the user who checked out the item
* Notify who checked out the item as long as the model can route notifications
*/
if (method_exists($event->checkedOutTo, 'routeNotificationFor')) {
$notifiables->push($event->checkedOutTo);
}
/**
* Notify Admin users if the settings is activated

View file

@ -72,6 +72,8 @@ class Asset extends Depreciable
protected $casts = [
'purchase_date' => 'date',
'asset_eol_date' => 'date',
'eol_explicit' => 'boolean',
'last_checkout' => 'datetime',
'last_checkin' => 'datetime',
'expected_checkin' => 'date',
@ -104,7 +106,8 @@ class Asset extends Depreciable
'serial' => 'unique_serial|nullable',
'purchase_cost' => 'numeric|nullable|gte:0',
'supplier_id' => 'exists:suppliers,id|nullable',
'asset_eol_date' => 'date|max:10|min:10|nullable',
'asset_eol_date' => 'date|nullable',
'eol_explicit' => 'boolean|nullable',
'byod' => 'boolean',
];
@ -136,8 +139,10 @@ class Asset extends Depreciable
'expected_checkin',
'byod',
'asset_eol_date',
'eol_explicit',
'last_audit_date',
'next_audit_date',
'asset_eol_date',
];
use Searchable;

View file

@ -291,4 +291,9 @@ class AssetModel extends SnipeModel
{
return $query->leftJoin('categories', 'models.category_id', '=', 'categories.id')->orderBy('categories.name', $order);
}
public function scopeOrderFieldset($query, $order)
{
return $query->leftJoin('custom_fieldsets', 'models.fieldset_id', '=', 'custom_fieldsets.id')->orderBy('custom_fieldsets.name', $order);
}
}

View file

@ -9,6 +9,7 @@ use Watson\Validating\ValidatingTrait;
class Department extends SnipeModel
{
use CompanyableTrait;
use HasFactory;
/**

View file

@ -21,6 +21,8 @@ class Field {
public static function makeArray(Field $field, Asset $asset) {
return $field->getOptions()
// filter out any FieldOptions that are accidentally null
->filter()
->map(fn($option) => $option->toArray($asset))
->filter(fn($result) => $result['value'] != null);
}

View file

@ -7,6 +7,7 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use TCPDF;
use TCPDF_STATIC;
use TypeError;
/**
* Model for Labels.
@ -372,8 +373,8 @@ abstract class Label
if (empty($value)) return;
try {
$pdf->write1DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
} catch (\Exception $e) {
\Log::error('The 1D barcode ' . $value . ' is not compliant with the barcode type '. $type);
} catch (\Exception|TypeError $e) {
\Log::debug('The 1D barcode ' . $value . ' is not compliant with the barcode type '. $type);
}
}

View file

@ -252,13 +252,10 @@ class Ldap extends Model
$user->last_name = $item['lastname'];
$user->username = $item['username'];
$user->email = $item['email'];
$user->password = $user->noPassword();
if (Setting::getSettings()->ldap_pw_sync == '1') {
$user->password = bcrypt($password);
} else {
$pass = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 25);
$user->password = bcrypt($pass);
}
$user->activated = 1;
@ -268,7 +265,7 @@ class Ldap extends Model
if ($user->save()) {
return $user;
} else {
LOG::debug('Could not create user.'.$user->getErrors());
\Log::debug('Could not create user.'.$user->getErrors());
throw new Exception('Could not create user: '.$user->getErrors());
}
}

View file

@ -9,8 +9,7 @@ class SCIMUser extends User
protected $throwValidationExceptions = true; // we want model-level validation to fully THROW, not just return false
public function __construct(array $attributes = []) {
$attributes['password'] = "*NO PASSWORD*";
// $attributes['activated'] = 1;
$attributes['password'] = $this->noPassword();
parent::__construct($attributes);
}
}

View file

@ -456,6 +456,22 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at');
}
/**
* Set a common string when the user has been imported/synced from:
*
* - LDAP without password syncing
* - SCIM
* - CSV import where no password was provided
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.2.0]
* @return string
*/
public function noPassword()
{
return "*** NO PASSWORD ***";
}
/**
* Query builder scope to return NOT-deleted users

View file

@ -6,6 +6,7 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Setting;
use Auth;
use Carbon\Carbon;
class AssetObserver
{
@ -119,4 +120,29 @@ class AssetObserver
$logAction->user_id = Auth::id();
$logAction->logaction('delete');
}
public function saving(Asset $asset)
{
//determine if calculated eol and then calculate it - this should only happen on a new asset
if(is_null($asset->asset_eol_date) && !is_null($asset->purchase_date) && !is_null($asset->model->eol)){
$asset->asset_eol_date = $asset->purchase_date->addMonths($asset->model->eol)->format('Y-m-d');
$asset->eol_explicit = false;
}
//determine if explicit and set eol_explit to true
if(!is_null($asset->asset_eol_date) && !is_null($asset->purchase_date)) {
if($asset->model->eol) {
$months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
if($months != $asset->model->eol) {
$asset->eol_explicit = true;
}
}
} elseif (!is_null($asset->asset_eol_date) && is_null($asset->purchase_date)) {
$asset->eol_explicit = true;
}
if ((!is_null($asset->asset_eol_date)) && (!is_null($asset->purchase_date)) && (is_null($asset->model->eol))) {
$asset->eol_explicit = true;
}
}
}

View file

@ -3,6 +3,7 @@
namespace App\Presenters;
use App\Models\CustomField;
use Carbon\CarbonImmutable;
use DateTime;
/**
@ -142,8 +143,8 @@ class AssetPresenter extends Presenter
'formatter' => 'dateDisplayFormatter',
], [
'field' => 'age',
'searchable' => true,
'sortable' => true,
'searchable' => false,
'sortable' => false,
'visible' => false,
'title' => trans('general.age'),
], [
@ -429,10 +430,7 @@ class AssetPresenter extends Presenter
public function eol_date()
{
if (($this->purchase_date) && ($this->model->model) && ($this->model->model->eol)) {
$date = date_create($this->purchase_date);
date_add($date, date_interval_create_from_date_string($this->model->model->eol.' months'));
return date_format($date, 'Y-m-d');
return CarbonImmutable::parse($this->purchase_date)->addMonths($this->model->model->eol)->format('Y-m-d');
}
}

View file

@ -254,6 +254,14 @@ class LicensePresenter extends Presenter
'visible' => true,
'formatter' => 'locationsLinkObjFormatter',
],
[
'field' => 'notes',
'searchable' => false,
'sortable' => false,
'visible' => false,
'title' => trans('general.notes'),
'formatter' => 'notesFormatter'
],
[
'field' => 'checkincheckout',
'searchable' => false,

View file

@ -239,7 +239,7 @@ return [
|
*/
'min_php' => '7.2.5',
'min_php' => '7.4.0',
/*

View file

@ -35,6 +35,13 @@ return [
'files' => [
/*
* This path is used to make directories in resulting zip-file relative
* Set to false to include complete absolute path
* Example: base_path()
*/
'relative_path' => base_path(),
/*
* The list of directories and files that will be included in the backup.
*/

View file

@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v6.2.0-pre',
'full_app_version' => 'v6.2.0-pre - build 11391-g319cb2305',
'build_version' => '11391',
'app_version' => 'v6.2.1',
'full_app_version' => 'v6.2.1 - build 11625-gc45ede2d1',
'build_version' => '11625',
'prerelease_version' => '',
'hash_version' => 'g319cb2305',
'full_hash' => 'v6.2.0-pre-451-g319cb2305',
'hash_version' => 'gc45ede2d1',
'full_hash' => 'v6.2.1-47-gc45ede2d1',
'branch' => 'develop',
);

View file

@ -8,6 +8,8 @@ use App\Models\Location;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Factories\Factory;
class AssetFactory extends Factory
@ -49,6 +51,18 @@ class AssetFactory extends Factory
];
}
public function configure()
{
return $this->afterMaking(function (Asset $asset) {
// calculates the EOL date most of the time, but sometimes sets a random date so we have some explicits
// the explicit boolean gets set in the saving() method on the observer
$asset->asset_eol_date = $this->faker->boolean(5)
? CarbonImmutable::parse($asset->purchase_date)->addMonths(rand(0, 20))->format('Y-m-d')
: CarbonImmutable::parse($asset->purchase_date)->addMonths($asset->model->eol)->format('Y-m-d');
});
}
public function laptopMbp()
{
return $this->state(function () {

View file

@ -22,7 +22,7 @@ class CompanyFactory extends Factory
public function definition()
{
return [
'name' => $this->faker->company(),
'name' => $this->faker->unique()->company(),
];
}
}

View file

@ -22,7 +22,7 @@ class CustomFieldFactory extends Factory
public function definition()
{
return [
'name' => $this->faker->catchPhrase(),
'name' => $this->faker->unique()->catchPhrase(),
'format' => '',
'element' => 'text',
'auto_add_to_fieldsets' => '0',

View file

@ -22,7 +22,7 @@ class CustomFieldsetFactory extends Factory
public function definition()
{
return [
'name' => $this->faker->catchPhrase(),
'name' => $this->faker->unique()->catchPhrase(),
];
}

View file

@ -23,7 +23,7 @@ class DepreciationFactory extends Factory
public function definition()
{
return [
'name' => $this->faker->catchPhrase(),
'name' => $this->faker->unique()->catchPhrase(),
'user_id' => User::factory()->superuser(),
'months' => 36,
];

View file

@ -23,7 +23,7 @@ class ManufacturerFactory extends Factory
public function definition()
{
return [
'name' => $this->faker->company(),
'name' => $this->faker->unique()->company(),
'user_id' => User::factory()->superuser(),
'support_phone' => $this->faker->phoneNumber(),
'url' => $this->faker->url(),

View file

@ -33,7 +33,7 @@ class UserFactory extends Factory
'permissions' => '{}',
'phone' => $this->faker->phoneNumber(),
'state' => $this->faker->stateAbbr(),
'username' => $this->faker->username(),
'username' => $this->faker->unique()->username(),
'zip' => $this->faker->postcode(),
];
}

View file

@ -0,0 +1,71 @@
<?php
use App\Models\Asset;
use Carbon\CarbonImmutable;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Log;
class DenormalizedEolAndAddColumnForExplicitDateToAssets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('assets', function (Blueprint $table) {
$table->boolean('eol_explicit')->default(false)->after('asset_eol_date');
});
// Update the eol_explicit column with the value from asset_eol_date if it exists and is different from the calculated value
Asset::whereNotNull('asset_eol_date')->with('model')->chunkById(500, function ($assetsWithEolDates) {
foreach ($assetsWithEolDates as $asset) {
if ($asset->asset_eol_date && $asset->purchase_date) {
try {
$months = CarbonImmutable::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date);
} catch (\Exception $e) {
Log::info('asset_eol_date invalid for asset '.$asset->id);
}
if ($asset->model->eol) {
if ($months != $asset->model->eol) {
$asset->update(['eol_explicit' => true]);
}
} else {
$asset->update(['eol_explicit' => true]);
}
}
}
});
// Update the asset_eol_date column with the calculated value if it doesn't exist
Asset::whereNull('asset_eol_date')->with('model')->chunkById(500, function ($assets) {
foreach ($assets as $asset) {
if ($asset->model->eol && $asset->purchase_date) {
try {
$asset_eol_date = CarbonImmutable::parse($asset->purchase_date)->addMonths($asset->model->eol)->format('Y-m-d');
$asset->update(['asset_eol_date' => $asset_eol_date]);
} catch (\Exception $e) {
Log::info('purchase date invalid for asset '.$asset->id);
}
}
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('assets', function (Blueprint $table) {
$table->dropColumn('eol_explicit');
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class FixAssetModelMinQtyNullability extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('models', function (Blueprint $table) {
$table->integer('min_amt')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('models', function (Blueprint $table) {
$table->integer('min_amt')->nullable(false)->change();
});
}
}

3781
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -37,6 +37,7 @@
"bootstrap-less": "^3.3.8",
"bootstrap-table": "1.22.1",
"chart.js": "^2.9.4",
"clipboard": "^2.0.11",
"css-loader": "^4.0.0",
"ekko-lightbox": "^5.1.1",
"imagemin": "^8.0.1",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/js/dist/all.js vendored

Binary file not shown.

Binary file not shown.

View file

@ -1,8 +1,8 @@
{
"/js/build/app.js": "/js/build/app.js?id=43fc984e5d0f901e04cef2474972e97f",
"/js/build/app.js": "/js/build/app.js?id=72071a8a4dc754c61b0440d3c4119cbf",
"/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=392cc93cfc0be0349bab9697669dd091",
"/css/build/overrides.css": "/css/build/overrides.css?id=eb013ebb79d92e25ce24b0c0b53185e4",
"/css/build/app.css": "/css/build/app.css?id=ac51a0600b419996e705675b6fcf1a8b",
"/css/build/overrides.css": "/css/build/overrides.css?id=d96bcc45dc2a4414dd9840a14b096d4f",
"/css/build/app.css": "/css/build/app.css?id=b0aa590a3a4de33d19147264fd31b743",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=a67bd93bed52e6a29967fe472de66d6c",
"/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=268041e902b019730c23ee3875838005",
"/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=d409d9b1a3b69247df8b98941ba06e33",
@ -18,7 +18,7 @@
"/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=f0fbbb0ac729ea092578fb05ca615460",
"/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=b9a74ec0cd68f83e7480d5ae39919beb",
"/css/dist/all.css": "/css/dist/all.css?id=829410b57e28448fef5c57beda0861e3",
"/css/dist/all.css": "/css/dist/all.css?id=6d6bfa80b1bd2785b35a85ea81daafc8",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=a656b2d865fe379d8851757e8e4001ef",
@ -29,10 +29,10 @@
"/css/webfonts/fa-solid-900.woff2": "/css/webfonts/fa-solid-900.woff2?id=7f63d634454e771396bce3e09dfcdbc5",
"/css/webfonts/fa-v4compatibility.ttf": "/css/webfonts/fa-v4compatibility.ttf?id=70ad875b2378eb850254f01dec991ade",
"/css/webfonts/fa-v4compatibility.woff2": "/css/webfonts/fa-v4compatibility.woff2?id=d36941873b661076f146b0221f13497d",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=281bcfe26549412d128f695234961081",
"/js/build/vendor.js": "/js/build/vendor.js?id=8ac1d250496313e93744790e5138305d",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=df78f0c4cc93c29c02a41144590f6350",
"/js/dist/all.js": "/js/dist/all.js?id=b487452bd6bef7fa6201586415d383f2",
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=2bd29fa7f9d666800c246a52ce708633",
"/js/build/vendor.js": "/js/build/vendor.js?id=ede02ee2aad89fe10c64a87f6c76838a",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=1f678160a05960c3087fb8263168ff41",
"/js/dist/all.js": "/js/dist/all.js?id=c539685e205ada5b7259499d491fae87",
"/js/dist/all-defer.js": "/js/dist/all-defer.js?id=07e52318da2cdf3171c4d88113f25fb6",
"/css/dist/skins/skin-green.min.css": "/css/dist/skins/skin-green.min.css?id=b48f4d8af0e1ca5621c161e93951109f",
"/css/dist/skins/skin-green-dark.min.css": "/css/dist/skins/skin-green-dark.min.css?id=44f9320d0739f419c9246f7f39395b02",

View file

@ -0,0 +1,40 @@
/*
* dragtable
*
* @Version 2.0.15
*
* default css
*
*/
/*##### the dragtable stuff #####*/
.dragtable-sortable {
list-style-type: none; margin: 0; padding: 0; -moz-user-select: none;
}
.dragtable-sortable li {
margin: 0; padding: 0; float: left; font-size: 1em; background: white;
}
.dragtable-sortable th, .dragtable-sortable td{
border-left: 0px;
}
.dragtable-sortable li:first-child th, .dragtable-sortable li:first-child td {
border-left: 1px solid #CCC;
}
.ui-sortable-helper {
opacity: 0.7;filter: alpha(opacity=70);
}
.ui-sortable-placeholder {
-moz-box-shadow: 4px 5px 4px #C6C6C6 inset;
-webkit-box-shadow: 4px 5px 4px #C6C6C6 inset;
box-shadow: 4px 5px 4px #C6C6C6 inset;
border-bottom: 1px solid #CCCCCC;
border-top: 1px solid #CCCCCC;
visibility: visible !important;
background: #EFEFEF !important;
visibility: visible !important;
}
.ui-sortable-placeholder * {
opacity: 0.0; visibility: hidden;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,403 @@
/*!
* dragtable
*
* @Version 2.0.15
*
* Copyright (c) 2010-2013, Andres akottr@gmail.com
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* Inspired by the the dragtable from Dan Vanderkam (danvk.org/dragtable/)
* Thanks to the jquery and jqueryui comitters
*
* Any comment, bug report, feature-request is welcome
* Feel free to contact me.
*/
/* TOKNOW:
* For IE7 you need this css rule:
* table {
* border-collapse: collapse;
* }
* Or take a clean reset.css (see http://meyerweb.com/eric/tools/css/reset/)
*/
/* TODO: investigate
* Does not work properly with css rule:
* html {
* overflow: -moz-scrollbars-vertical;
* }
* Workaround:
* Fixing Firefox issues by scrolling down the page
* http://stackoverflow.com/questions/2451528/jquery-ui-sortable-scroll-helper-element-offset-firefox-issue
*
* var start = $.noop;
* var beforeStop = $.noop;
* if($.browser.mozilla) {
* var start = function (event, ui) {
* if( ui.helper !== undefined )
* ui.helper.css('position','absolute').css('margin-top', $(window).scrollTop() );
* }
* var beforeStop = function (event, ui) {
* if( ui.offset !== undefined )
* ui.helper.css('margin-top', 0);
* }
* }
*
* and pass this as start and stop function to the sortable initialisation
* start: start,
* beforeStop: beforeStop
*/
/*
* Special thx to all pull requests comitters
*/
(function($) {
$.widget("akottr.dragtable", {
options: {
revert: false, // smooth revert
dragHandle: '.table-handle', // handle for moving cols, if not exists the whole 'th' is the handle
maxMovingRows: 40, // 1 -> only header. 40 row should be enough, the rest is usually not in the viewport
excludeFooter: false, // excludes the footer row(s) while moving other columns. Make sense if there is a footer with a colspan. */
onlyHeaderThreshold: 100, // TODO: not implemented yet, switch automatically between entire col moving / only header moving
dragaccept: null, // draggable cols -> default all
persistState: null, // url or function -> plug in your custom persistState function right here. function call is persistState(originalTable)
restoreState: null, // JSON-Object or function: some kind of experimental aka Quick-Hack TODO: do it better
exact: true, // removes pixels, so that the overlay table width fits exactly the original table width
clickDelay: 10, // ms to wait before rendering sortable list and delegating click event
containment: null, // @see http://api.jqueryui.com/sortable/#option-containment, use it if you want to move in 2 dimesnions (together with axis: null)
cursor: 'move', // @see http://api.jqueryui.com/sortable/#option-cursor
cursorAt: false, // @see http://api.jqueryui.com/sortable/#option-cursorAt
distance: 0, // @see http://api.jqueryui.com/sortable/#option-distance, for immediate feedback use "0"
tolerance: 'pointer', // @see http://api.jqueryui.com/sortable/#option-tolerance
axis: 'x', // @see http://api.jqueryui.com/sortable/#option-axis, Only vertical moving is allowed. Use 'x' or null. Use this in conjunction with the 'containment' setting
beforeStart: $.noop, // returning FALSE will stop the execution chain.
beforeMoving: $.noop,
beforeReorganize: $.noop,
beforeStop: $.noop
},
originalTable: {
el: null,
selectedHandle: null,
sortOrder: null,
startIndex: 0,
endIndex: 0
},
sortableTable: {
el: $(),
selectedHandle: $(),
movingRow: $()
},
persistState: function() {
var _this = this;
this.originalTable.el.find('th').each(function(i) {
if (this.id !== '') {
_this.originalTable.sortOrder[this.id] = i;
}
});
$.ajax({
url: this.options.persistState,
data: this.originalTable.sortOrder
});
},
/*
* persistObj looks like
* {'id1':'2','id3':'3','id2':'1'}
* table looks like
* | id2 | id1 | id3 |
*/
_restoreState: function(persistObj) {
for (var n in persistObj) {
this.originalTable.startIndex = $('#' + n).closest('th').prevAll().length + 1;
this.originalTable.endIndex = parseInt(persistObj[n], 10) + 1;
this._bubbleCols();
}
},
// bubble the moved col left or right
_bubbleCols: function() {
var i, j, col1, col2;
var from = this.originalTable.startIndex;
var to = this.originalTable.endIndex;
/* Find children thead and tbody.
* Only to process the immediate tr-children. Bugfix for inner tables
*/
var thtb = this.originalTable.el.children();
if (this.options.excludeFooter) {
thtb = thtb.not('tfoot');
}
if (from < to) {
for (i = from; i < to; i++) {
col1 = thtb.find('> tr > td:nth-child(' + i + ')')
.add(thtb.find('> tr > th:nth-child(' + i + ')'));
col2 = thtb.find('> tr > td:nth-child(' + (i + 1) + ')')
.add(thtb.find('> tr > th:nth-child(' + (i + 1) + ')'));
for (j = 0; j < col1.length; j++) {
swapNodes(col1[j], col2[j]);
}
}
} else {
for (i = from; i > to; i--) {
col1 = thtb.find('> tr > td:nth-child(' + i + ')')
.add(thtb.find('> tr > th:nth-child(' + i + ')'));
col2 = thtb.find('> tr > td:nth-child(' + (i - 1) + ')')
.add(thtb.find('> tr > th:nth-child(' + (i - 1) + ')'));
for (j = 0; j < col1.length; j++) {
swapNodes(col1[j], col2[j]);
}
}
}
},
_rearrangeTableBackroundProcessing: function() {
var _this = this;
return function() {
_this._bubbleCols();
_this.options.beforeStop(_this.originalTable);
_this.sortableTable.el.remove();
restoreTextSelection();
// persist state if necessary
if (_this.options.persistState !== null) {
$.isFunction(_this.options.persistState) ? _this.options.persistState(_this.originalTable) : _this.persistState();
}
};
},
_rearrangeTable: function() {
var _this = this;
return function() {
// remove handler-class -> handler is now finished
_this.originalTable.selectedHandle.removeClass('dragtable-handle-selected');
// add disabled class -> reorgorganisation starts soon
_this.sortableTable.el.sortable("disable");
_this.sortableTable.el.addClass('dragtable-disabled');
_this.options.beforeReorganize(_this.originalTable, _this.sortableTable);
// do reorganisation asynchronous
// for chrome a little bit more than 1 ms because we want to force a rerender
_this.originalTable.endIndex = _this.sortableTable.movingRow.prevAll().length + 1;
setTimeout(_this._rearrangeTableBackroundProcessing(), 50);
};
},
/*
* Disrupts the table. The original table stays the same.
* But on a layer above the original table we are constructing a list (ul > li)
* each li with a separate table representig a single col of the original table.
*/
_generateSortable: function(e) {
!e.cancelBubble && (e.cancelBubble = true);
var _this = this;
// table attributes
var attrs = this.originalTable.el[0].attributes;
var attrsString = '';
for (var i = 0; i < attrs.length; i++) {
if (attrs[i].nodeValue && attrs[i].nodeName != 'id' && attrs[i].nodeName != 'width') {
attrsString += attrs[i].nodeName + '="' + attrs[i].nodeValue + '" ';
}
}
// row attributes
var rowAttrsArr = [];
//compute height, special handling for ie needed :-(
var heightArr = [];
this.originalTable.el.find('tr').slice(0, this.options.maxMovingRows).each(function(i, v) {
// row attributes
var attrs = this.attributes;
var attrsString = "";
for (var j = 0; j < attrs.length; j++) {
if (attrs[j].nodeValue && attrs[j].nodeName != 'id') {
attrsString += " " + attrs[j].nodeName + '="' + attrs[j].nodeValue + '"';
}
}
rowAttrsArr.push(attrsString);
heightArr.push($(this).height());
});
// compute width, no special handling for ie needed :-)
var widthArr = [];
// compute total width, needed for not wrapping around after the screen ends (floating)
var totalWidth = 0;
/* Find children thead and tbody.
* Only to process the immediate tr-children. Bugfix for inner tables
*/
var thtb = _this.originalTable.el.children();
if (this.options.excludeFooter) {
thtb = thtb.not('tfoot');
}
thtb.find('> tr > th').each(function(i, v) {
var w = $(this).is(':visible') ? $(this).outerWidth() : 0;
widthArr.push(w);
totalWidth += w;
});
if(_this.options.exact) {
var difference = totalWidth - _this.originalTable.el.outerWidth();
widthArr[0] -= difference;
}
// one extra px on right and left side
totalWidth += 2
var sortableHtml = '<ul class="dragtable-sortable" style="position:absolute; width:' + totalWidth + 'px;">';
// assemble the needed html
thtb.find('> tr > th').each(function(i, v) {
var width_li = $(this).is(':visible') ? $(this).outerWidth() : 0;
sortableHtml += '<li style="width:' + width_li + 'px;">';
sortableHtml += '<table ' + attrsString + '>';
var row = thtb.find('> tr > th:nth-child(' + (i + 1) + ')');
if (_this.options.maxMovingRows > 1) {
row = row.add(thtb.find('> tr > td:nth-child(' + (i + 1) + ')').slice(0, _this.options.maxMovingRows - 1));
}
row.each(function(j) {
// TODO: May cause duplicate style-Attribute
var row_content = $(this).clone().wrap('<div></div>').parent().html();
if (row_content.toLowerCase().indexOf('<th') === 0) sortableHtml += "<thead>";
sortableHtml += '<tr ' + rowAttrsArr[j] + '" style="height:' + heightArr[j] + 'px;">';
sortableHtml += row_content;
if (row_content.toLowerCase().indexOf('<th') === 0) sortableHtml += "</thead>";
sortableHtml += '</tr>';
});
sortableHtml += '</table>';
sortableHtml += '</li>';
});
sortableHtml += '</ul>';
this.sortableTable.el = this.originalTable.el.before(sortableHtml).prev();
// set width if necessary
this.sortableTable.el.find('> li > table').each(function(i, v) {
$(this).css('width', widthArr[i] + 'px');
});
// assign this.sortableTable.selectedHandle
this.sortableTable.selectedHandle = this.sortableTable.el.find('th .dragtable-handle-selected');
var items = !this.options.dragaccept ? 'li' : 'li:has(' + this.options.dragaccept + ')';
this.sortableTable.el.sortable({
items: items,
stop: this._rearrangeTable(),
// pass thru options for sortable widget
revert: this.options.revert,
tolerance: this.options.tolerance,
containment: this.options.containment,
cursor: this.options.cursor,
cursorAt: this.options.cursorAt,
distance: this.options.distance,
axis: this.options.axis
});
// assign start index
this.originalTable.startIndex = $(e.target).closest('th').prevAll().length + 1;
this.options.beforeMoving(this.originalTable, this.sortableTable);
// Start moving by delegating the original event to the new sortable table
this.sortableTable.movingRow = this.sortableTable.el.find('> li:nth-child(' + this.originalTable.startIndex + ')');
// prevent the user from drag selecting "highlighting" surrounding page elements
disableTextSelection();
// clone the initial event and trigger the sort with it
this.sortableTable.movingRow.trigger($.extend($.Event(e.type), {
which: 1,
clientX: e.clientX,
clientY: e.clientY,
pageX: e.pageX,
pageY: e.pageY,
screenX: e.screenX,
screenY: e.screenY
}));
// Some inner divs to deliver the posibillity to style the placeholder more sophisticated
var placeholder = this.sortableTable.el.find('.ui-sortable-placeholder');
if(!placeholder.height() <= 0) {
placeholder.css('height', this.sortableTable.el.find('.ui-sortable-helper').height());
}
placeholder.html('<div class="outer" style="height:100%;"><div class="inner" style="height:100%;"></div></div>');
},
bindTo: {},
_create: function() {
this.originalTable = {
el: this.element,
selectedHandle: $(),
sortOrder: {},
startIndex: 0,
endIndex: 0
};
// bind draggable to 'th' by default
this.bindTo = this.originalTable.el.find('th');
// filter only the cols that are accepted
if (this.options.dragaccept) {
this.bindTo = this.bindTo.filter(this.options.dragaccept);
}
// bind draggable to handle if exists
if (this.bindTo.find(this.options.dragHandle).length > 0) {
this.bindTo = this.bindTo.find(this.options.dragHandle);
}
// restore state if necessary
if (this.options.restoreState !== null) {
$.isFunction(this.options.restoreState) ? this.options.restoreState(this.originalTable) : this._restoreState(this.options.restoreState);
}
var _this = this;
this.bindTo.mousedown(function(evt) {
// listen only to left mouse click
if(evt.which!==1) return;
if (_this.options.beforeStart(_this.originalTable) === false) {
return;
}
clearTimeout(this.downTimer);
this.downTimer = setTimeout(function() {
_this.originalTable.selectedHandle = $(this);
_this.originalTable.selectedHandle.addClass('dragtable-handle-selected');
_this._generateSortable(evt);
}, _this.options.clickDelay);
}).mouseup(function(evt) {
clearTimeout(this.downTimer);
});
},
redraw: function(){
this.destroy();
this._create();
},
destroy: function() {
this.bindTo.unbind('mousedown');
$.Widget.prototype.destroy.apply(this, arguments); // default destroy
// now do other stuff particular to this widget
}
});
/** closure-scoped "private" functions **/
var body_onselectstart_save = $(document.body).attr('onselectstart'),
body_unselectable_save = $(document.body).attr('unselectable');
// css properties to disable user-select on the body tag by appending a <style> tag to the <head>
// remove any current document selections
function disableTextSelection() {
// jQuery doesn't support the element.text attribute in MSIE 8
// http://stackoverflow.com/questions/2692770/style-style-textcss-appendtohead-does-not-work-in-ie
var $style = $('<style id="__dragtable_disable_text_selection__" type="text/css">body { -ms-user-select:none;-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;user-select:none; }</style>');
$(document.head).append($style);
$(document.body).attr('onselectstart', 'return false;').attr('unselectable', 'on');
if (window.getSelection) {
window.getSelection().removeAllRanges();
} else {
document.selection.empty(); // MSIE http://msdn.microsoft.com/en-us/library/ms535869%28v=VS.85%29.aspx
}
}
// remove the <style> tag, and restore the original <body> onselectstart attribute
function restoreTextSelection() {
$('#__dragtable_disable_text_selection__').remove();
if (body_onselectstart_save) {
$(document.body).attr('onselectstart', body_onselectstart_save);
} else {
$(document.body).removeAttr('onselectstart');
}
if (body_unselectable_save) {
$(document.body).attr('unselectable', body_unselectable_save);
} else {
$(document.body).removeAttr('unselectable');
}
}
function swapNodes(a, b) {
var aparent = a.parentNode;
var asibling = a.nextSibling === b ? a : a.nextSibling;
b.parentNode.insertBefore(a, b);
aparent.insertBefore(b, asibling);
}
})(jQuery);

View file

@ -454,12 +454,18 @@ $(document).ready(function () {
$('#assigned_location').hide();
$('.notification-callout').fadeOut();
$('[name="assigned_location"]').val('').trigger('change.select2');
$('[name="assigned_user"]').val('').trigger('change.select2');
} else if (assignto_type == 'location') {
$('#current_assets_box').fadeOut();
$('#assigned_asset').hide();
$('#assigned_user').hide();
$('#assigned_location').show();
$('.notification-callout').fadeOut();
$('[name="assigned_asset"]').val('').trigger('change.select2');
$('[name="assigned_user"]').val('').trigger('change.select2');
} else {
$('#assigned_asset').hide();
@ -470,6 +476,8 @@ $(document).ready(function () {
}
$('.notification-callout').fadeIn();
$('[name="assigned_asset"]').val('').trigger('change.select2');
$('[name="assigned_location"]').val('').trigger('change.select2');
}
});
});

View file

@ -40,11 +40,17 @@ $(function () {
select = link.data("select");
refreshSelector = link.data("refresh");
$('#createModal').load(link.attr('href'),function () {
// this sets the focus to be the name field
$('#modal-name').focus();
//do we need to re-select2 this, after load? Probably.
$('#createModal').find('select.select2').select2();
// Initialize the ajaxy select2 with images.
// This is a copy/paste of the code from snipeit.js, would be great to only have this in one place.
$('.js-data-ajax').each( function (i,item) {
var link = $(item);
var endpoint = link.data("endpoint");

View file

@ -2,6 +2,7 @@
return [
'does_not_exist' => 'Maatskappy bestaan nie.',
'deleted' => 'Deleted company',
'assoc_users' => 'Hierdie maatskappy is tans geassosieer met ten minste een model en kan nie verwyder word nie. Dateer asseblief jou modelle op om nie meer hierdie maatskappy te verwys nie en probeer weer.',
'create' => [
'error' => 'Maatskappy is nie geskep nie, probeer asseblief weer.',

View file

@ -10,6 +10,9 @@ return [
'bulk_update' => 'Grootskaalse opdateringsbates',
'bulk_update_help' => 'Met hierdie vorm kan u verskeie bates gelyktydig bywerk. Vul slegs die velde in wat u moet verander. Enige velde wat leeg is, bly onveranderd.',
'bulk_update_warn' => 'You are about to edit the properties of a single asset.|You are about to edit the properties of :asset_count assets.',
'bulk_update_with_custom_field' => 'Note the assets are :asset_model_count different types of models.',
'bulk_update_model_prefix' => 'On Models',
'bulk_update_custom_field_unique' => 'This is a unique field and can not be bulk edited.',
'checkedout_to' => 'Gekontroleer na',
'checkout_date' => 'Checkout Datum',
'checkin_date' => 'Incheckdatum',

View file

@ -50,6 +50,7 @@ return [
'success' => 'Jou lêer is ingevoer',
'file_delete_success' => 'Jou lêer is suksesvol verwyder',
'file_delete_error' => 'Die lêer kon nie uitgevee word nie',
'file_missing' => 'The file selected is missing',
'header_row_has_malformed_characters' => 'One or more attributes in the header row contain malformed UTF-8 characters',
'content_row_has_malformed_characters' => 'One or more attributes in the first row of content contain malformed UTF-8 characters',
],

View file

@ -14,6 +14,7 @@ return [
'dl_csv' => 'Laai CSV af',
'eol' => 'EOL',
'id' => 'ID',
'last_checkin_date' => 'Last Checkin Date',
'location' => 'plek',
'purchase_cost' => 'koste',
'purchase_date' => 'gekoop',

View file

@ -0,0 +1,11 @@
<?php
return [
'invalid_return_count' => 'Invalid count returned from :name. Expected :expected, got :actual.',
'invalid_return_type' => 'Invalid type returned from :name. Expected :expected, got :actual.',
'invalid_return_value' => 'Invalid value returned from :name. Expected :expected, got :actual.',
'does_not_exist' => 'Label does not exist',
];

View file

@ -0,0 +1,13 @@
<?php
return [
'labels_per_page' => 'Labels',
'support_fields' => 'Fields',
'support_asset_tag' => 'Tag',
'support_1d_barcode' => '1D',
'support_2d_barcode' => '2D',
'support_logo' => 'Logo',
'support_title' => 'Title',
];

View file

@ -26,6 +26,7 @@ return array(
'modal' => 'This will action checkin one seat. | This action will checkin all :checkedout_seats_count seats for this license.',
'enabled_tooltip' => 'Checkin ALL seats for this license from both users and assets',
'disabled_tooltip' => 'This is disabled because there are no seats currently checked out',
'disabled_tooltip_reassignable' => 'This is disabled because the License is not reassignable',
'success' => 'License successfully checked in! | All licenses were successfully checked in!',
'log_msg' => 'Checked in via bulk license checkout in license GUI',
],

View file

@ -2,7 +2,7 @@
return array(
'support_url_help' => 'Use <code>{LOCALE}</code> and <code>{SERIAL}</code> in your URL as variables to have those values auto-populate when viewing assets.',
'support_url_help' => 'Variables <code>{LOCALE}</code>, <code>{SERIAL}</code>, <code>{MODEL_NUMBER}</code>, and <code>{MODEL_NAME}</code> may be used in your URL to have those values auto-populate when viewing assets - for example https://support.apple.com/{LOCALE}/{SERIAL}.',
'does_not_exist' => 'Vervaardiger bestaan nie.',
'assoc_users' => 'Hierdie vervaardiger word tans geassosieer met ten minste een model en kan nie verwyder word nie. Dateer asseblief jou modelle op om nie meer hierdie vervaardiger te gebruik nie en probeer weer.',

View file

@ -2,6 +2,7 @@
return array(
'deleted' => 'Deleted asset model',
'does_not_exist' => 'Model bestaan nie.',
'no_association' => 'WARNING! The asset model for this item is invalid or missing!',
'no_association_fix' => 'This will break things in weird and horrible ways. Edit this asset now to assign it a model.',

View file

@ -330,9 +330,36 @@ return [
'setup_migration_create_user' => 'Next: Create User',
'ldap_settings_link' => 'LDAP Settings Page',
'slack_test' => 'Test <i class="fab fa-slack"></i> Integration',
'label2_enable' => 'New Label Engine',
'label2_enable_help' => 'Switch to the new label engine. <b>Note: You will need to save this setting before setting others.</b>',
'label2_template' => 'Template',
'label2_template_help' => 'Select which template to use for label generation',
'label2_title' => 'Title',
'label2_title_help' => 'The title to show on labels that support it',
'label2_title_help_phold' => 'The placeholder <code>{COMPANY}</code> will be replaced with the asset&apos;s company name',
'label2_asset_logo' => 'Use Asset Logo',
'label2_asset_logo_help' => 'Use the logo of the asset&apos;s assigned company, rather than the value at <code>:setting_name</code>',
'label2_1d_type' => '1D Barcode Type',
'label2_1d_type_help' => 'Format for 1D barcodes',
'label2_2d_type' => '2D Barcode Type',
'label2_2d_type_help' => 'Format for 2D barcodes',
'label2_2d_target' => '2D Barcode Target',
'label2_2d_target_help' => 'The URL the 2D barcode points to when scanned',
'label2_fields' => 'Field Definitions',
'label2_fields_help' => 'Fields can be added, removed, and reordered in the left column. For each field, multiple options for Label and DataSource can be added, removed, and reordered in the right column.',
'help_asterisk_bold' => 'Text entered as <code>**text**</code> will be displayed as bold',
'help_blank_to_use' => 'Leave blank to use the value from <code>:setting_name</code>',
'help_default_will_use' => '<code>:default</code> will use the value from <code>:setting_name</code>. <br>Note that the value of the barcodes must comply with the respective barcode spec in order to be successfully generated. Please see <a href="https://snipe-it.readme.io/docs/barcodes">the documentation <i class="fa fa-external-link"></i></a> for more details. ',
'default' => 'Default',
'none' => 'None',
'google_callback_help' => 'This should be entered as the callback URL in your Google OAuth app settings in your organization&apos;s <strong><a href="https://console.cloud.google.com/" target="_blank">Google developer console <i class="fa fa-external-link" aria-hidden="true"></i></a></strong>.',
'google_login' => 'Google Workspace Login Settings',
'enable_google_login' => 'Enable users to login with Google Workspace',
'enable_google_login_help' => 'Users will not be automatically provisioned. They must have an existing account here AND in Google Workspace, and their username here must match their Google Workspace email address. ',
'mail_reply_to' => 'Mail Reply-To Address',
'mail_from' => 'Mail From Address',
'database_driver' => 'Database Driver',
'bs_table_storage' => 'Table Storage',
'timezone' => 'Timezone',
];

View file

@ -35,10 +35,12 @@ return [
],
'webhook' => [
'sending' => 'Sending :app test message...',
'success' => 'Your :webhook_name Integration works!',
'success_pt1' => 'Success! Check the ',
'success_pt2' => ' channel for your test message, and be sure to click SAVE below to store your settings.',
'500' => '500 Server Error.',
'error' => 'Something went wrong. :app responded with: :error_message',
'error_redirect' => 'ERROR: 301/302 :endpoint returns a redirect. For security reasons, we dont follow redirects. Please use the actual endpoint.',
'error_misc' => 'Something went wrong. :( ',
]
];

View file

@ -2,6 +2,7 @@
return array(
'deleted' => 'Deleted supplier',
'does_not_exist' => 'Verskaffer bestaan nie.',

View file

@ -29,6 +29,7 @@ return array(
'show_deleted' => 'Wys verwyderde gebruikers',
'title' => 'Titel',
'to_restore_them' => 'om hulle te herstel.',
'total_assets_cost' => "Total Assets Cost",
'updateuser' => 'Update gebruiker',
'username' => 'Gebruikersnaam',
'user_deleted_text' => 'Hierdie gebruiker is gemerk as verwyder.',

View file

@ -120,6 +120,9 @@ return [
'firstname_lastname_underscore_format' => 'First Name Last Name (jane_smith@example.com)',
'lastnamefirstinitial_format' => 'Last Name First Initial (smithj@example.com)',
'firstintial_dot_lastname_format' => 'First Initial Last Name (j.smith@example.com)',
'firstname_lastname_display' => 'First Name Last Name (Jane Smith)',
'lastname_firstname_display' => 'Last Name First Name (Smith Jane)',
'name_display_format' => 'Name Display Format',
'first' => 'eerste',
'firstnamelastname' => 'First Name Last Name (janesmith@example.com)',
'lastname_firstinitial' => 'Last Name First Initial (smith_j@example.com)',
@ -265,6 +268,8 @@ return [
'supplier' => 'verskaffer',
'suppliers' => 'Verskaffers',
'sure_to_delete' => 'Is jy seker jy wil verwyder',
'sure_to_delete_var' => 'Are you sure you wish to delete :item?',
'delete_what' => 'Delete :item',
'submit' => 'Indien',
'target' => 'teiken',
'time_and_date_display' => 'Tyd en datum vertoon',
@ -283,6 +288,7 @@ return [
'user' => 'gebruiker',
'accepted' => 'aanvaarde',
'declined' => 'afgeneem',
'unassigned' => 'Unassigned',
'unaccepted_asset_report' => 'Onaanvaarde Bates',
'users' => 'gebruikers',
'viewall' => 'View All',
@ -364,9 +370,10 @@ return [
'licenses_count' => 'Licenses Count',
'notification_error' => 'Error:',
'notification_error_hint' => 'Please check the form below for errors',
'notification_success' => 'Success:',
'notification_warning' => 'Warning:',
'notification_info' => 'Info:',
'notification_bulk_error_hint' => 'The following fields had validation errors and were not edited:',
'notification_success' => 'Success',
'notification_warning' => 'Warning',
'notification_info' => 'Info',
'asset_information' => 'Asset Information',
'model_name' => 'Model Name',
'asset_name' => 'Asset Name',
@ -452,6 +459,8 @@ return [
'serial_number' => 'Serial Number',
'item_notes' => ':item Notes',
'item_name_var' => ':item Name',
'error_user_company' => 'Checkout target company and asset company do not match',
'error_user_company_accept_view' => 'An Asset assigned to you belongs to a different company so you can\'t accept nor deny it, please check with your manager',
'importer' => [
'checked_out_to_fullname' => 'Checked Out to: Full Name',
'checked_out_to_first_name' => 'Checked Out to: First Name',
@ -474,5 +483,10 @@ return [
'address2' => 'Address Line 2',
'import_note' => 'Imported using csv importer',
],
'percent_complete' => '% complete',
'uploading' => 'Uploading... ',
'upload_error' => 'Error uploading file. Please check that there are no empty trailing rows.',
'copy_to_clipboard' => 'Copy to Clipboard',
'copied' => 'Copied!',
];

View file

@ -30,6 +30,7 @@ return [
'ga-IE'=> 'Irish',
'it'=> 'Italian',
'ja'=> 'Japanese',
'km' => 'Khmer',
'ko'=> 'Korean',
'lv'=>'Latvian',
'lt'=> 'Lithuanian',

View file

@ -2,6 +2,7 @@
return [
'does_not_exist' => 'Company does not exist.',
'deleted' => 'Deleted company',
'assoc_users' => 'This company is currently associated with at least one model and cannot be deleted. Please update your models to no longer reference this company and try again. ',
'create' => [
'error' => 'Company was not created, please try again.',

View file

@ -10,6 +10,9 @@ return [
'bulk_update' => 'Bulk Update Assets',
'bulk_update_help' => 'This form allows you to update multiple assets at once. Only fill in the fields you need to change. Any fields left blank will remain unchanged. ',
'bulk_update_warn' => 'You are about to edit the properties of a single asset.|You are about to edit the properties of :asset_count assets.',
'bulk_update_with_custom_field' => 'Note the assets are :asset_model_count different types of models.',
'bulk_update_model_prefix' => 'On Models',
'bulk_update_custom_field_unique' => 'This is a unique field and can not be bulk edited.',
'checkedout_to' => 'Checked Out To',
'checkout_date' => 'Checkout Date',
'checkin_date' => 'Checkin Date',

View file

@ -51,6 +51,7 @@ return [
'success' => 'Your file has been imported',
'file_delete_success' => 'Your file has been been successfully deleted',
'file_delete_error' => 'The file was unable to be deleted',
'file_missing' => 'The file selected is missing',
'header_row_has_malformed_characters' => 'One or more attributes in the header row contain malformed UTF-8 characters',
'content_row_has_malformed_characters' => 'One or more attributes in the first row of content contain malformed UTF-8 characters',
],

View file

@ -14,6 +14,7 @@ return [
'dl_csv' => 'Download CSV',
'eol' => 'EOL',
'id' => 'ID',
'last_checkin_date' => 'Last Checkin Date',
'location' => 'Location',
'purchase_cost' => 'Cost',
'purchase_date' => 'Purchased',

View file

@ -0,0 +1,11 @@
<?php
return [
'invalid_return_count' => 'Invalid count returned from :name. Expected :expected, got :actual.',
'invalid_return_type' => 'Invalid type returned from :name. Expected :expected, got :actual.',
'invalid_return_value' => 'Invalid value returned from :name. Expected :expected, got :actual.',
'does_not_exist' => 'Label does not exist',
];

View file

@ -0,0 +1,13 @@
<?php
return [
'labels_per_page' => 'Labels',
'support_fields' => 'Fields',
'support_asset_tag' => 'Tag',
'support_1d_barcode' => '1D',
'support_2d_barcode' => '2D',
'support_logo' => 'Logo',
'support_title' => 'Title',
];

View file

@ -26,6 +26,7 @@ return array(
'modal' => 'This will action checkin one seat. | This action will checkin all :checkedout_seats_count seats for this license.',
'enabled_tooltip' => 'Checkin ALL seats for this license from both users and assets',
'disabled_tooltip' => 'This is disabled because there are no seats currently checked out',
'disabled_tooltip_reassignable' => 'This is disabled because the License is not reassignable',
'success' => 'License successfully checked in! | All licenses were successfully checked in!',
'log_msg' => 'Checked in via bulk license checkout in license GUI',
],

View file

@ -2,7 +2,7 @@
return array(
'support_url_help' => 'Use <code>{LOCALE}</code> and <code>{SERIAL}</code> in your URL as variables to have those values auto-populate when viewing assets.',
'support_url_help' => 'Variables <code>{LOCALE}</code>, <code>{SERIAL}</code>, <code>{MODEL_NUMBER}</code>, and <code>{MODEL_NAME}</code> may be used in your URL to have those values auto-populate when viewing assets - for example https://support.apple.com/{LOCALE}/{SERIAL}.',
'does_not_exist' => 'Manufacturer does not exist.',
'assoc_users' => 'This manufacturer is currently associated with at least one model and cannot be deleted. Please update your models to no longer reference this manufacturer and try again. ',

View file

@ -2,6 +2,7 @@
return array(
'deleted' => 'Deleted asset model',
'does_not_exist' => 'Model does not exist.',
'no_association' => 'WARNING! The asset model for this item is invalid or missing!',
'no_association_fix' => 'This will break things in weird and horrible ways. Edit this asset now to assign it a model.',

View file

@ -330,9 +330,36 @@ return [
'setup_migration_create_user' => 'Next: Create User',
'ldap_settings_link' => 'LDAP Settings Page',
'slack_test' => 'Test <i class="fab fa-slack"></i> Integration',
'label2_enable' => 'New Label Engine',
'label2_enable_help' => 'Switch to the new label engine. <b>Note: You will need to save this setting before setting others.</b>',
'label2_template' => 'Template',
'label2_template_help' => 'Select which template to use for label generation',
'label2_title' => 'Title',
'label2_title_help' => 'The title to show on labels that support it',
'label2_title_help_phold' => 'The placeholder <code>{COMPANY}</code> will be replaced with the asset&apos;s company name',
'label2_asset_logo' => 'Use Asset Logo',
'label2_asset_logo_help' => 'Use the logo of the asset&apos;s assigned company, rather than the value at <code>:setting_name</code>',
'label2_1d_type' => '1D Barcode Type',
'label2_1d_type_help' => 'Format for 1D barcodes',
'label2_2d_type' => '2D Barcode Type',
'label2_2d_type_help' => 'Format for 2D barcodes',
'label2_2d_target' => '2D Barcode Target',
'label2_2d_target_help' => 'The URL the 2D barcode points to when scanned',
'label2_fields' => 'Field Definitions',
'label2_fields_help' => 'Fields can be added, removed, and reordered in the left column. For each field, multiple options for Label and DataSource can be added, removed, and reordered in the right column.',
'help_asterisk_bold' => 'Text entered as <code>**text**</code> will be displayed as bold',
'help_blank_to_use' => 'Leave blank to use the value from <code>:setting_name</code>',
'help_default_will_use' => '<code>:default</code> will use the value from <code>:setting_name</code>. <br>Note that the value of the barcodes must comply with the respective barcode spec in order to be successfully generated. Please see <a href="https://snipe-it.readme.io/docs/barcodes">the documentation <i class="fa fa-external-link"></i></a> for more details. ',
'default' => 'Default',
'none' => 'None',
'google_callback_help' => 'This should be entered as the callback URL in your Google OAuth app settings in your organization&apos;s <strong><a href="https://console.cloud.google.com/" target="_blank">Google developer console <i class="fa fa-external-link" aria-hidden="true"></i></a></strong>.',
'google_login' => 'Google Workspace Login Settings',
'enable_google_login' => 'Enable users to login with Google Workspace',
'enable_google_login_help' => 'Users will not be automatically provisioned. They must have an existing account here AND in Google Workspace, and their username here must match their Google Workspace email address. ',
'mail_reply_to' => 'Mail Reply-To Address',
'mail_from' => 'Mail From Address',
'database_driver' => 'Database Driver',
'bs_table_storage' => 'Table Storage',
'timezone' => 'Timezone',
];

View file

@ -35,10 +35,12 @@ return [
],
'webhook' => [
'sending' => 'Sending :app test message...',
'success' => 'Your :webhook_name Integration works!',
'success_pt1' => 'Success! Check the ',
'success_pt2' => ' channel for your test message, and be sure to click SAVE below to store your settings.',
'500' => '500 Server Error.',
'error' => 'Something went wrong. :app responded with: :error_message',
'error_redirect' => 'ERROR: 301/302 :endpoint returns a redirect. For security reasons, we dont follow redirects. Please use the actual endpoint.',
'error_misc' => 'Something went wrong. :( ',
]
];

View file

@ -2,6 +2,7 @@
return array(
'deleted' => 'Deleted supplier',
'does_not_exist' => 'Supplier does not exist.',

View file

@ -29,6 +29,7 @@ return array(
'show_deleted' => 'Show Deleted Users',
'title' => 'Title',
'to_restore_them' => 'to restore them.',
'total_assets_cost' => "Total Assets Cost",
'updateuser' => 'Update User',
'username' => 'Username',
'user_deleted_text' => 'This user has been marked as deleted.',

View file

@ -120,6 +120,9 @@ return [
'firstname_lastname_underscore_format' => 'First Name Last Name (jane_smith@example.com)',
'lastnamefirstinitial_format' => 'Last Name First Initial (smithj@example.com)',
'firstintial_dot_lastname_format' => 'First Initial Last Name (j.smith@example.com)',
'firstname_lastname_display' => 'First Name Last Name (Jane Smith)',
'lastname_firstname_display' => 'Last Name First Name (Smith Jane)',
'name_display_format' => 'Name Display Format',
'first' => 'First',
'firstnamelastname' => 'First Name Last Name (janesmith@example.com)',
'lastname_firstinitial' => 'Last Name First Initial (smith_j@example.com)',
@ -265,6 +268,8 @@ return [
'supplier' => 'Supplier',
'suppliers' => 'Suppliers',
'sure_to_delete' => 'Are you sure you wish to delete',
'sure_to_delete_var' => 'Are you sure you wish to delete :item?',
'delete_what' => 'Delete :item',
'submit' => 'Submit',
'target' => 'Target',
'time_and_date_display' => 'Time and Date Display',
@ -283,6 +288,7 @@ return [
'user' => 'User',
'accepted' => 'accepted',
'declined' => 'declined',
'unassigned' => 'Unassigned',
'unaccepted_asset_report' => 'Unaccepted Assets',
'users' => 'Users',
'viewall' => 'View All',
@ -364,9 +370,10 @@ return [
'licenses_count' => 'Licenses Count',
'notification_error' => 'Error:',
'notification_error_hint' => 'Please check the form below for errors',
'notification_success' => 'Success:',
'notification_warning' => 'Warning:',
'notification_info' => 'Info:',
'notification_bulk_error_hint' => 'The following fields had validation errors and were not edited:',
'notification_success' => 'Success',
'notification_warning' => 'Warning',
'notification_info' => 'Info',
'asset_information' => 'Asset Information',
'model_name' => 'Model Name',
'asset_name' => 'Asset Name',
@ -452,6 +459,8 @@ return [
'serial_number' => 'Serial Number',
'item_notes' => ':item Notes',
'item_name_var' => ':item Name',
'error_user_company' => 'Checkout target company and asset company do not match',
'error_user_company_accept_view' => 'An Asset assigned to you belongs to a different company so you can\'t accept nor deny it, please check with your manager',
'importer' => [
'checked_out_to_fullname' => 'Checked Out to: Full Name',
'checked_out_to_first_name' => 'Checked Out to: First Name',
@ -474,5 +483,10 @@ return [
'address2' => 'Address Line 2',
'import_note' => 'Imported using csv importer',
],
'percent_complete' => '% complete',
'uploading' => 'Uploading... ',
'upload_error' => 'Error uploading file. Please check that there are no empty trailing rows.',
'copy_to_clipboard' => 'Copy to Clipboard',
'copied' => 'Copied!',
];

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