Merge branch 'master' into integrations/2020-08-31-v5-rc

# Conflicts:
#	.all-contributorsrc
#	.nvmrc
#	README.md
#	app/Console/Commands/LdapSync.php
#	app/Http/Controllers/Api/ConsumablesController.php
#	app/Http/Controllers/Api/ImportController.php
#	app/Http/Controllers/Assets/AssetsController.php
#	app/Http/Controllers/Auth/LoginController.php
#	app/Http/Controllers/CustomFieldsetsController.php
#	app/Http/Controllers/LicensesController.php
#	app/Http/Controllers/UsersController.php
#	app/Importer/import_mappings.md
#	app/Models/Ldap.php
#	app/Models/Loggable.php
#	composer.json
#	composer.lock
#	config/version.php
#	public/css/build/all.css
#	public/css/dist/all.css
#	public/css/skins/skin-contrast.css
#	public/css/skins/skin-contrast.css.map
#	public/js/build/all.js
#	public/js/build/vue.js
#	public/js/build/vue.js.map
#	public/js/dist/all.js
#	public/mix-manifest.json
#	resources/assets/js/components/importer/importer-file.vue
#	resources/assets/less/overrides.less
#	resources/macros/macros.php
#	resources/views/custom_fields/fieldsets/view.blade.php
#	resources/views/hardware/edit.blade.php
#	resources/views/hardware/labels.blade.php
#	resources/views/hardware/view.blade.php
#	resources/views/layouts/default.blade.php
#	resources/views/modals/model.blade.php
#	resources/views/modals/user.blade.php
#	resources/views/users/index.blade.php
#	routes/api.php
#	routes/web/fields.php
#	tests/unit/UserTest.php
This commit is contained in:
snipe 2020-08-31 12:14:44 -07:00
commit e321aeabae
No known key found for this signature in database
GPG key ID: 10BFFDA3ED34B5AC
51 changed files with 1106 additions and 551 deletions

View file

@ -77,6 +77,7 @@ ALLOW_IFRAMING=false
REFERRER_POLICY=same-origin
ENABLE_CSP=false
CORS_ALLOWED_ORIGINS=null
ENABLE_HSTS=false
# --------------------------------------------
# OPTIONAL: CACHE SETTINGS

40
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,40 @@
# Description
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context, providing screenshots where practical. List any dependencies that are required for this change.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
# How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
- [ ] Test A
- [ ] Test B
**Test Configuration**:
* PHP version:
* MySQL version
* Webserver version
* OS version
# Checklist:
- [ ] I have read the Contributing documentation available here: https://snipe-it.readme.io/docs/contributing-overview
- [ ] I have formatted this PR according to the project guidelines: https://snipe-it.readme.io/docs/contributing-overview#pull-request-guidelines
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes

View file

@ -1,4 +1,5 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=master)](https://travis-ci.org/snipe/snipe-it) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![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-189-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
[![All Contributors](https://img.shields.io/badge/all_contributors-212-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
@ -114,6 +115,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [<img src="https://avatars0.githubusercontent.com/u/17459600?v=4" width="110px;"/><br /><sub>Marián Skrip</sub>](https://github.com/mskrip)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/47435081?v=4" width="110px;"/><br /><sub>Godfrey Martinez</sub>](https://github.com/Godmartinz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") | [<img src="https://avatars1.githubusercontent.com/u/2075128?v=4" width="110px;"/><br /><sub>bigtreeEdo</sub>](https://github.com/bigtreeEdo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [<img src="https://avatars0.githubusercontent.com/u/5000430?v=4" width="110px;"/><br /><sub>Colin McNeil</sub>](https://colinmcneil.me/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [<img src="https://avatars0.githubusercontent.com/u/421625?v=4" width="110px;"/><br /><sub>JoKneeMo</sub>](https://github.com/JoKneeMo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [<img src="https://avatars0.githubusercontent.com/u/54849013?v=4" width="110px;"/><br /><sub>Joshi</sub>](http://www.redbridge.se)<br />[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [<img src="https://avatars2.githubusercontent.com/u/15731458?v=4" width="110px;"/><br /><sub>Anthony Burns</sub>](https://github.com/anthonypburns)<br />[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [<img src="https://avatars2.githubusercontent.com/u/1972329?v=4" width="110px;"/><br /><sub>Alexander Chibrikin</sub>](http://phpprofi.ru/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/4930051?v=4" width="110px;"/><br /><sub>Wes Hulette</sub>](http://macfoo.wordpress.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [<img src="https://avatars0.githubusercontent.com/u/8134591?v=4" width="110px;"/><br /><sub>patrict</sub>](https://github.com/patrict)<br />[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/2611616?v=4" width="110px;"/><br /><sub>Dmitriy Minaev</sub>](https://github.com/VELIKII-DIVAN)<br />[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [<img src="https://avatars0.githubusercontent.com/u/5132245?v=4" width="110px;"/><br /><sub>liquidhorse</sub>](https://github.com/liquidhorse)<br />[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [<img src="https://avatars1.githubusercontent.com/u/183678?v=4" width="110px;"/><br /><sub>Jordi Boggiano</sub>](https://seld.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [<img src="https://avatars0.githubusercontent.com/u/653557?v=4" width="110px;"/><br /><sub>Ivan Nieto</sub>](https://github.com/inietov)<br />[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [<img src="https://avatars2.githubusercontent.com/u/6764151?v=4" width="110px;"/><br /><sub>Ben RUBSON</sub>](https://github.com/benrubson)<br />[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [<img src="https://avatars2.githubusercontent.com/u/8554558?v=4" width="110px;"/><br /><sub>NMathar</sub>](https://github.com/NMathar)<br />[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [<img src="https://avatars1.githubusercontent.com/u/139566?v=4" width="110px;"/><br /><sub>Steffen</sub>](https://github.com/smb)<br />[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") |
| [<img src="https://avatars0.githubusercontent.com/u/6609453?v=4" width="110px;"/><br /><sub>Sxderp</sub>](https://github.com/Sxderp)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [<img src="https://avatars1.githubusercontent.com/u/4807843?v=4" width="110px;"/><br /><sub>fanta8897</sub>](https://github.com/fanta8897)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [<img src="https://avatars2.githubusercontent.com/u/2576509?v=4" width="110px;"/><br /><sub>Andrey Bolonin</sub>](https://andreybolonin.com/phpconsulting/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [<img src="https://avatars3.githubusercontent.com/u/2173307?v=4" width="110px;"/><br /><sub>shinayoshi</sub>](http://www.shinayoshi.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [<img src="https://avatars3.githubusercontent.com/u/2130159?v=4" width="110px;"/><br /><sub>Hubert</sub>](https://github.com/reuser)<br />[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [<img src="https://avatars0.githubusercontent.com/u/6865789?v=4" width="110px;"/><br /><sub>KeenRivals</sub>](https://brashear.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [<img src="https://avatars3.githubusercontent.com/u/2902513?v=4" width="110px;"/><br /><sub>omyno</sub>](https://github.com/omyno)<br />[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") |

View file

@ -0,0 +1,95 @@
<?php
namespace App\Console\Commands;
use App\Models\LicenseSeat;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\License;
use Illuminate\Database\Eloquent\Model;
class CheckinLicensesFromAllUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:checkin-from-all {--license_id=} {--notify}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Checks in licenses from all users';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$license_id = $this->option('license_id');
$notify = $this->option('notify');
if (!$license_id) {
$this->error('ERROR: License ID is required.');
return false;
}
if (!$license = License::where('id','=',$license_id)->first()) {
$this->error('Invalid license ID');
return false;
}
$this->info('Checking in ALL seats for '.$license->name);
$licenseSeats = LicenseSeat::where('license_id', '=', $license_id)
->whereNotNull('assigned_to')
->with('user')
->get();
$this->info(' There are ' .$licenseSeats->count(). ' seats checked out: ');
if (!$notify) {
$this->info('No mail will be sent.');
}
foreach ($licenseSeats as $seat) {
$this->info($seat->user->username .' has a license seat for '.$license->name);
$seat->assigned_to = null;
if ($seat->save()) {
// Override the email address so we don't notify on checkin
if (!$notify) {
$seat->user->email = null;
}
// Log the checkin
$seat->logCheckin($seat->user, 'Checked in via cli tool');
}
}
}
}

View file

@ -0,0 +1,112 @@
<?php
namespace App\Console\Commands;
use App\Models\LicenseSeat;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\License;
use Illuminate\Database\Eloquent\Model;
class CheckoutLicenseToAllUsers extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:checkout-to-all {--license_id=} {--notify}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$license_id = $this->option('license_id');
$notify = $this->option('notify');
if (!$license_id) {
$this->error('ERROR: License ID is required.');
return false;
}
if (!$license = License::where('id','=',$license_id)->with('assignedusers')->first()) {
$this->error('Invalid license ID');
return false;
}
$users = User::whereNull('deleted_at')->with('licenses')->get();
if ($users->count() > $license->getAvailSeatsCountAttribute()) {
$this->info('You do not have enough free seats to complete this task, so we will check out as many as we can. ');
}
$this->info('Checking out '.$users->count().' of '.$license->getAvailSeatsCountAttribute().' seats for '.$license->name);
if (!$notify) {
$this->info('No mail will be sent.');
}
foreach ($users as $user) {
// Check to make sure this user doesn't already have this license checked out
// to them
if ($user->licenses->where('id', '=', $license_id)->count()) {
$this->info($user->username .' already has this license checked out to them. Skipping... ');
continue;
}
// If the license is valid, check that there is an available seat
if ($license->availCount()->count() < 1) {
$this->error('ERROR: No available seats');
return false;
}
$this->info($license->availCount()->count().' seats left');
// Get the seat ID
$licenseSeat = $license->freeSeat();
// Update the seat with checkout info,
$licenseSeat->assigned_to = $user->id;
if ($licenseSeat->save()) {
// Temporarily null the user's email address so we don't send mail if we're not supposed to
if (!$notify) {
$user->email = null;
}
// Log the checkout
$licenseSeat->logCheckout('Checked out via cli tool', $user);
$this->info('License '.$license_id.' seat '.$licenseSeat->id.' checked out to '.$user->username);
}
}
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use Carbon\Carbon;
class MergeUsersByUsername extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:merge-users';
/**
* The console command description.
*
* @var string
*/
protected $description = 'This command allows you to merge the history of users. It looks for users without an email address as their username and merges them into the version that does have an email username.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
// Get the list of users who have an email address as their username
$users = User::where('username', 'LIKE', '%@%')->whereNull('deleted_at')->get();
foreach ($users as $user) {
$parts = explode("@", $user->username);
$bad_users = User::where('username', '=', $parts[0])->whereNull('deleted_at')->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations')->get();
foreach ($bad_users as $bad_user) {
$this->info($bad_user->username.' ('.$bad_user->id.') will be merged into '.$user->username.' ('.$user->id.') ');
// Walk the list of assets
foreach ($bad_user->assets as $asset) {
$this->info( 'Updating asset '.$asset->asset_tag.' '.$asset->id.' to user '.$user->id);
$asset->assigned_to = $user->id;
$asset->save();
}
// Walk the list of licenses
foreach ($bad_user->licenses as $license) {
$this->info( 'Updating license '.$license->name.' '.$license->id.' to user '.$user->id);
$bad_user->licenses()->updateExistingPivot($license->id, ['assigned_to' => $user->id]);
}
// Walk the list of consumables
foreach ($bad_user->consumables as $consumable) {
$this->info( 'Updating consumable '.$consumable->id.' to user '.$user->id);
$bad_user->consumables()->updateExistingPivot($consumable->id, ['assigned_to' => $user->id]);
}
// Walk the list of accessories
foreach ($bad_user->accessories as $accessory) {
$this->info( 'Updating accessory '.$accessory->id.' to user '.$user->id);
$bad_user->accessories()->updateExistingPivot($accessory->id, ['assigned_to' => $user->id]);
}
// Walk the list of logs
foreach ($bad_user->userlog as $log) {
$this->info( 'Updating action log record '.$log->id.' to user '.$user->id);
$log->target_id = $user->id;
$log->save();
}
// Update any manager IDs
$this->info( 'Updating managed user records to user '.$user->id);
User::where('manager_id', '=', $bad_user->id)->update(['manager_id' => $user->id]);
// Update location manager IDs
foreach ($bad_user->managedLocations as $managedLocation) {
$this->info( 'Updating managed location record '.$managedLocation->name.' to manager '.$user->id);
$managedLocation->manager_id = $user->id;
$managedLocation->save();
}
// Mark the user as deleted
$this->info( 'Marking the user as deleted');
$bad_user->deleted_at = Carbon::now()->timestamp;
$bad_user->save();
}
}
}
}

View file

@ -457,6 +457,7 @@ class AssetsController extends Controller
$asset->supplier_id = $request->get('supplier_id', 0);
$asset->requestable = $request->get('requestable', 0);
$asset->rtd_location_id = $request->get('rtd_location_id', null);
$asset->location_id = $request->get('rtd_location_id', null);
if ($request->has('image_source') && $request->input('image_source') != "") {
$saved_image_path = Helper::processUploadedImage(

View file

@ -8,7 +8,9 @@ use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company;
use App\Models\Consumable;
use Illuminate\Http\Request;
use App\Models\User;
use App\Http\Transformers\ConsumablesTransformer;
use App\Helpers\Helper;
class ConsumablesController extends Controller
{
@ -158,7 +160,7 @@ class ConsumablesController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.delete.success')));
}
/**
/**
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
@ -198,6 +200,57 @@ class ConsumablesController extends Controller
return $data;
}
/**
* Checkout a consumable
*
* @author [A. Gutierrez] [<andres@baller.tv>]
* @param int $id
* @since [v4.9.5]
* @return JsonResponse
*/
public function checkout(Request $request, $id)
{
// Check if the consumable exists
if (is_null($consumable = Consumable::find($id))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.does_not_exist')));
}
$this->authorize('checkout', $consumable);
if ($consumable->qty > 0) {
// Check if the user exists
$assigned_to = $request->input('assigned_to');
if (is_null($user = User::find($assigned_to))) {
// Return error message
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
}
// Update the consumable data
$consumable->assigned_to = e($assigned_to);
$consumable->users()->attach($consumable->id, [
'consumable_id' => $consumable->id,
'user_id' => $user->id,
'assigned_to' => $assigned_to
]);
// Log checkout event
$logaction = $consumable->logCheckout(e($request->input('note')), $user);
$data['log_id'] = $logaction->id;
$data['eula'] = $consumable->getEula();
$data['first_name'] = $user->first_name;
$data['item_name'] = $consumable->name;
$data['checkout_date'] = $logaction->created_at;
$data['note'] = $logaction->note;
$data['require_acceptance'] = $consumable->requireAcceptance();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No consumables remaining'));
}
/**
* Gets a paginated collection for the select2 menus
*

View file

@ -78,6 +78,14 @@ class UsersController extends Controller
$users = $users->where('users.location_id', '=', $request->input('location_id'));
}
if ($request->filled('email')) {
$users = $users->where('users.email', '=', $request->input('email'));
}
if ($request->filled('username')) {
$users = $users->where('users.username', '=', $request->input('username'));
}
if ($request->filled('group_id')) {
$users = $users->ByGroup($request->get('group_id'));
}

View file

@ -385,6 +385,7 @@ class LoginController extends Controller
$request->session()->regenerate(true);
$request->session()->regenerate(true);
Auth::logout();
if (!empty($sloRedirectUrl)) {

View file

@ -17,15 +17,12 @@ class Kernel extends HttpKernel
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\FrameGuard::class,
\App\Http\Middleware\XssProtectHeader::class,
\App\Http\Middleware\ReferrerPolicyHeader::class,
\App\Http\Middleware\ContentSecurityPolicyHeader::class,
\App\Http\Middleware\NosniffGuard::class,
\Fideloper\Proxy\TrustProxies::class,
\App\Http\Middleware\CheckForSetup::class,
\App\Http\Middleware\CheckForDebug::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\SecurityHeaders::class,
];
/**

View file

@ -1,35 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
class ContentSecurityPolicyHeader
{
/**
* Handle the given request and get the response.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, Closure $next)
{
if ((config('app.debug')=='true') || (config('app.enable_csp')!='true')) {
$response = $next($request);
return $response;
}
$policy[] = "default-src 'self'";
$policy[] = "style-src 'self' 'unsafe-inline' oss.maxcdn.com";
$policy[] = "script-src 'self' 'unsafe-inline' 'unsafe-eval' cdnjs.cloudflare.com";
$policy[] = "connect-src 'self'";
$policy[] = "object-src 'none'";
$policy[] = "font-src 'self' data:";
$policy[] = "img-src 'self' data: gravatar.com";
$policy = join(';', $policy);
$response = $next($request);
$response->headers->set('Content-Security-Policy', $policy);
return $response;
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
class FrameGuard
{
/**
* Handle the given request and get the response.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, Closure $next)
{
$response = $next($request);
if (config('app.allow_iframing') == false) {
$response->headers->set('X-Frame-Options', 'SAMEORIGIN', false);
}
return $response;
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
class NosniffGuard
{
/**
* Handle the given request and get the response.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('X-Content-Type-Options', 'nosniff', false);
return $response;
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
class ReferrerPolicyHeader
{
/**
* Handle the given request and get the response.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('Referrer-Policy', config('app.referrer_policy'));
return $response;
}
}

View file

@ -0,0 +1,122 @@
<?php
namespace App\Http\Middleware;
use Closure;
class SecurityHeaders
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
// See https://securityheaders.com/
private $unwantedHeaderList = [
'X-Powered-By',
'Server',
];
public function handle($request, Closure $next)
{
$this->removeUnwantedHeaders($this->unwantedHeaderList);
$response = $next($request);
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
// Ugh. Feature-Policy is dumb and clumsy and mostly irrelevant for Snipe-IT,
// since we don't provide any way to IFRAME anything in in the first place.
// There is currently no easy way to default ALL THE THINGS to 'none', but
// security audits will still ding you if you don't have this header, even
// though we don't allow IFRAMING in the first place.
//
// So for security and compliance sake, here we are. Sigh.
//
// See also:
// - https://developers.google.com/web/updates/2018/06/feature-policy
// - https://scotthelme.co.uk/a-new-security-header-feature-policy/
// - https://github.com/w3c/webappsec-feature-policy/issues/189
$feature_policy[] = "accelerometer 'none'";
$feature_policy[] = "ambient-light-sensor 'none'";
$feature_policy[] = "animations 'none'";
$feature_policy[] = "autoplay 'none'";
$feature_policy[] = "battery 'none'";
$feature_policy[] = "camera 'none'";
$feature_policy[] = "display-capture 'none'";
$feature_policy[] = "document-domain 'none'";
$feature_policy[] = "encrypted-media 'none'";
$feature_policy[] = "fullscreen 'none'";
$feature_policy[] = "geolocation 'none'";
$feature_policy[] = "gyroscope 'none'";
$feature_policy[] = "legacy-image-formats 'none'";
$feature_policy[] = "magnetometer 'none'";
$feature_policy[] = "microphone 'none'";
$feature_policy[] = "midi 'none'";
$feature_policy[] = "oversized-images 'none'";
$feature_policy[] = "payment 'none'";
$feature_policy[] = "picture-in-picture 'none'";
$feature_policy[] = "publickey-credentials 'none'";
$feature_policy[] = "sync-xhr 'none'";
$feature_policy[] = "unsized-media 'none'";
$feature_policy[] = "usb 'none'";
$feature_policy[] = "vibrate 'none'";
$feature_policy[] = "wake-lock 'none'";
$feature_policy[] = "xr-spatial-tracking 'none'";
$feature_policy = join(';', $feature_policy);
$response->headers->set('Feature-Policy', $feature_policy);
// Defaults to same-origin if REFERRER_POLICY is not set in the .env
$response->headers->set('Referrer-Policy', config('app.referrer_policy'));
// The .env var ALLOW_IFRAMING defaults to false (which disallows IFRAMING)
// if not present, but some unique cases require this to be enabled.
// For example, some IT depts have IFRAMED Snipe-IT into their IT portal
// for convenience so while it is normally disallowed, there is
// an override that exists.
if (config('app.allow_iframing') == false) {
$response->headers->set('X-Frame-Options', 'DENY');
}
// This defaults to false to maintain backwards compatibility for
// people who are not running Snipe-IT over TLS (shame, shame, shame!)
// Seriously though, please run Snipe-IT over TLS. Let's Encrypt is free.
// https://letsencrypt.org
if (config('app.enable_hsts') === true) {
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
}
// We have to exclude debug mode here because debugbar pulls from a CDN or two
// and it will break things.
if ((config('app.debug')!='true') || (config('app.enable_csp')=='true')) {
$csp_policy[] = "default-src 'self'";
$csp_policy[] = "style-src 'self' 'unsafe-inline'";
$csp_policy[] = "script-src 'self' 'unsafe-inline' 'unsafe-eval'";
$csp_policy[] = "connect-src 'self'";
$csp_policy[] = "object-src 'none'";
$csp_policy[] = "font-src 'self' data:";
$csp_policy[] = "img-src 'self' data: gravatar.com maps.google.com maps.gstatic.com *.googleapis.com";
$csp_policy = join(';', $csp_policy);
$response->headers->set('Content-Security-Policy', $csp_policy);
}
return $response;
}
private function removeUnwantedHeaders($headerList)
{
foreach ($headerList as $header)
header_remove($header);
}
}

View file

@ -1,22 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
class XssProtectHeader
{
/**
* Handle the given request and get the response.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response
*/
public function handle($request, Closure $next)
{
$mode = '1;mode=block';
$response = $next($request);
$response->headers->set('X-XSS-Protection', $mode);
return $response;
}
}

View file

@ -63,6 +63,10 @@ abstract class Importer
'full_name' => 'full name',
'email' => 'email',
'username' => 'username',
'address' => 'address',
'city' => 'city',
'state' => 'state',
'country' => 'country',
'jobtitle' => 'job title',
'employee_num' => 'employee number',
'phone_number' => 'phone number',

View file

@ -48,6 +48,10 @@ class UserImporter extends ItemImporter
$this->item['email'] = $this->findCsvMatch($row, 'email');
$this->item['phone'] = $this->findCsvMatch($row, 'phone_number');
$this->item['jobtitle'] = $this->findCsvMatch($row, 'jobtitle');
$this->item['address'] = $this->findCsvMatch($row, 'address');
$this->item['city'] = $this->findCsvMatch($row, 'city');
$this->item['state'] = $this->findCsvMatch($row, 'state');
$this->item['country'] = $this->findCsvMatch($row, 'country');
$this->item['activated'] = ($this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')) == 1) ? '1' : 0;
$this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
$this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));

View file

@ -7,13 +7,14 @@
| department_id | | User ? All |
| item name | item_name | All |
| image | image | Asset |
| department_id | | User ? All |
| email | | |
| expiration date | expiration_date | License |
| location | location | All |
| notes | notes | All |
| licensed to email | license_email | License |
| licensed to name | license_name | License |
| maintained | maintained | License |
| manager_id | | User |
| manufacturer | manufacturer | All |
| model name | asset_model | Asset |
| model number | model_number | Asset |
@ -34,4 +35,8 @@
| name | | |
| email | | |
| username | | |
| address | address | User |
| city | city | User |
| state | state | User |
| country | country | User |

View file

@ -1094,7 +1094,7 @@ class Asset extends Depreciable
$interval = $settings->audit_warning_days ?? 0;
return $query->whereNotNull('assets.next_audit_date')
->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'")
->whereRaw("DATE_SUB(".DB::getTablePrefix()."assets.next_audit_date, INTERVAL $interval DAY) <= '".Carbon::now()."'")
->where('assets.archived', '=', 0)
->NotArchived();
}

View file

@ -126,9 +126,12 @@ final class Company extends SnipeModel
} elseif (!static::isFullMultipleCompanySupportEnabled()) {
return true;
} else {
$current_user_company_id = Auth::user()->company_id;
$companyable_company_id = $companyable->company_id;
return ($current_user_company_id == null || $current_user_company_id == $companyable_company_id || Auth::user()->isSuperUser());
if (Auth::user()) {
$current_user_company_id = Auth::user()->company_id;
$companyable_company_id = $companyable->company_id;
return ($current_user_company_id == null || $current_user_company_id == $companyable_company_id || Auth::user()->isSuperUser());
}
}
}

View file

@ -28,8 +28,9 @@ trait Loggable
{
$log = new Actionlog;
$log = $this->determineLogItemType($log);
if(Auth::user())
if (Auth::user()) {
$log->user_id = Auth::user()->id;
}
if (!isset($target)) {
throw new \Exception('All checkout logs require a target.');
@ -113,14 +114,45 @@ trait Loggable
$log->location_id = null;
$log->note = $note;
$log->action_date = $action_date;
if (!$log->action_date) {
$log->action_date = date('Y-m-d H:i:s');
if (Auth::user()) {
$log->user_id = Auth::user()->id;
}
$log->user_id = Auth::user()->id;
$log->logaction('checkin from');
$params = [
'target' => $target,
'item' => $log->item,
'admin' => $log->user,
'note' => $note,
'target_type' => $log->target_type,
'settings' => $settings,
];
$checkinClass = null;
if (method_exists($target, 'notify')) {
try {
$target->notify(new static::$checkinClass($params));
} catch (\Exception $e) {
\Log::debug($e);
}
}
// Send to the admin, if settings dictate
$recipient = new \App\Models\Recipients\AdminRecipient();
if (($settings->admin_cc_email!='') && (static::$checkinClass!='')) {
try {
$recipient->notify(new static::$checkinClass($params));
} catch (\Exception $e) {
\Log::debug($e);
}
}
return $log;
}

View file

@ -331,7 +331,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/
public function userlog()
{
return $this->hasMany('\App\Models\Actionlog', 'target_id')->orderBy('created_at', 'DESC')->withTrashed();
return $this->hasMany('\App\Models\Actionlog', 'target_id')->where('target_type', '=', 'App\Models\User')->orderBy('created_at', 'DESC')->withTrashed();
}
@ -521,6 +521,18 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
} elseif ($format=='firstname') {
$username = str_slug($first_name);
}
elseif ($format=='firstinitial.lastname') {
$username = str_slug(substr($first_name, 0, 1). '.' . str_slug($last_name));
}
elseif ($format=='lastname_firstinitial') {
$username = str_slug($last_name).'_'.str_slug(substr($first_name, 0, 1));
}
elseif ($format=='firstnamelastname') {
$username = str_slug($first_name) . str_slug($last_name);
}
elseif ($format=='firstnamelastinitial') {
$username = str_slug(($first_name.substr($last_name, 0, 1)));
}
}
$user['first_name'] = $first_name;

View file

@ -67,10 +67,18 @@ class CheckinLicenseSeatNotification extends Notification
$botname = ($this->settings->slack_botname) ? $this->settings->slack_botname : 'Snipe-Bot' ;
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
];
if ($admin) {
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>',
];
} else {
$fields = [
'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>',
'By' => 'CLI tool',
];
}

View file

@ -268,8 +268,11 @@ class AssetPresenter extends Presenter
"searchable" => true,
"sortable" => true,
"switchable" => true,
"title" => ($field->field_encrypted=='1') ?'<i class="fa fa-lock"></i> '.$field->name : $field->name,
"formatter" => "customFieldsFormatter"
"title" => $field->name,
"formatter"=> 'customFieldsFormatter',
"escape" => true,
"class" => ($field->field_encrypted=='1') ? 'css-padlock' : '',
"visible" => true,
];
}

View file

@ -197,19 +197,33 @@ return [
/*
|--------------------------------------------------------------------------
| ALLOW I-FRAMING
|--------------------------------------------------------------------------
|
| Normal users will never need to edit this. This option lets you run
| Snipe-IT within an I-Frame, which is normally disabled by default for
| security reasons, to prevent clickjacking. It should normally be set to false.
|
*/
|--------------------------------------------------------------------------
| ALLOW I-FRAMING
|--------------------------------------------------------------------------
|
| Normal users will never need to edit this. This option lets you run
| Snipe-IT within an I-Frame, which is normally disabled by default for
| security reasons, to prevent clickjacking. It should normally be set to false.
|
*/
'allow_iframing' => env('ALLOW_IFRAMING', false),
/*
|--------------------------------------------------------------------------
| ENABLE HTTP Strict Transport Security (HSTS)
|--------------------------------------------------------------------------
|
| This is set to default false for backwards compatibilty but should be
| set to true if the hosting environment allows it.
|
| See https://scotthelme.co.uk/hsts-the-missing-link-in-tls/
|
*/
'enable_hsts' => env('ENABLE_HSTS', false),
/*
|--------------------------------------------------------------------------
| REFERRER-POLICY

Binary file not shown.

Binary file not shown.

View file

@ -1,15 +1,9 @@
<style>
tr {
padding-left:30px;
}
</style>
<template>
<div v-show="processDetail" class="col-md-6 col-md-offset-3">
<div class="row">
<div class="dynamic-form-row">
<div class="col-md-5 col-xs-12">
<div class="col-md-5 col-xs-12">
<label for="import-type">Import Type:</label>
</div>
<div class="col-md-7 col-xs-12">
@ -18,90 +12,84 @@ tr {
</select2>
</div>
</div>
</div>
<div class="row">
<div class="dynamic-form-row">
<div class="col-md-5 col-xs-12">
<label for="import-update">Update Existing Values?</label>
<div class="dynamic-form-row">
<div class="col-md-5 col-xs-12">
<label for="import-update">Update Existing Values?:</label>
</div>
<div class="col-md-7 col-xs-12">
<input type="checkbox" class="minimal" name="import-update" v-model="options.update">
</div>
</div>
<div class="col-md-7 col-xs-12">
<input type="checkbox" class="minimal" name="import-update" v-model="options.update">
<div class="dynamic-form-row">
<div class="col-md-5 col-xs-12">
<label for="send-welcome">Send Welcome Email for new Users?</label>
</div>
<div class="col-md-7 col-xs-12">
<input type="checkbox" class="minimal" name="send-welcome" v-model="options.send_welcome">
</div>
</div>
<div class="dynamic-form-row">
<div class="col-md-5 col-xs-12">
<label for="run-backup">Backup before importing?</label>
</div>
<div class="col-md-7 col-xs-12">
<input type="checkbox" class="minimal" name="run-backup" v-model="options.run_backup">
</div>
</div>
</div>
</div>
<div class="row">
<div class="dynamic-form-row">
<div class="col-md-5 col-xs-12">
<label for="send-welcome">Send Welcome Email for new Users?</label>
</div>
<div class="col-md-7 col-xs-12">
<input type="checkbox" class="minimal" name="send-welcome" v-model="options.send_welcome">
</div>
</div>
</div>
<div class="row">
<div class="dynamic-form-row">
<div class="col-md-5 col-xs-12">
<label for="run-backup">Backup before importing?</label>
</div>
<div class="col-md-7 col-xs-12">
<input type="checkbox" class="minimal" name="run-backup" v-model="options.run_backup">
</div>
</div>
</div>
<div class="row">
<div class="alert col-md-8 col-md-offset-2" style="text-align:left"
:class="alertClass"
v-if="statusText">
{{ this.statusText }}
</div>
</div>
<div class="row">
<div class="col-md-12" style="padding-top: 30px;">
<div class="col-md-4 text-right"><h4>Header Field</h4></div>
<div class="col-md-4 text-left"><h4>Import Field</h4></div>
<div class="col-md-4 text-left"><h4>Sample Value</h4></div>
</div>
</div>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8" style="padding-top: 30px;">
<div class="col-md-4 text-right"><h4>Header Field</h4></div>
<div class="col-md-4"><h4>Import Field</h4></div>
<div class="col-md-4"><h4>Sample Value</h4></div>
</div>
</div>
<template v-for="(header, index) in file.header_row">
<div class="row">
<div class="col-md-12">
<div class="col-md-4 text-right">
<label :for="header" class="control-label">{{ header }}</label>
</div>
<div class="col-md-4 form-group">
<div required>
<select2 :options="columns" v-model="columnMappings[header]">
<option value="0">Do Not Import</option>
</select2>
</div>
</div>
<div class="col-md-4 text-left">
<p class="form-control-static">{{ activeFile.first_row[index] }}</p>
</div>
<template v-for="(header, index) in file.header_row">
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8">
<div class="col-md-4 text-right">
<label :for="header" class="control-label">{{ header }}</label>
</div>
<div class="col-md-4 form-group">
<div required>
<select2 :options="columns" v-model="columnMappings[header]">
<option value="0">Do Not Import</option>
</select2>
</div>
</div>
</template>
<div class="row">
<div class="col-md-6 col-md-offset-2 text-right" style="padding-top: 20px;">
<button type="button" class="btn btn-sm btn-default" @click="processDetail = false">Cancel</button>
<button type="submit" class="btn btn-sm btn-primary" @click="postSave">Import</button>
<br><br>
</div>
</div>
<div class="row">
<div class="alert col-md-12" style="padding-top: 20px;"
:class="alertClass"
v-if="statusText">
{{ this.statusText }}
<div class="col-md-4">
<p class="form-control-static">{{ activeFile.first_row[index] }}</p>
</div>
</div>
</div>
</div>
</template>
<div class="row">
<div class="col-md-6 col-md-offset-2 text-right" style="padding-top: 20px;">
<button type="button" class="btn btn-sm btn-default" @click="processDetail = false">Cancel</button>
<button type="submit" class="btn btn-sm btn-primary" @click="postSave">Import</button>
<br><br>
</div>
</div>
<div class="row">
<div class="alert col-md-8 col-md-offset-2" style="padding-top: 20px;"
:class="alertClass"
v-if="statusText">
{{ this.statusText }}
</div>
</div>
</div>
</template>
<script>
@ -181,6 +169,10 @@ tr {
{id: 'manager_last_name', text: 'Manager Last Name' },
{id: 'department', text: 'Department' },
{id: 'activated', text: 'Activated' },
{id: 'address', text: 'Address' },
{id: 'city', text: 'City' },
{id: 'state', text: 'State' },
{id: 'country', text: 'Country' },
],
customFields: this.customFields,
@ -206,14 +198,14 @@ tr {
switch(this.options.importType) {
case 'asset':
return this.columnOptions.general
.concat(this.columnOptions.assets)
.concat(this.columnOptions.customFields)
.sort(sorter);
.concat(this.columnOptions.assets)
.concat(this.columnOptions.customFields)
.sort(sorter);
case 'consumable':
return this.columnOptions.general
.concat(this.columnOptions.consumables)
.sort(sorter);
.concat(this.columnOptions.consumables)
.sort(sorter);
case 'license':
return this.columnOptions.general.concat(this.columnOptions.licenses).sort(sorter);
case 'user':
@ -304,4 +296,4 @@ tr {
select2: require('../select2.vue')
}
}
</script>
</script>

View file

@ -83,7 +83,7 @@
<div class="modal-header">
<button type="button " class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h2 class="modal-title">
<h4 class="modal-title">
Create Client
</h2>
</div>
@ -151,7 +151,7 @@
<div class="modal-header">
<button type="button " class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h2 class="modal-title">
<h4 class="modal-title">
Edit Client
</h2>
</div>

View file

@ -466,6 +466,74 @@ h4 {
display: table-cell;
}
/**
COLUMN SELECTOR ICONS
-----------------------------
This is kind of weird, but it is necessary to prevent the column-selector code from barfing, since
any HTML used in the UserPresenter "title" attribute breaks the column selector HTML.
Instead, we use CSS to add the icon into the table header, which leaves the column selector
"title" text as-is.
See https://github.com/snipe/snipe-it/issues/7989
*/
th.css-barcode > .th-inner,
th.css-license > .th-inner,
th.css-consumable > .th-inner,
th.css-accessory > .th-inner
{
font-size: 0px;
line-height: 4!important;
text-align: left;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
th.css-padlock > .th-inner::before,
th.css-barcode > .th-inner::before,
th.css-license > .th-inner::before,
th.css-consumable > .th-inner::before,
th.css-accessory > .th-inner::before
{
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: 20px;
}
th.css-padlock > .th-inner::before
{
content: "\f023";
padding-right: 2px;
}
th.css-barcode > .th-inner::before
{
content: "\f02a";
}
th.css-license > .th-inner::before
{
content: "\f0c7";
}
th.css-consumable > .th-inner::before
{
content: "\f043";
}
th.css-accessory > .th-inner::before
{
content: "\f11c";
}
.small-box .inner {
padding-left: 15px;
padding-right: 15px;

View file

@ -88,6 +88,9 @@
'firstname_lastname_underscore_format' => 'First Name Last Name (jane_smith@example.com)',
'lastnamefirstinitial_format' => 'Last Name First Initial (smithj@example.com)',
'first' => 'First',
'firstnamelastname' => 'First Name Last Name (janesmith@example.com)',
'lastname_firstinitial' => 'Last Name First Initial (smith_j@example.com)',
'firstinitial.lastname' => 'First Initial Last Name (j.smith@example.com)', 'first' => 'First',
'first_name' => 'First Name',
'first_name_format' => 'First Name (jane@example.com)',
'files' => 'Files',

View file

@ -8,6 +8,7 @@ return array(
'owner_doesnt_match_asset' => 'The asset you are trying to associate with this license is owned by somene other than the person selected in the assigned to dropdown.',
'assoc_users' => 'This license is currently checked out to a user and cannot be deleted. Please check the license in first, and then try deleting again. ',
'select_asset_or_person' => 'You must select an asset or a user, but not both.',
'not_found' => 'License not found',
'create' => array(

View file

@ -91,6 +91,11 @@
'lastnamefirstinitial_format' => 'Last Name First Initial (smithj@example.com)',
'firstintial_dot_lastname_format' => 'First Initial Last Name (j.smith@example.com)',
'first' => 'First',
'firstnamelastname' => 'First Name Last Name (janesmith@example.com)',
'lastname_firstinitial' => 'Last Name First Initial (smith_j@example.com)',
'firstinitial.lastname' => 'First Initial Last Name (j.smith@example.com)',
'firstnamelastinitial' => 'First Name Last Initial (janes@example.com)',
'first' => 'First',
'first_name' => 'First Name',
'first_name_format' => 'First Name (jane@example.com)',
'files' => 'Files',

View file

@ -464,11 +464,14 @@ Form::macro('username_format', function ($name = "username_format", $selected =
$formats = array(
'firstname.lastname' => trans('general.firstname_lastname_format'),
'firstname_lastname' => trans('general.firstname_lastname_underscore_format'),
'filastname' => trans('general.filastname_format'),
'firstintial.lastname' => trans('general.firstintial_dot_lastname_format'),
'firstname' => trans('general.first_name_format'),
'filastname' => trans('general.filastname_format'),
'lastnamefirstinitial' => trans('general.lastnamefirstinitial_format'),
'firstname_lastname' => trans('general.firstname_lastname_underscore_format'),
'firstinitial.lastname' => trans('general.firstinitial.lastname'),
'lastname_firstinitial' => trans('general.lastname_firstinitial'),
'firstnamelastname' => trans('general.firstnamelastname'),
'firstnamelastinitial' => trans('general.firstnamelastinitial')
);
$select = '<select name="'.$name.'" class="'.$class.'" style="width: 100%" aria-label="'.$name.'">';

View file

@ -314,7 +314,7 @@ View Assets for {{ $user->present()->fullName() }}
}'>
<thead>
<tr>
<th data-switchable="true" data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter"><span class="sr-only">Icon</span></th>
<th data-switchable="true" data-visible="true" data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">Icon</th>
<th data-switchable="true" data-visible="true" class="col-sm-3" data-field="created_at" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
<th data-switchable="true" data-visible="true" class="col-sm-3" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
<th data-switchable="true" data-visible="true" class="col-sm-3" data-field="action_type">{{ trans('general.action') }}</th>

View file

@ -179,16 +179,18 @@
$("#assignto_selector").show();
$("#assigned_user").show();
$("#selected_status_status").removeClass('alert-msg');
$("#selected_status_status").removeClass('text-danger');
$("#selected_status_status").removeClass('text-warning');
$("#selected_status_status").addClass('text-success');
$("#selected_status_status").html('<i class="fa fa-check"></i> That status is deployable. This asset can be checked out.');
} else {
$("#assignto_selector").hide();
$("#selected_status_status").removeClass('text-danger');
$("#selected_status_status").removeClass('text-success');
$("#selected_status_status").addClass('alert-msg');
$("#selected_status_status").html('<i class="fa fa-times"></i> That asset status is not deployable. This asset cannot be checked out. ');
$("#selected_status_status").addClass('text-warning');
$("#selected_status_status").html('<i class="fa fa-warning"></i> That asset status is not deployable. This asset cannot be checked out. ');
}
}
});

View file

@ -1,210 +1,167 @@
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Labels</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Labels</title>
</head>
<body>
<?php
$settings->labels_width = $settings->labels_width - $settings->labels_display_sgutter;
$settings->labels_height = $settings->labels_height - $settings->labels_display_bgutter;
// Leave space on bottom for 1D barcode if necessary
$qr_size = ($settings->alt_barcode_enabled=='1') && ($settings->alt_barcode!='') ? $settings->labels_height - .3 : $settings->labels_height;
// Leave space on left for QR code if necessary
$qr_txt_size = ($settings->qr_code=='1' ? $settings->labels_width - $qr_size - .1 : $settings->labels_width);
?>
<?php
$settings->labels_width = $settings->labels_width - $settings->labels_display_sgutter;
$settings->labels_height = $settings->labels_height - $settings->labels_display_bgutter;
// Leave space on bottom for 1D barcode if necessary
$qr_size = ($settings->alt_barcode_enabled=='1') && ($settings->alt_barcode!='') ? $settings->labels_height - .3 : $settings->labels_height - .3;
?>
<style>
<style>
body {
font-family: arial, helvetica, sans-serif;
width: {{ $settings->labels_pagewidth }}in;
height: {{ $settings->labels_pageheight }}in;
margin: {{ $settings->labels_pmargin_top }}in {{ $settings->labels_pmargin_right }}in {{ $settings->labels_pmargin_bottom }}in {{ $settings->labels_pmargin_left }}in;
font-size: {{ $settings->labels_fontsize }}pt;
}
.label {
width: {{ $settings->labels_width }}in;
height: {{ $settings->labels_height }}in;
padding: 0in;
margin-right: {{ $settings->labels_display_sgutter }}in; /* the gutter */
margin-bottom: {{ $settings->labels_display_bgutter }}in;
display: inline-block;
overflow: hidden;
}
.page-break {
page-break-after:always;
}
div.qr_img {
width: {{ $qr_size }}in;
height: {{ $qr_size }}in;
body {
font-family: arial, helvetica, sans-serif;
width: {{ $settings->labels_pagewidth }}in;
height: {{ $settings->labels_pageheight }}in;
margin: {{ $settings->labels_pmargin_top }}in {{ $settings->labels_pmargin_right }}in {{ $settings->labels_pmargin_bottom }}in {{ $settings->labels_pmargin_left }}in;
font-size: {{ $settings->labels_fontsize }}pt;
}
float: left;
display: inline-flex;
padding-right: .15in;
}
img.qr_img {
.label {
width: {{ $settings->labels_width }}in;
height: {{ $settings->labels_height }}in;
padding: 0in;
margin-right: {{ $settings->labels_display_sgutter }}in; /* the gutter */
margin-bottom: {{ $settings->labels_display_bgutter }}in;
display: inline-block;
overflow: hidden;
}
width: 120.79%;
height: 120.79%;
margin-top: -6.9%;
margin-left: -6.9%;
padding-bottom: .04in;
}
img.barcode {
display:block;
.page-break {
page-break-after:always;
}
padding-top: .11in;
width: 100%;
}
.qr_text {
width: {{ $settings->labels_width }}in;
height: {{ $settings->labels_height }}in;
padding-top: {{$settings->labels_display_bgutter}}in;
font-family: arial, helvetica, sans-serif;
font-size: {{$settings->labels_fontsize}};
padding-right: .01in;
overflow: hidden !important;
display: inline;
word-wrap: break-word;
word-break: break-all;
}
div.barcode_container {
div.qr_img {
width: {{ $qr_size }}in;
height: {{ $qr_size }}in;
float: left;
display: inline-block;
padding-right: .04in;
}
img.qr_img {
width: 100%;
height: 100%;
}
img.barcode {
display: block;
margin-left: auto;
margin-right: auto;
}
div.label-logo {
float: right;
display: inline-block;
}
img.label-logo {
height: 0.5in;
}
.qr_text {
width: {{ $qr_txt_size }}in;
height: {{ $qr_size }}in;
padding-top: .10in;
font-family: arial, helvetica, sans-serif;
padding-right: .01in;
overflow: hidden !important;
display: inline-block;
word-wrap: break-word;
word-break: break-all;
}
div.barcode_container {
float: left;
width: 100%;
display: inline;
height: 50px;
}
.next-padding {
margin: {{ $settings->labels_pmargin_top }}in {{ $settings->labels_pmargin_right }}in {{ $settings->labels_pmargin_bottom }}in {{ $settings->labels_pmargin_left }}in;
}
.label-model::before {
content: "M: "
}
.label-company::before {
content: "C: "
}
.label-asset_tag::before {
content: "T: "
}
.label-serial::before {
content: "S: "
}
.label-name::before {
content: "N: "
}
@media print {
.noprint {
display: none !important;
width: 100%;
display: inline;
overflow: hidden;
}
.next-padding {
margin: {{ $settings->labels_pmargin_top }}in {{ $settings->labels_pmargin_right }}in {{ $settings->labels_pmargin_bottom }}in {{ $settings->labels_pmargin_left }}in;
font-size: 0;
margin: {{ $settings->labels_pmargin_top }}in {{ $settings->labels_pmargin_right }}in {{ $settings->labels_pmargin_bottom }}in {{ $settings->labels_pmargin_left }}in;
}
}
@media screen {
.label {
outline: .02in black solid; /* outline doesn't occupy space like border does */
@media print {
.noprint {
display: none !important;
}
.next-padding {
margin: {{ $settings->labels_pmargin_top }}in {{ $settings->labels_pmargin_right }}in {{ $settings->labels_pmargin_bottom }}in {{ $settings->labels_pmargin_left }}in;
font-size: 0;
}
}
.noprint {
font-size: 13px;
padding-bottom: 15px;
@media screen {
.label {
outline: .02in black solid; /* outline doesn't occupy space like border does */
}
.noprint {
font-size: 13px;
padding-bottom: 15px;
}
}
}
@if ($snipeSettings->custom_css)
{!! $snipeSettings->show_custom_css() !!}
@endif
</style>
@if ($snipeSettings->custom_css)
{{ $snipeSettings->show_custom_css() }}
@endif
</style>
@foreach ($assets as $asset)
<?php $count++; ?>
<div class="label">
<?php $count++; ?>
<div class="label">
@if ($settings->qr_code=='1')
<div class="label-qr_img qr_img">
@if ($bulkedit==False)
<img src="./qr_code" class="qr_img">
@else
<img src="./{{ $asset->id }}/qr_code" class="qr_img">
@endif
</div>
@endif
<div class="qr_text">
@if ($settings->label_logo)
<div class="label-logo">
<img class="label-logo" src="{{ Storage::disk('public')->url('').e($snipeSettings->label_logo) }}">
</div>
@if ($settings->qr_code=='1')
<div class="qr_img">
<img src="./{{ $asset->id }}/qr_code" class="qr_img">
</div>
@endif
@if ($settings->qr_text!='')
<div class="label-qr_text pull-left">
<strong>{{ $settings->qr_text }}</strong>
<br>
<div class="qr_text">
@if ($settings->qr_text!='')
<div class="pull-left">
<strong>{{ $settings->qr_text }}</strong>
<br>
</div>
@endif
@if (($settings->labels_display_company_name=='1') && ($asset->company))
<div class="pull-left">
C: {{ $asset->company->name }}
</div>
@endif
@if (($settings->labels_display_name=='1') && ($asset->name!=''))
<div class="pull-left">
N: {{ $asset->name }}
</div>
@endif
@if (($settings->labels_display_tag=='1') && ($asset->asset_tag!=''))
<div class="pull-left">
T: {{ $asset->asset_tag }}
</div>
@endif
@if (($settings->labels_display_serial=='1') && ($asset->serial!=''))
<div class="pull-left">
S: {{ $asset->serial }}
</div>
@endif
@if (($settings->labels_display_model=='1') && ($asset->model->name!=''))
<div class="pull-left">
M: {{ $asset->model->name }} {{ $asset->model->model_number }}
</div>
@endif
</div>
@if ((($settings->alt_barcode_enabled=='1') && $settings->alt_barcode!=''))
<div class="barcode_container">
<img src="./{{ $asset->id }}/barcode" class="barcode">
</div>
@endif
@if (($settings->labels_display_company_name=='1') && ($asset->company))
<div class="label-company pull-left">
{{ $asset->company->name }}
</div>
@endif
@if (($settings->labels_display_name=='1') && ($asset->name!=''))
<div class="label-name pull-left">
{{ $asset->name }}
</div>
@endif
@if (($settings->labels_display_tag=='1') && ($asset->asset_tag!=''))
<div class="label-asset_tag pull-left">
{{ $asset->asset_tag }}
</div>
@endif
@if (($settings->labels_display_serial=='1') && ($asset->serial!=''))
<div class="label-serial pull-left">
{{ $asset->serial }}
</div>
@endif
@if (($settings->labels_display_model=='1') && ($asset->model->name!=''))
<div class="label-model pull-left">
{{ $asset->model->name }} {{ $asset->model->model_number }}
</div>
@endif
</div>
@if ((($settings->alt_barcode_enabled=='1') && $settings->alt_barcode!=''))
<div class="label-alt_barcode barcode_container">
@if ($bulkedit==False)
<img src="./barcode" class="barcode">
@else
<img src="./{{ $asset->id }}/barcode" class="barcode">
@endif
</div>
@if ($count % $settings->labels_per_page == 0)
<div class="page-break"></div>
<div class="next-padding">&nbsp;</div>
@endif
</div>
@if ($count % $settings->labels_per_page == 0)
<div class="page-break"></div>
<div class="next-padding">&nbsp;</div>
@endif
@endforeach
</body>
</html>
</html>

View file

@ -991,48 +991,48 @@
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'
data-url="{{ route('api.activity.index', ['item_id' => $asset->id, 'item_type' => 'asset']) }}"
data-cookie-id-table="assetHistory">
<thead>
<tr>
<th data-field="icon" data-visible="true" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter"><span class="sr-only">Icon</span></th>
<th class="col-sm-2" data-visible="true" data-field="created_at" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
<th class="col-sm-1" data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
<th class="col-sm-1" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
<th class="col-sm-2" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
<th class="col-sm-2" data-visible="true" data-field="target" data-formatter="polymorphicItemFormatter">{{ trans('general.target') }}</th>
<th class="col-sm-2" data-field="note">{{ trans('general.notes') }}</th>
@if ($snipeSettings->require_accept_signature=='1')
<th class="col-md-3" data-field="signature_file" data-visible="false" data-formatter="imageFormatter">{{ trans('general.signature') }}</th>
@endif
<th class="col-md-3" data-visible="false" data-field="file" data-visible="false" data-formatter="fileUploadFormatter">{{ trans('general.download') }}</th>
<th class="col-sm-2" data-field="log_meta" data-visible="true" data-formatter="changeLogFormatter">Changed</th>
</tr>
</thead>
</table>
data-url="{{ route('api.activity.index', ['item_id' => $asset->id, 'item_type' => 'asset']) }}"
data-cookie-id-table="assetHistory">
<thead>
<tr>
<th data-visible="true" style="width: 40px;" class="hidden-xs">Icon</th>
<th class="col-sm-2" data-visible="true" data-field="created_at" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
<th class="col-sm-1" data-visible="true" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
<th class="col-sm-1" data-visible="true" data-field="action_type">{{ trans('general.action') }}</th>
<th class="col-sm-2" data-visible="true" data-field="item" data-formatter="polymorphicItemFormatter">{{ trans('general.item') }}</th>
<th class="col-sm-2" data-visible="true" data-field="target" data-formatter="polymorphicItemFormatter">{{ trans('general.target') }}</th>
<th class="col-sm-2" data-field="note">{{ trans('general.notes') }}</th>
@if ($snipeSettings->require_accept_signature=='1')
<th class="col-md-3" data-field="signature_file" data-visible="false" data-formatter="imageFormatter">{{ trans('general.signature') }}</th>
@endif
<th class="col-md-3" data-visible="false" data-field="file" data-visible="false" data-formatter="fileUploadFormatter">{{ trans('general.download') }}</th>
<th class="col-sm-2" data-field="log_meta" data-visible="true" data-formatter="changeLogFormatter">Changed</th>
</tr>
</thead>
</table>
</div>
</div> <!-- /.row -->
</div> <!-- /.tab-pane history -->
</div>
</div> <!-- /.row -->
</div> <!-- /.tab-pane history -->
<div class="tab-pane fade" id="files">
<div class="row">
<div class="col-md-12">
<div class="tab-pane fade" id="files">
<div class="row">
<div class="col-md-12">
@if ($asset->uploads->count() > 0)
<table
class="table table-striped snipe-table"
id="assetFileHistory"
data-pagination="true"
data-id-table="assetFileHistory"
data-search="true"
data-side-pagination="client"
data-show-columns="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
@if ($asset->uploads->count() > 0)
<table
class="table table-striped snipe-table"
id="assetFileHistory"
data-pagination="true"
data-id-table="assetFileHistory"
data-search="true"
data-side-pagination="client"
data-show-columns="true"
data-show-refresh="true"
data-sort-order="desc"
data-sort-name="created_at"
data-show-export="true"
data-export-options='{
"fileName": "export-asset-{{ $asset->id }}-files",
"ignoreColumn": ["actions","image","change","checkbox","checkincheckout","icon"]
}'

View file

@ -821,7 +821,7 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h2 class="modal-title" id="myModalLabel">&nbsp;</h4>
<h4 class="modal-title" id="myModalLabel">&nbsp;</h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">

View file

@ -326,9 +326,9 @@
<table
data-columns="{{ \App\Presenters\LicensePresenter::dataTableLayoutSeats() }}"
data-cookie-id-table="seatsTable-{{ $license->id }}"
data-id-table="seatsTable-{{ $license->id }}"
id="seatsTable-{{$license->id}}"
data-cookie-id-table="seatsTable"
data-id-table="seatsTable"
id="seatsTable"
data-pagination="true"
data-search="false"
data-side-pagination="server"
@ -375,7 +375,7 @@
}'>
<thead>
<tr>
<th data-visible="true" aria-hidden="true"><span class="sr-only">Icon</span></th>
<th data-visible="true" aria-hidden="true">Icon</th>
<th class="col-md-4" data-field="file_name" data-visible="true" data-sortable="true" data-switchable="true">{{ trans('general.file_name') }}</th>
<th class="col-md-4" data-field="notes" data-visible="true" data-sortable="true" data-switchable="true">{{ trans('general.notes') }}</th>
<th class="col-md-2" data-field="created_at" data-visible="true" data-sortable="true" data-switchable="true">{{ trans('general.created_at') }}</th>

View file

@ -1,12 +1,12 @@
{{-- See snipeit_modals.js for what powers this --}}
<script nonce="{{ csrf_token() }}">
$(document).ready(function () {
$('#genPassword').pGenerator({
window.setTimeout(function () {
$('#modal-genPassword').pGenerator({
'bind': 'click',
'passwordElement': '#modal-password',
'displayElement': '#generated-password',
'displayElement': '#modal-generated-password',
'passwordLength': 16,
'uppercase': true,
'lowercase': true,
@ -16,13 +16,14 @@
$('#modal-password_confirmation').val($('#modal-password').val());
}
});
});
}, 1000);
</script>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h2 class="modal-title">{{ trans('admin/users/table.createuser') }}</h4>
<h2 class="modal-title">{{ trans('admin/users/table.createuser') }}</h2>
</div>
<div class="modal-body">
<form action="{{ route('api.users.store') }}" onsubmit="return false">
@ -46,14 +47,14 @@
<div class="dynamic-form-row">
<div class="col-md-4 col-xs-12"><label for="modal-password">{{ trans('admin/users/table.password') }}:</label></div>
<div class="col-md-8 col-xs-12 required"><input type='password' name="password" id='modal-password' class="form-control">
<a href="#" class="left" id="genPassword">Generate</a>
<a href="#" class="left" id="modal-genPassword">Generate</a>
</div>
</div>
<div class="dynamic-form-row">
<div class="col-md-4 col-xs-12"><label for="modal-password_confirmation">{{ trans('admin/users/table.password_confirm') }}:</label></div>
<div class="col-md-8 col-xs-12 required"><input type='password' name="password_confirmation" id='modal-password_confirmation' class="form-control">
<div id="generated-password"></div>
<div id="modal-generated-password"></div>
</div>
</div>
</form>

View file

@ -37,7 +37,7 @@
<thead>
<tr>
<th data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter"><span class="sr-only">Icon</span></th>
<th data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">Icon</th>
<th class="col-sm-3" data-searchable="false" data-sortable="true" data-field="created_at" data-formatter="dateDisplayFormatter">{{ trans('general.date') }}</th>
<th class="col-sm-2" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
<th class="col-sm-2" data-field="action_type">{{ trans('general.action') }}</th>

View file

@ -55,6 +55,15 @@
</div>
</div>
<!-- City -->
<div class="form-group{{ $errors->has('city') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="city">{{ trans('general.city') }}</label>
<div class="col-md-4">
<input class="form-control" type="text" name="city" id="city" aria-label="city" />
{!! $errors->first('city', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<!-- activated -->
<div class="form-group">
<div class="col-sm-3 control-label">

View file

@ -15,66 +15,6 @@
@section('header_right')
<style>
/**
This is kind of weird, but it is necessary to prevent the column-selector code from barfing, since
any HTML used in the UserPresenter "title" attribute breaks the column selector HTML.
Instead, we use CSS to add the icon into the table header, which leaves the column selector
"title" text as-is.
See https://github.com/snipe/snipe-it/issues/7989
*/
th.css-barcode > .th-inner,
th.css-license > .th-inner,
th.css-consumable > .th-inner,
th.css-accessory > .th-inner
{
font-size: 0px;
line-height: 4!important;
text-align: left;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
th.css-barcode > .th-inner::before,
th.css-license > .th-inner::before,
th.css-consumable > .th-inner::before,
th.css-accessory > .th-inner::before
{
display: inline-block;
font: normal normal normal 14px/1 FontAwesome;
font-size: 20px;
}
th.css-barcode > .th-inner::before
{
content: "\f02a";
}
th.css-license > .th-inner::before
{
content: "\f0c7";
}
th.css-consumable > .th-inner::before
{
content: "\f043";
}
th.css-accessory > .th-inner::before
{
content: "\f11c";
}
</style>
@can('create', \App\Models\User::class)
@if ($snipeSettings->ldap_enabled == 1)
<a href="{{ route('ldap/user') }}" class="btn btn-default pull-right"><span class="fa fa-sitemap"></span> LDAP Sync</a>
@ -153,6 +93,7 @@
@section('moar_scripts')
@include ('partials.bootstrap-table')

View file

@ -142,11 +142,31 @@
<td class="text-nowrap">{{ trans('admin/users/table.name') }}</td>
<td>{{ $user->present()->fullName() }}</td>
</tr>
<tr>
<td class="text-nowrap">{{ trans('admin/users/table.username') }}</td>
<td>{{ $user->username }}</td>
</tr>
@if (($user->address) || ($user->city) || ($user->state) || ($user->country))
<tr>
<td class="text-nowrap">{{ trans('general.address') }}</td>
<td>
@if ($user->address)
{{ $user->address }} <br>
@endif
@if ($user->city)
{{ $user->city }}
@endif
@if ($user->state)
{{ $user->state }}
@endif
@if ($user->country)
{{ $user->country }}
@endif
</td>
</tr>
@endif
<tr>
<td class="text-nowrap">{{ trans('general.groups') }}</td>
<td>
@ -244,6 +264,13 @@
<td class="text-nowrap">{{ trans('general.login_enabled') }}</td>
<td>{{ ($user->activated=='1') ? trans('general.yes') : trans('general.no') }}</td>
</tr>
@if ($user->ldap_import!='1')
<tr>
<td class="text-nowrap">LDAP</td>
<td>{{ trans('general.yes') }}</td>
</tr>
@endif
@if ($user->activated=='1')
<tr>
@ -278,6 +305,13 @@
@endif
@if ($user->notes)
<tr>
<td class="text-nowrap">{{ trans('admin/users/table.notes') }}</td>
<td>{{ $user->notes }}</td>
</tr>
@endif
</table>
</div>
@ -541,7 +575,7 @@
}'>
<thead>
<tr>
<th data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter"><span class="sr-only">Icon</span></th>
<th data-field="icon" style="width: 40px;" class="hidden-xs" data-formatter="iconFormatter">Icon</th>
<th class="col-sm-3" data-field="created_at" data-formatter="dateDisplayFormatter" data-sortable="true">{{ trans('general.date') }}</th>
<th class="col-sm-2" data-field="admin" data-formatter="usersLinkObjFormatter">{{ trans('general.admin') }}</th>
<th class="col-sm-2" data-field="action_type">{{ trans('general.action') }}</th>

View file

@ -233,12 +233,21 @@ Route::group(['prefix' => 'v1','namespace' => 'Api', 'middleware' => 'auth:api']
]
); // Consumables resource
Route::get('consumables/view/{id}/users',
[
'as' => 'api.consumables.showUsers',
'uses' => 'ConsumablesController@getDataView'
]
);
Route::group(['prefix' => 'consumables'], function () {
Route::get('view/{id}/users',
[
'as' => 'api.consumables.showUsers',
'uses' => 'ConsumablesController@getDataView'
]
);
Route::post('{consumable}/checkout',
[
'as' => 'api.consumables.checkout',
'uses' => 'ConsumablesController@checkout'
]
);
});
/*--- Depreciations API ---*/

View file

@ -17,7 +17,6 @@ Route::group([ 'prefix' => 'fields','middleware' => ['auth'] ], function () {
'as' => 'fields.optional']
);
Route::get('{field_id}/fieldset/{fieldset_id}/disassociate',
['uses' => 'CustomFieldsController@deleteFieldFromFieldset',
'as' => 'fields.disassociate']

View file

@ -438,7 +438,7 @@ case $distro in
fi
;;
ubuntu)
if [ "$version" == "18.04" ]; then
if [ "$version" -ge "18.04" ]; then
# Install for Ubuntu 18.04
tzone=$(cat /etc/timezone)

View file

@ -33,7 +33,7 @@ class UserTest extends BaseTest
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_firstname = 'Natalia';
$expected_lastname = "Allanovna Romanova-O'Shostakova";
$user = User::generateFormattedNameFromFullName($fullname, 'firstname');
$user = User::generateFormattedNameFromFullName('firstname', $fullname);
$this->assertEquals($expected_firstname, $user['first_name']);
$this->assertEquals($expected_lastname, $user['last_name']);
}
@ -74,7 +74,7 @@ class UserTest extends BaseTest
public function testFirstInitialUnderscoreLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'natalia_allanovna-romanova-oshostakova';
$expected_username = 'n_allanovna-romanova-oshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstname_lastname');
$this->assertEquals($expected_username, $user['username']);
}
@ -83,9 +83,36 @@ class UserTest extends BaseTest
{
$fullname = "Natalia";
$expected_username = 'natalia';
$user = User::generateFormattedNameFromFullName($fullname, 'firstname_lastname');
$user = User::generateFormattedNameFromFullName('firstname_lastname', $fullname);
$this->assertEquals($expected_username, $user['username']);
}
public function firstInitialDotLastname()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'n.allanovnaromanovaoshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstinitial.lastname');
$this->assertEquals($expected_username, $user['username']);
}
public function lastNameUnderscoreFirstInitial()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'allanovnaromanovaoshostakova_n';
$user = User::generateFormattedNameFromFullName($fullname, 'lastname_firstinitial');
$this->assertEquals($expected_username, $user['username']);
}
public function firstNameLastName()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'nataliaallanovnaromanovaoshostakova';
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastname)';
$this->assertEquals($expected_username, $user['username']);
}
public function firstNameLastInitial()
{
$fullname = "Natalia Allanovna Romanova-O'Shostakova";
$expected_username = 'nataliaa';
$user = User::generateFormattedNameFromFullName($fullname, 'firstnamelastinitial');
$this->assertEquals($expected_username, $user['username']);
}
}