Merge branch 'develop' into develop

This commit is contained in:
cram42 2023-01-24 09:53:42 +08:00 committed by GitHub
commit c2c009a018
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
693 changed files with 6136 additions and 3136 deletions

View file

@ -2783,6 +2783,60 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "lukasfehling",
"name": "Lukas Fehling",
"avatar_url": "https://avatars.githubusercontent.com/u/56871540?v=4",
"profile": "https://github.com/lukasfehling",
"contributions": [
"code"
]
},
{
"login": "fernando-almeida",
"name": "Fernando Almeida",
"avatar_url": "https://avatars.githubusercontent.com/u/1975990?v=4",
"profile": "https://github.com/fernando-almeida",
"contributions": [
"code"
]
},
{
"login": "akemidx",
"name": "akemidx",
"avatar_url": "https://avatars.githubusercontent.com/u/116301219?v=4",
"profile": "https://github.com/akemidx",
"contributions": [
"code"
]
},
{
"login": "oguzbilgic",
"name": "Oguz Bilgic",
"avatar_url": "https://avatars.githubusercontent.com/u/144778?v=4",
"profile": "http://oguz.site",
"contributions": [
"code"
]
},
{
"login": "scoo73r",
"name": "Scooter Crawford",
"avatar_url": "https://avatars.githubusercontent.com/u/9262438?v=4",
"profile": "https://github.com/scoo73r",
"contributions": [
"code"
]
},
{
"login": "subdriven",
"name": "subdriven",
"avatar_url": "https://avatars.githubusercontent.com/u/5957345?v=4",
"profile": "https://github.com/subdriven",
"contributions": [
"code"
]
} }
] ]
} }

View file

@ -174,3 +174,9 @@ REPORT_TIME_LIMIT=12000
REQUIRE_SAML=false REQUIRE_SAML=false
API_THROTTLE_PER_MINUTE=120 API_THROTTLE_PER_MINUTE=120
CSV_ESCAPE_FORMULAS=true CSV_ESCAPE_FORMULAS=true
# --------------------------------------------
# OPTIONAL: SCIM
# --------------------------------------------
SCIM_TRACE=false
SCIM_STANDARDS_COMPLIANCE=false

View file

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

View file

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

View file

@ -41,7 +41,7 @@ jobs:
steps: steps:
# https://github.com/actions/checkout # https://github.com/actions/checkout
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
# https://github.com/docker/setup-buildx-action # https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx - name: Setup Docker Buildx

View file

@ -41,7 +41,7 @@ jobs:
steps: steps:
# https://github.com/actions/checkout # https://github.com/actions/checkout
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v3.1.0 uses: actions/checkout@v3.3.0
# https://github.com/docker/setup-buildx-action # https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx - name: Setup Docker Buildx

View file

@ -1,5 +1,5 @@
![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) ![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)
[![All Contributors](https://img.shields.io/badge/all_contributors-306-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) [![All Contributors](https://img.shields.io/badge/all_contributors-312-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 ## Snipe-IT - Open Source Asset Management System
@ -60,6 +60,7 @@ Since the release of the JSON REST API, several third-party developers have been
- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft) - [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it - [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
- [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance - [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
- [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT - [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
- [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira) - [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag. - [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
@ -138,7 +139,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | | [<img src="https://avatars.githubusercontent.com/u/38761237?v=4" width="110px;"/><br /><sub>Alex Janes</sub>](https://adagiohealth.org)<br />[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [<img src="https://avatars.githubusercontent.com/u/32387849?v=4" width="110px;"/><br /><sub>Nuraeil</sub>](https://github.com/nuraeil)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [<img src="https://avatars.githubusercontent.com/u/48162670?v=4" width="110px;"/><br /><sub>TenOfTens</sub>](https://github.com/TenOfTens)<br />[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [<img src="https://avatars.githubusercontent.com/u/9415391?v=4" width="110px;"/><br /><sub>waffle</sub>](https://ditisjens.be/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") | [<img src="https://avatars.githubusercontent.com/u/3839381?v=4" width="110px;"/><br /><sub>Achmad Fienan Rahardianto</sub>](https://github.com/veenone)<br />[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [<img src="https://avatars.githubusercontent.com/u/19945501?v=4" width="110px;"/><br /><sub>Yevhenii Huzii</sub>](https://github.com/QveenSi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=QveenSi "Code") |
| [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | | [<img src="https://avatars.githubusercontent.com/u/97299851?v=4" width="110px;"/><br /><sub>Christian Weirich</sub>](https://github.com/chrisweirich)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | [<img src="https://avatars.githubusercontent.com/u/1294403?v=4" width="110px;"/><br /><sub>denzfarid</sub>](https://github.com/denzfarid)<br /> | [<img src="https://avatars.githubusercontent.com/u/94018771?v=4" width="110px;"/><br /><sub>ntbutler-nbcs</sub>](https://github.com/ntbutler-nbcs)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [<img src="https://avatars.githubusercontent.com/u/172697?v=4" width="110px;"/><br /><sub>Naveen</sub>](https://naveensrinivasan.dev)<br />[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [<img src="https://avatars.githubusercontent.com/u/55674383?v=4" width="110px;"/><br /><sub>Mike Roquemore</sub>](https://github.com/mikeroq)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [<img src="https://avatars.githubusercontent.com/u/7991086?v=4" width="110px;"/><br /><sub>Daniel Reeder</sub>](https://github.com/reederda)<br />[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [<img src="https://avatars.githubusercontent.com/u/109422491?v=4" width="110px;"/><br /><sub>vickyjaura183</sub>](https://github.com/vickyjaura183)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | | [<img src="https://avatars.githubusercontent.com/u/32363424?v=4" width="110px;"/><br /><sub>Peace</sub>](https://github.com/julian-piehl)<br />[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | [<img src="https://avatars.githubusercontent.com/u/231528?v=4" width="110px;"/><br /><sub>Kyle Gordon</sub>](https://github.com/kylegordon)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [<img src="https://avatars.githubusercontent.com/u/53009155?v=4" width="110px;"/><br /><sub>Katharina Drexel</sub>](http://www.bfh.ch)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [<img src="https://avatars.githubusercontent.com/u/1931963?v=4" width="110px;"/><br /><sub>David Sferruzza</sub>](https://david.sferruzza.fr/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [<img src="https://avatars.githubusercontent.com/u/19511639?v=4" width="110px;"/><br /><sub>Rick Nelson</sub>](https://github.com/rnelsonee)<br />[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [<img src="https://avatars.githubusercontent.com/u/94169344?v=4" width="110px;"/><br /><sub>BasO12</sub>](https://github.com/BasO12)<br />[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [<img src="https://avatars.githubusercontent.com/u/111710123?v=4" width="110px;"/><br /><sub>Vautia</sub>](https://github.com/Vautia)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") |
| [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | | [<img src="https://avatars.githubusercontent.com/u/28321?v=4" width="110px;"/><br /><sub>Chris Hartjes</sub>](http://www.littlehart.net/atthekeyboard)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [<img src="https://avatars.githubusercontent.com/u/2404584?v=4" width="110px;"/><br /><sub>geo-chen</sub>](https://github.com/geo-chen)<br />[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [<img src="https://avatars.githubusercontent.com/u/6006620?v=4" width="110px;"/><br /><sub>Phan Nguyen</sub>](https://github.com/nh314)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [<img src="https://avatars.githubusercontent.com/u/115993812?v=4" width="110px;"/><br /><sub>Iisakki Jaakkola</sub>](https://github.com/StarlessNights)<br />[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="110px;"/><br /><sub>Ikko Ashimine</sub>](https://bandism.net/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [<img src="https://avatars.githubusercontent.com/u/56871540?v=4" width="110px;"/><br /><sub>Lukas Fehling</sub>](https://github.com/lukasfehling)<br />[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [<img src="https://avatars.githubusercontent.com/u/1975990?v=4" width="110px;"/><br /><sub>Fernando Almeida</sub>](https://github.com/fernando-almeida)<br />[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
| [<img src="https://avatars.githubusercontent.com/u/116301219?v=4" width="110px;"/><br /><sub>akemidx</sub>](https://github.com/akemidx)<br />[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [<img src="https://avatars.githubusercontent.com/u/144778?v=4" width="110px;"/><br /><sub>Oguz Bilgic</sub>](http://oguz.site)<br />[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [<img src="https://avatars.githubusercontent.com/u/9262438?v=4" width="110px;"/><br /><sub>Scooter Crawford</sub>](https://github.com/scoo73r)<br />[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [<img src="https://avatars.githubusercontent.com/u/5957345?v=4" width="110px;"/><br /><sub>subdriven</sub>](https://github.com/subdriven)<br />[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END --> <!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View file

@ -148,7 +148,7 @@
"image": "heroku/php", "image": "heroku/php",
"addons": [ "addons": [
"cleardb:ignite", "cleardb:ignite",
"heroku-redis:hobby-dev", "heroku-redis:mini",
"papertrail:choklad" "papertrail:choklad"
] ]
} }

View file

@ -22,7 +22,7 @@ class CheckoutLicenseToAllUsers extends Command
* *
* @var string * @var string
*/ */
protected $description = 'Command description'; protected $description = 'Checks out licenses to all users';
/** /**
* Create a new command instance. * Create a new command instance.

View file

@ -3,6 +3,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Department; use App\Models\Department;
use App\Models\Group;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\Setting; use App\Models\Setting;
use App\Models\Ldap; use App\Models\Ldap;
@ -57,6 +58,7 @@ class LdapSync extends Command
$ldap_result_country = Setting::getSettings()->ldap_country; $ldap_result_country = Setting::getSettings()->ldap_country;
$ldap_result_dept = Setting::getSettings()->ldap_dept; $ldap_result_dept = Setting::getSettings()->ldap_dept;
$ldap_result_manager = Setting::getSettings()->ldap_manager; $ldap_result_manager = Setting::getSettings()->ldap_manager;
$ldap_default_group = Setting::getSettings()->ldap_default_group;
try { try {
$ldapconn = Ldap::connectToLdap(); $ldapconn = Ldap::connectToLdap();
@ -177,6 +179,16 @@ class LdapSync extends Command
$manager_cache = []; $manager_cache = [];
if($ldap_default_group != null) {
$default = Group::find($ldap_default_group);
if (!$default) {
$ldap_default_group = null; // un-set the default group if that group doesn't exist
}
}
for ($i = 0; $i < $results['count']; $i++) { for ($i = 0; $i < $results['count']; $i++) {
$item = []; $item = [];
$item['username'] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : ''; $item['username'] = isset($results[$i][$ldap_result_username][0]) ? $results[$i][$ldap_result_username][0] : '';
@ -192,6 +204,7 @@ class LdapSync extends Command
$item['department'] = isset($results[$i][$ldap_result_dept][0]) ? $results[$i][$ldap_result_dept][0] : ''; $item['department'] = isset($results[$i][$ldap_result_dept][0]) ? $results[$i][$ldap_result_dept][0] : '';
$item['manager'] = isset($results[$i][$ldap_result_manager][0]) ? $results[$i][$ldap_result_manager][0] : ''; $item['manager'] = isset($results[$i][$ldap_result_manager][0]) ? $results[$i][$ldap_result_manager][0] : '';
$department = Department::firstOrCreate([ $department = Department::firstOrCreate([
'name' => $item['department'], 'name' => $item['department'],
]); ]);
@ -326,6 +339,10 @@ class LdapSync extends Command
if ($user->save()) { if ($user->save()) {
$item['note'] = $item['createorupdate']; $item['note'] = $item['createorupdate'];
$item['status'] = 'success'; $item['status'] = 'success';
if ( $item['createorupdate'] === 'created' && $ldap_default_group) {
$user->groups()->attach($ldap_default_group);
}
} else { } else {
foreach ($user->getErrors()->getMessages() as $key => $err) { foreach ($user->getErrors()->getMessages() as $key => $err) {
$errors .= $err[0]; $errors .= $err[0];

View file

@ -214,7 +214,7 @@ class RestoreFromBackup extends Command
$env_vars['MYSQL_PWD'] = config('database.connections.mysql.password'); $env_vars['MYSQL_PWD'] = config('database.connections.mysql.password');
// TODO notes: we are stealing the dump_binary_path (which *probably* also has your copy of the mysql binary in it. But it might not, so we might need to extend this) // TODO notes: we are stealing the dump_binary_path (which *probably* also has your copy of the mysql binary in it. But it might not, so we might need to extend this)
// we unilaterally prepend a slash to the `mysql` command. This might mean your path could look like /blah/blah/blah//mysql - which should be fine. But maybe in some environments it isn't? // we unilaterally prepend a slash to the `mysql` command. This might mean your path could look like /blah/blah/blah//mysql - which should be fine. But maybe in some environments it isn't?
$mysql_binary = config('database.connections.mysql.dump.dump_binary_path').'/mysql'; $mysql_binary = config('database.connections.mysql.dump.dump_binary_path').\DIRECTORY_SEPARATOR.'mysql'.(\DIRECTORY_SEPARATOR == '\\' ? ".exe" : "");
if( ! file_exists($mysql_binary) ) { if( ! file_exists($mysql_binary) ) {
return $this->error("mysql tool at: '$mysql_binary' does not exist, cannot restore. Please edit DB_DUMP_PATH in your .env to point to a directory that contains the mysqldump and mysql binary"); return $this->error("mysql tool at: '$mysql_binary' does not exist, cannot restore. Please edit DB_DUMP_PATH in your .env to point to a directory that contains the mysqldump and mysql binary");
} }

View file

@ -39,33 +39,39 @@ class SyncAssetCounters extends Command
public function handle() public function handle()
{ {
$start = microtime(true); $start = microtime(true);
$assets = Asset::withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')
->withTrashed()->get();
if ($assets) { // We need the whole count of all assets in order to set up the progress bar
$assets_count = Asset::withTrashed()->count();
$bar = $this->output->createProgressBar($assets_count);
$assets = Asset::withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')
->withTrashed()->chunk(100, function ($assets) use ($bar) {
if ($assets->count() > 0) { if ($assets->count() > 0) {
$bar = $this->output->createProgressBar($assets->count());
foreach ($assets as $asset) { foreach ($assets as $asset) {
$asset->checkin_counter = (int) $asset->checkins_count; $asset->checkin_counter = (int) $asset->checkins_count;
$asset->checkout_counter = (int) $asset->checkouts_count; $asset->checkout_counter = (int) $asset->checkouts_count;
$asset->requests_counter = (int) $asset->user_requests_count; $asset->requests_counter = (int) $asset->user_requests_count;
$asset->unsetEventDispatcher(); $asset->unsetEventDispatcher();
$asset->save(); $asset->save();
$output['info'][] = 'Asset: '.$asset->id.' has '.$asset->checkin_counter.' checkins, '.$asset->checkout_counter.' checkouts, and '.$asset->requests_counter.' requests';
$bar->advance(); $bar->advance();
}
$bar->finish();
foreach ($output['info'] as $key => $output_text) { \Log::debug('Asset: '.$asset->id.' has '.$asset->checkin_counter.' checkins, '.$asset->checkout_counter.' checkouts, and '.$asset->requests_counter.' requests');
$this->info($output_text);
} }
$time_elapsed_secs = microtime(true) - $start;
$this->info('Sync executed in '.$time_elapsed_secs.' seconds');
} else { } else {
$this->info('No assets to sync'); $this->info('No assets to sync');
} }
} });
$bar->finish();
$time_elapsed_secs = microtime(true) - $start;
$this->info("\nSync of ".$assets_count.' assets executed in '.$time_elapsed_secs.' seconds');
} }
} }

View file

@ -12,6 +12,7 @@ use App\Models\Statuslabel;
use Crypt; use Crypt;
use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\DecryptException;
use Image; use Image;
use Carbon\Carbon;
class Helper class Helper
{ {
@ -1125,6 +1126,26 @@ class Helper
return $settings; return $settings;
} }
public static function AgeFormat($date) {
$year = Carbon::parse($date)
->diff(now())->y;
$month = Carbon::parse($date)
->diff(now())->m;
$days = Carbon::parse($date)
->diff(now())->d;
$age='';
if ($year) {
$age .= $year.'y ';
}
if ($month) {
$age .= $month.'m ';
}
if ($days) {
$age .= $days.'d';
}
return $age;
}
/** /**
* Conversion between units of measurement * Conversion between units of measurement
@ -1164,5 +1185,4 @@ class Helper
default: throw new \InvalidArgumentException('Unit: \''.$unit.'\' is not supported'); default: throw new \InvalidArgumentException('Unit: \''.$unit.'\' is not supported');
} }
} }
} }

View file

@ -0,0 +1,186 @@
<?php
namespace App\Http\Controllers\Accessories;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
use App\Models\Accessory;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Accessory\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class AccessoriesFilesController extends Controller
{
/**
* Validates and stores files associated with a accessory.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param int $accessoryId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(AssetFileRequest $request, $accessoryId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled'));
}
$accessory = Accessory::find($accessoryId);
if (isset($accessory->id)) {
$this->authorize('accessories.files', $accessory);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/accessories')) {
Storage::makeDirectory('private_uploads/accessories', 775);
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'accessory-'.$accessory->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/accessories/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/accessories/'.$file_name, file_get_contents($file));
}
//Log the upload to the log
$accessory->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('accessories.show', $accessory->id)->with('success', trans('general.file_upload_success'));
}
return redirect()->route('accessories.show', $accessory->id)->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('accessories.index')
->with('error', trans('general.file_does_not_exist'));
}
/**
* Deletes the selected accessory file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $accessoryId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($accessoryId = null, $fileId = null)
{
$accessory = Accessory::find($accessoryId);
// the asset is valid
if (isset($accessory->id)) {
$this->authorize('update', $accessory);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('accessories/'.$log->filename)) {
try {
Storage::delete('accessories/'.$log->filename);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$log->delete();
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the licence management page
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $accessoryId
* @param int $fileId
* @return \Symfony\Accessory\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($accessoryId = null, $fileId = null, $download = true)
{
\Log::debug('Private filesystem is: '.config('filesystems.default'));
$accessory = Accessory::find($accessoryId);
// the accessory is valid
if (isset($accessory->id)) {
$this->authorize('view', $accessory);
$this->authorize('accessories.files', $accessory);
if (! $log = Actionlog::find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
$file = 'private_uploads/accessories/'.$log->filename;
if (Storage::missing($file)) {
\Log::debug('FILE DOES NOT EXISTS for '.$file);
\Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain');
} else {
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
// won't work, as they're not accessible via the web
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
} else {
if ($download != 'true') {
\Log::debug('display the file');
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(['error' => 'Failed validation: '], 500);
}
return StorageHelper::downloader($file);
}
}
}
return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
}
}

View file

@ -100,6 +100,7 @@ class AssetsController extends Controller
'checkout_counter', 'checkout_counter',
'checkin_counter', 'checkin_counter',
'requests_counter', 'requests_counter',
'byod',
]; ];
$filter = []; $filter = [];
@ -120,7 +121,6 @@ class AssetsController extends Controller
if ($filter_non_deprecable_assets) { if ($filter_non_deprecable_assets) {
$non_deprecable_models = AssetModel::select('id')->whereNotNull('depreciation_id')->get(); $non_deprecable_models = AssetModel::select('id')->whereNotNull('depreciation_id')->get();
$assets->InModelList($non_deprecable_models->toArray()); $assets->InModelList($non_deprecable_models->toArray());
} }
@ -141,6 +141,14 @@ class AssetsController extends Controller
$assets->where('assets.status_id', '=', $request->input('status_id')); $assets->where('assets.status_id', '=', $request->input('status_id'));
} }
if ($request->filled('asset_tag')) {
$assets->where('assets.asset_tag', '=', $request->input('asset_tag'));
}
if ($request->filled('serial')) {
$assets->where('assets.serial', '=', $request->input('serial'));
}
if ($request->input('requestable') == 'true') { if ($request->input('requestable') == 'true') {
$assets->where('assets.requestable', '=', '1'); $assets->where('assets.requestable', '=', '1');
} }
@ -182,6 +190,10 @@ class AssetsController extends Controller
$assets->ByDepreciationId($request->input('depreciation_id')); $assets->ByDepreciationId($request->input('depreciation_id'));
} }
if ($request->filled('byod')) {
$assets->where('assets.byod', '=', $request->input('byod'));
}
$request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : ''; $request->filled('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
@ -259,6 +271,11 @@ class AssetsController extends Controller
// more sad, horrible workarounds for laravel bugs when doing full text searches // more sad, horrible workarounds for laravel bugs when doing full text searches
$assets->where('assets.assigned_to', '>', '0'); $assets->where('assets.assigned_to', '>', '0');
break; break;
case 'byod':
// This is kind of redundant, since we already check for byod=1 above, but this keeps the
// sidebar nav links a little less chaotic
$assets->where('assets.byod', '=', '1');
break;
default: default:
if ((! $request->filled('status_id')) && ($settings->show_archived_in_list != '1')) { if ((! $request->filled('status_id')) && ($settings->show_archived_in_list != '1')) {
@ -357,19 +374,38 @@ class AssetsController extends Controller
/** /**
* Returns JSON with information about an asset (by tag) for detail view. * Returns JSON with information about an asset (by tag) for detail view.
* *
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $tag * @param string $tag
* @since [v4.2.1] * @since [v4.2.1]
* @return JsonResponse * @author [A. Gianotto] [<snipe@snipe.net>]
* @return \Illuminate\Http\JsonResponse
*/ */
public function showByTag(Request $request, $tag) public function showByTag(Request $request, $tag)
{ {
if ($asset = Asset::with('assetstatus')->with('assignedTo')->where('asset_tag', $tag)->first()) { $this->authorize('index', Asset::class);
$this->authorize('view', $asset); $assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo');
return (new AssetsTransformer)->transformAsset($asset, $request); // Check if they've passed ?deleted=true
if ($request->input('deleted', 'false') == 'true') {
$assets = $assets->withTrashed();
} }
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
if (($assets = $assets->get()) && ($assets->count()) > 0) {
// If there is exactly one result and the deleted parameter is not passed, we should pull the first (and only)
// asset from the returned collection, since transformAsset() expects an Asset object, NOT a collection
if (($assets->count() == 1) && ($request->input('deleted') != 'true')) {
return (new AssetsTransformer)->transformAsset($assets->first());
// If there is more than one result OR if the endpoint is requesting deleted items (even if there is only one
// match, return the normal collection transformed.
} else {
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
}
// If there are 0 results, return the "no such asset" response
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
} }
@ -379,29 +415,25 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $serial * @param string $serial
* @since [v4.2.1] * @since [v4.2.1]
* @return JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
public function showBySerial(Request $request, $serial) public function showBySerial(Request $request, $serial)
{ {
$this->authorize('index', Asset::class); $this->authorize('index', Asset::class);
if ($assets = Asset::with('assetstatus')->with('assignedTo') $assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo');
->withTrashed()->where('serial', $serial)->get()) {
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
$assets = Asset::with('assetstatus')->with('assignedTo'); // Check if they've passed ?deleted=true
if ($request->input('deleted', 'false') == 'true') {
if ($request->input('deleted', 'false') === 'true') {
$assets = $assets->withTrashed(); $assets = $assets->withTrashed();
} }
$assets = $assets->where('serial', $serial)->get(); if (($assets = $assets->get()) && ($assets->count()) > 0) {
if ($assets) {
return (new AssetsTransformer)->transformAssets($assets, $assets->count()); return (new AssetsTransformer)->transformAssets($assets, $assets->count());
} else {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
} }
// If there are 0 results, return the "no such asset" response
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
} }
/** /**

View file

@ -10,6 +10,7 @@ use App\Models\Category;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class CategoriesController extends Controller class CategoriesController extends Controller
{ {
@ -107,7 +108,7 @@ class CategoriesController extends Controller
public function show($id) public function show($id)
{ {
$this->authorize('view', Category::class); $this->authorize('view', Category::class);
$category = Category::findOrFail($id); $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
return (new CategoriesTransformer)->transformCategory($category); return (new CategoriesTransformer)->transformCategory($category);
} }
@ -126,8 +127,14 @@ class CategoriesController extends Controller
{ {
$this->authorize('update', Category::class); $this->authorize('update', Category::class);
$category = Category::findOrFail($id); $category = Category::findOrFail($id);
// Don't allow the user to change the category_type once it's been created
if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) {
return response()->json(
Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.update.cannot_change_category_type'))
);
}
$category->fill($request->all()); $category->fill($request->all());
$category->category_type = strtolower($request->input('category_type'));
$category = $request->handleImages($category); $category = $request->handleImages($category);
if ($category->save()) { if ($category->save()) {
@ -148,7 +155,7 @@ class CategoriesController extends Controller
public function destroy($id) public function destroy($id)
{ {
$this->authorize('delete', Category::class); $this->authorize('delete', Category::class);
$category = Category::findOrFail($id); $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
if (! $category->isDeletable()) { if (! $category->isDeletable()) {
return response()->json( return response()->json(

View file

@ -228,6 +228,7 @@ class ConsumablesController extends Controller
foreach ($consumable->consumableAssignments as $consumable_assignment) { foreach ($consumable->consumableAssignments as $consumable_assignment) {
$rows[] = [ $rows[] = [
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User', 'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User',
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'), 'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null, 'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,

View file

@ -96,7 +96,7 @@ class CustomFieldsController extends Controller
$data = $request->all(); $data = $request->all();
$regex_format = null; $regex_format = null;
if (str_contains($data['format'], 'regex:')) { if ((array_key_exists('format', $data)) && (str_contains($data['format'], 'regex:'))) {
$regex_format = $data['format']; $regex_format = $data['format'];
} }

View file

@ -127,7 +127,7 @@ class ImportController extends Controller
$this->authorize('import'); $this->authorize('import');
// Run a backup immediately before processing // Run a backup immediately before processing
if ($request->has('run-backup')) { if ($request->get('run-backup')) {
\Log::debug('Backup manually requested via importer'); \Log::debug('Backup manually requested via importer');
Artisan::call('backup:run'); Artisan::call('backup:run');
} else { } else {

View file

@ -15,6 +15,7 @@ use App\Models\Asset;
use App\Models\Company; use App\Models\Company;
use App\Models\License; use App\Models\License;
use App\Models\User; use App\Models\User;
use App\Notifications\CurrentInventory;
use Auth; use Auth;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
@ -148,6 +149,14 @@ class UsersController extends Controller
$users = $users->where('remote', '=', $request->input('remote')); $users = $users->where('remote', '=', $request->input('remote'));
} }
if ($request->filled('two_factor_enrolled')) {
$users = $users->where('two_factor_enrolled', '=', $request->input('two_factor_enrolled'));
}
if ($request->filled('two_factor_optin')) {
$users = $users->where('two_factor_optin', '=', $request->input('two_factor_optin'));
}
if ($request->filled('start_date')) { if ($request->filled('start_date')) {
$users = $users->where('users.start_date', '=', $request->input('start_date')); $users = $users->where('users.start_date', '=', $request->input('start_date'));
} }
@ -156,7 +165,6 @@ class UsersController extends Controller
$users = $users->where('users.end_date', '=', $request->input('end_date')); $users = $users->where('users.end_date', '=', $request->input('end_date'));
} }
if ($request->filled('assets_count')) { if ($request->filled('assets_count')) {
$users->has('assets', '=', $request->input('assets_count')); $users->has('assets', '=', $request->input('assets_count'));
} }
@ -207,11 +215,39 @@ class UsersController extends Controller
default: default:
$allowed_columns = $allowed_columns =
[ [
'last_name', 'first_name', 'email', 'jobtitle', 'username', 'employee_num', 'last_name',
'assets', 'accessories', 'consumables', 'licenses', 'groups', 'activated', 'created_at', 'first_name',
'two_factor_enrolled', 'two_factor_optin', 'last_login', 'assets_count', 'licenses_count', 'email',
'consumables_count', 'accessories_count', 'phone', 'address', 'city', 'state', 'jobtitle',
'country', 'zip', 'id', 'ldap_import', 'remote', 'start_date', 'end_date', 'username',
'employee_num',
'assets',
'accessories',
'consumables',
'licenses',
'groups',
'activated',
'created_at',
'two_factor_enrolled',
'two_factor_optin',
'last_login',
'assets_count',
'licenses_count',
'consumables_count',
'accessories_count',
'phone',
'address',
'city',
'state',
'country',
'zip',
'id',
'ldap_import',
'two_factor_optin',
'two_factor_enrolled',
'remote',
'start_date',
'end_date',
]; ];
$sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name'; $sort = in_array($request->get('sort'), $allowed_columns) ? $request->get('sort') : 'first_name';
@ -490,6 +526,26 @@ class UsersController extends Controller
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request); return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
} }
/**
* Notify a specific user via email with all of their assigned assets.
*
* @author [Lukas Fehling] [<lukas.fehling@adabay.rocks>]
* @since [v6.0.13]
* @param Request $request
* @param $id
* @return string JSON
*/
public function emailAssetList(Request $request, $id)
{
$user = User::findOrFail($id);
if (empty($user->email)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.inventorynotification.error')));
}
return response()->Helper::formatStandardApiResponse('success', null, trans('admin/users/message.inventorynotification.success'));
}
/** /**
* Return JSON containing a list of consumables assigned to a user. * Return JSON containing a list of consumables assigned to a user.

View file

@ -62,7 +62,7 @@ class AssetModelsFilesController extends Controller
$model->logUpload($file_name, e($request->get('notes'))); $model->logUpload($file_name, e($request->get('notes')));
} }
return redirect()->back()->with('success', trans('admin/hardware/message.upload.success')); return redirect()->back()->with('success', trans('general.file_upload_success'));
} }
return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles')); return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles'));

View file

@ -147,6 +147,7 @@ class AssetsController extends Controller
$asset->supplier_id = request('supplier_id', null); $asset->supplier_id = request('supplier_id', null);
$asset->requestable = request('requestable', 0); $asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null); $asset->rtd_location_id = request('rtd_location_id', null);
$asset->byod = request('byod', 0);
if (! empty($settings->audit_interval)) { if (! empty($settings->audit_interval)) {
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); $asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
@ -319,6 +320,7 @@ class AssetsController extends Controller
// If the box isn't checked, it's not in the request at all. // If the box isn't checked, it's not in the request at all.
$asset->requestable = $request->filled('requestable'); $asset->requestable = $request->filled('requestable');
$asset->rtd_location_id = $request->input('rtd_location_id', null); $asset->rtd_location_id = $request->input('rtd_location_id', null);
$asset->byod = $request->input('byod', 0);
if ($asset->assigned_to == '') { if ($asset->assigned_to == '') {
$asset->location_id = $request->input('rtd_location_id', null); $asset->location_id = $request->input('rtd_location_id', null);

View file

@ -135,9 +135,7 @@ class LoginController extends Controller
} else { } else {
// Better logging // Better logging
if (!$saml->isEnabled()) { if (empty($samlData)) {
\Log::debug("SAML page requested, but SAML does not seem to enabled.");
} else {
\Log::debug("SAML page requested, but samlData seems empty."); \Log::debug("SAML page requested, but samlData seems empty.");
} }
} }

View file

@ -41,6 +41,7 @@ class ResetPasswordController extends Controller
public function __construct() public function __construct()
{ {
$this->middleware('guest'); $this->middleware('guest');
$this->middleware('throttle:10,1');
} }
protected function rules() protected function rules()
@ -116,7 +117,7 @@ class ResetPasswordController extends Controller
} }
\Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid'); \Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid');
return redirect()->back()->withInput($request->only('email'))->with('error', trans('passwords.token')); return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset'));
} }

View file

@ -0,0 +1,180 @@
<?php
namespace App\Http\Controllers\Components;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
use App\Models\Component;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class ComponentsFilesController extends Controller
{
/**
* Validates and stores files associated with a component.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param int $componentId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(AssetFileRequest $request, $componentId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('components.show', ['component'=>$componentId])->with('error', trans('general.feature_disabled'));
}
$component = Component::find($componentId);
if (isset($component->id)) {
$this->authorize('update', $component);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/components')) {
Storage::makeDirectory('private_uploads/components', 775);
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'component-'.$component->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/components/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/components/'.$file_name, file_get_contents($file));
}
//Log the upload to the log
$component->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('components.show', $component->id)->with('success', trans('general.file_upload_success'));
}
return redirect()->route('components.show', $component->id)->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('components.index')
->with('error', trans('general.file_does_not_exist'));
}
/**
* Deletes the selected component file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $componentId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($componentId = null, $fileId = null)
{
$component = Component::find($componentId);
// the asset is valid
if (isset($component->id)) {
$this->authorize('update', $component);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('components/'.$log->filename)) {
try {
Storage::delete('components/'.$log->filename);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$log->delete();
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the licence management page
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $componentId
* @param int $fileId
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($componentId = null, $fileId = null, $download = true)
{
\Log::debug('Private filesystem is: '.config('filesystems.default'));
$component = Component::find($componentId);
// the component is valid
if (isset($component->id)) {
$this->authorize('view', $component);
$this->authorize('components.files', $component);
if (! $log = Actionlog::find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
$file = 'private_uploads/components/'.$log->filename;
if (Storage::missing($file)) {
\Log::debug('FILE DOES NOT EXISTS for '.$file);
\Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain');
} else {
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
} else {
if ($download != 'true') {
\Log::debug('display the file');
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(['error' => 'Failed validation: '], 500);
}
return StorageHelper::downloader($file);
}
}
}
return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
}
}

View file

@ -0,0 +1,180 @@
<?php
namespace App\Http\Controllers\Consumables;
use App\Helpers\StorageHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
use App\Models\Consumable;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Consumable\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class ConsumablesFilesController extends Controller
{
/**
* Validates and stores files associated with a consumable.
*
* @todo Switch to using the AssetFileRequest form request validator.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param AssetFileRequest $request
* @param int $consumableId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function store(AssetFileRequest $request, $consumableId = null)
{
if (config('app.lock_passwords')) {
return redirect()->route('consumables.show', ['consumable'=>$consumableId])->with('error', trans('general.feature_disabled'));
}
$consumable = Consumable::find($consumableId);
if (isset($consumable->id)) {
$this->authorize('update', $consumable);
if ($request->hasFile('file')) {
if (! Storage::exists('private_uploads/consumables')) {
Storage::makeDirectory('private_uploads/consumables', 775);
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$file_name = 'consumable-'.$consumable->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/consumables/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/consumables/'.$file_name, file_get_contents($file));
}
//Log the upload to the log
$consumable->logUpload($file_name, e($request->input('notes')));
}
return redirect()->route('consumables.show', $consumable->id)->with('success', trans('general.file_upload_success'));
}
return redirect()->route('consumables.show', $consumable->id)->with('error', trans('general.no_files_uploaded'));
}
// Prepare the error message
return redirect()->route('consumables.index')
->with('error', trans('general.file_does_not_exist'));
}
/**
* Deletes the selected consumable file.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @param int $consumableId
* @param int $fileId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($consumableId = null, $fileId = null)
{
$consumable = Consumable::find($consumableId);
// the asset is valid
if (isset($consumable->id)) {
$this->authorize('update', $consumable);
$log = Actionlog::find($fileId);
// Remove the file if one exists
if (Storage::exists('consumables/'.$log->filename)) {
try {
Storage::delete('consumables/'.$log->filename);
} catch (\Exception $e) {
\Log::debug($e);
}
}
$log->delete();
return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success'));
}
// Redirect to the licence management page
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist'));
}
/**
* Allows the selected file to be viewed.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.4]
* @param int $consumableId
* @param int $fileId
* @return \Symfony\Consumable\HttpFoundation\Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($consumableId = null, $fileId = null, $download = true)
{
$consumable = Consumable::find($consumableId);
// the consumable is valid
if (isset($consumable->id)) {
$this->authorize('view', $consumable);
$this->authorize('consumables.files', $consumable);
if (! $log = Actionlog::find($fileId)) {
return response('No matching record for that asset/file', 500)
->header('Content-Type', 'text/plain');
}
$file = 'private_uploads/consumables/'.$log->filename;
if (Storage::missing($file)) {
\Log::debug('FILE DOES NOT EXISTS for '.$file);
\Log::debug('URL should be '.Storage::url($file));
return response('File '.$file.' ('.Storage::url($file).') not found on server', 404)
->header('Content-Type', 'text/plain');
} else {
// We have to override the URL stuff here, since local defaults in Laravel's Flysystem
// won't work, as they're not accessible via the web
if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer?
return StorageHelper::downloader($file);
} else {
if ($download != 'true') {
\Log::debug('display the file');
if ($contents = file_get_contents(Storage::url($file))) { // TODO - this will fail on private S3 files or large public ones
return Response::make(Storage::url($file)->header('Content-Type', mime_content_type($file)));
}
return JsonResponse::create(['error' => 'Failed validation: '], 500);
}
return StorageHelper::downloader($file);
}
}
}
return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId]));
}
}

View file

@ -86,14 +86,24 @@ class CustomFieldsController extends Controller
{ {
$this->authorize('create', CustomField::class); $this->authorize('create', CustomField::class);
$show_in_email = $request->get("show_in_email", 0);
$display_in_user_view = $request->get("display_in_user_view", 0);
// Override the display settings if the field is encrypted
if ($request->get("field_encrypted") == '1') {
$show_in_email = '0';
$display_in_user_view = '0';
}
$field = new CustomField([ $field = new CustomField([
"name" => trim($request->get("name")), "name" => trim($request->get("name")),
"element" => $request->get("element"), "element" => $request->get("element"),
"help_text" => $request->get("help_text"), "help_text" => $request->get("help_text"),
"field_values" => $request->get("field_values"), "field_values" => $request->get("field_values"),
"field_encrypted" => $request->get("field_encrypted", 0), "field_encrypted" => $request->get("field_encrypted", 0),
"show_in_email" => $request->get("show_in_email", 0), "show_in_email" => $show_in_email,
"is_unique" => $request->get("is_unique", 0), "is_unique" => $request->get("is_unique", 0),
"display_in_user_view" => $display_in_user_view,
"user_id" => Auth::id() "user_id" => Auth::id()
]); ]);
@ -221,13 +231,24 @@ class CustomFieldsController extends Controller
$this->authorize('update', $field); $this->authorize('update', $field);
$show_in_email = $request->get("show_in_email", 0);
$display_in_user_view = $request->get("display_in_user_view", 0);
// Override the display settings if the field is encrypted
if ($request->get("field_encrypted") == '1') {
$show_in_email = '0';
$display_in_user_view = '0';
}
$field->name = trim(e($request->get("name"))); $field->name = trim(e($request->get("name")));
$field->element = e($request->get("element")); $field->element = e($request->get("element"));
$field->field_values = e($request->get("field_values")); $field->field_values = e($request->get("field_values"));
$field->user_id = Auth::id(); $field->user_id = Auth::id();
$field->help_text = $request->get("help_text"); $field->help_text = $request->get("help_text");
$field->show_in_email = $request->get("show_in_email", 0); $field->show_in_email = $show_in_email;
$field->is_unique = $request->get("is_unique", 0); $field->is_unique = $request->get("is_unique", 0);
$field->display_in_user_view = $display_in_user_view;
if ($request->get('format') == 'CUSTOM REGEX') { if ($request->get('format') == 'CUSTOM REGEX') {
$field->format = e($request->get('custom_format')); $field->format = e($request->get('custom_format'));

View file

@ -34,7 +34,7 @@ class DashboardController extends Controller
$counts['license'] = \App\Models\License::assetcount(); $counts['license'] = \App\Models\License::assetcount();
$counts['consumable'] = \App\Models\Consumable::count(); $counts['consumable'] = \App\Models\Consumable::count();
$counts['component'] = \App\Models\Component::count(); $counts['component'] = \App\Models\Component::count();
$counts['user'] = \App\Models\User::count(); $counts['user'] = \App\Models\Company::scopeCompanyables(Auth::user())->count();
$counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable']; $counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable'];
if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) { if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) {

View file

@ -763,7 +763,7 @@ class ReportsController extends Controller
if ($request->filled('username')) { if ($request->filled('username')) {
// Only works if we're checked out to a user, not anything else. // Only works if we're checked out to a user, not anything else.
if ($asset->checkedOutToUser()) { if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedTo) ? $asset->assignedTo->username : ''; $row[] = ($asset->assignedto) ? $asset->assignedto->username : '';
} else { } else {
$row[] = ''; // Empty string if unassigned $row[] = ''; // Empty string if unassigned
} }
@ -772,7 +772,7 @@ class ReportsController extends Controller
if ($request->filled('employee_num')) { if ($request->filled('employee_num')) {
// Only works if we're checked out to a user, not anything else. // Only works if we're checked out to a user, not anything else.
if ($asset->checkedOutToUser()) { if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedTo) ? $asset->assignedTo->employee_num : ''; $row[] = ($asset->assignedto) ? $asset->assignedto->employee_num : '';
} else { } else {
$row[] = ''; // Empty string if unassigned $row[] = ''; // Empty string if unassigned
} }
@ -780,7 +780,7 @@ class ReportsController extends Controller
if ($request->filled('manager')) { if ($request->filled('manager')) {
if ($asset->checkedOutToUser()) { if ($asset->checkedOutToUser()) {
$row[] = (($asset->assignedTo) && ($asset->assignedTo->manager)) ? $asset->assignedTo->manager->present()->fullName : ''; $row[] = (($asset->assignedto) && ($asset->assignedto->manager)) ? $asset->assignedto->manager->present()->fullName : '';
} else { } else {
$row[] = ''; // Empty string if unassigned $row[] = ''; // Empty string if unassigned
} }
@ -788,7 +788,7 @@ class ReportsController extends Controller
if ($request->filled('department')) { if ($request->filled('department')) {
if ($asset->checkedOutToUser()) { if ($asset->checkedOutToUser()) {
$row[] = (($asset->assignedTo) && ($asset->assignedTo->department)) ? $asset->assignedTo->department->name : ''; $row[] = (($asset->assignedto) && ($asset->assignedto->department)) ? $asset->assignedto->department->name : '';
} else { } else {
$row[] = ''; // Empty string if unassigned $row[] = ''; // Empty string if unassigned
} }
@ -796,7 +796,7 @@ class ReportsController extends Controller
if ($request->filled('title')) { if ($request->filled('title')) {
if ($asset->checkedOutToUser()) { if ($asset->checkedOutToUser()) {
$row[] = ($asset->assignedTo) ? $asset->assignedTo->jobtitle : ''; $row[] = ($asset->assignedto) ? $asset->assignedto->jobtitle : '';
} else { } else {
$row[] = ''; // Empty string if unassigned $row[] = ''; // Empty string if unassigned
} }
@ -1110,6 +1110,9 @@ class ReportsController extends Controller
$rows[] = implode(',', $header); $rows[] = implode(',', $header);
foreach ($assetsForReport as $item) { foreach ($assetsForReport as $item) {
if ($item['assetItem'] != null){
$row = [ ]; $row = [ ];
$row[] = str_replace(',', '', e($item['assetItem']->model->category->name)); $row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
$row[] = str_replace(',', '', e($item['assetItem']->model->name)); $row[] = str_replace(',', '', e($item['assetItem']->model->name));
@ -1117,6 +1120,9 @@ class ReportsController extends Controller
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag)); $row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user'))); $row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
$rows[] = implode(',', $row); $rows[] = implode(',', $row);
} else {
// Log the error maybe?
}
} }
// spit out a csv // spit out a csv

View file

@ -7,6 +7,7 @@ use App\Helpers\StorageHelper;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\SettingsSamlRequest; use App\Http\Requests\SettingsSamlRequest;
use App\Http\Requests\SetupUserRequest; use App\Http\Requests\SetupUserRequest;
use App\Models\Group;
use App\Models\Setting; use App\Models\Setting;
use App\Models\Asset; use App\Models\Asset;
use App\Models\User; use App\Models\User;
@ -919,6 +920,8 @@ class SettingsController extends Controller
public function getLdapSettings() public function getLdapSettings()
{ {
$setting = Setting::getSettings(); $setting = Setting::getSettings();
$groups = Group::pluck('name', 'id');
/** /**
* This validator is only temporary (famous last words.) - @snipe * This validator is only temporary (famous last words.) - @snipe
@ -937,7 +940,7 @@ class SettingsController extends Controller
return view('settings.ldap', compact('setting'))->withErrors($validator); return view('settings.ldap', compact('setting', 'groups'))->withErrors($validator);
} }
/** /**
@ -964,6 +967,7 @@ class SettingsController extends Controller
$setting->ldap_pword = Crypt::encrypt($request->input('ldap_pword')); $setting->ldap_pword = Crypt::encrypt($request->input('ldap_pword'));
} }
$setting->ldap_basedn = $request->input('ldap_basedn'); $setting->ldap_basedn = $request->input('ldap_basedn');
$setting->ldap_default_group = $request->input('ldap_default_group');
$setting->ldap_filter = $request->input('ldap_filter'); $setting->ldap_filter = $request->input('ldap_filter');
$setting->ldap_username_field = $request->input('ldap_username_field'); $setting->ldap_username_field = $request->input('ldap_username_field');
$setting->ldap_lname_field = $request->input('ldap_lname_field'); $setting->ldap_lname_field = $request->input('ldap_lname_field');

View file

@ -8,6 +8,7 @@ use App\Models\AssetModel;
use App\Models\Company; use App\Models\Company;
use App\Models\Setting; use App\Models\Setting;
use App\Models\User; use App\Models\User;
use App\Models\CustomField;
use App\Notifications\RequestAssetCancelation; use App\Notifications\RequestAssetCancelation;
use App\Notifications\RequestAssetNotification; use App\Notifications\RequestAssetNotification;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -29,23 +30,41 @@ class ViewAssetsController extends Controller
public function getIndex() public function getIndex()
{ {
$user = User::with( $user = User::with(
'assets',
'assets.model', 'assets.model',
'assets.model.fieldset.fields',
'consumables', 'consumables',
'accessories', 'accessories',
'licenses', 'licenses',
'userloc', )->find(Auth::user()->id);
'userlog'
)->withTrashed()->find(Auth::user()->id);
$userlog = $user->userlog->load('item', 'user', 'target'); $field_array = array();
// Loop through all the custom fields that are applied to any model the user has assigned
foreach ($user->assets as $asset) {
// Make sure the model has a custom fieldset before trying to loop through the associated fields
if ($asset->model->fieldset) {
foreach ($asset->model->fieldset->fields as $field) {
// check and make sure they're allowed to see the value of the custom field
if ($field->display_in_user_view == '1') {
$field_array[$field->db_column] = $field->name;
}
}
}
}
// Since some models may re-use the same fieldsets/fields, let's make the array unique so we don't repeat columns
array_unique($field_array);
if (isset($user->id)) { if (isset($user->id)) {
return view('account/view-assets', compact('user', 'userlog')) return view('account/view-assets', compact('user', 'field_array' ))
->with('settings', Setting::getSettings()); ->with('settings', Setting::getSettings());
} else {
// Redirect to the user management page
return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id')));
} }
// Redirect to the user management page // Redirect to the user management page
return redirect()->route('users.index') return redirect()->route('users.index')
->with('error', trans('admin/users/message.user_not_found', $user->id)); ->with('error', trans('admin/users/message.user_not_found', $user->id));

View file

@ -52,6 +52,13 @@ class AssetCountForSidebar
\Log::debug($e); \Log::debug($e);
} }
try {
$total_byod_sidebar = Asset::where('byod', '=', '1')->count();
view()->share('total_byod_sidebar', $total_byod_sidebar);
} catch (\Exception $e) {
\Log::debug($e);
}
return $next($request); return $next($request);
} }
} }

View file

@ -146,7 +146,7 @@ class ImageUploadRequest extends Request
} }
// Remove Current image if exists // Remove Current image if exists
if (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field})) { if (($item->{$use_db_field}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field}))) {
\Log::debug('A file already exists that we are replacing - we should delete the old one.'); \Log::debug('A file already exists that we are replacing - we should delete the old one.');
try { try {
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field}); Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});

View file

@ -49,7 +49,7 @@ class ItemImportRequest extends FormRequest
$errorMessage = null; $errorMessage = null;
if (is_null($fieldValue)) { if (is_null($fieldValue)) {
$errorMessage = trans('validation.import_field_empty'); $errorMessage = trans('validation.import_field_empty', ['fieldname' => $field]);
$this->errorCallback($import, $field, $errorMessage); $this->errorCallback($import, $field, $errorMessage);
return $this->errors; return $this->errors;

View file

@ -85,6 +85,7 @@ class ActionlogsTransformer
'id' => (int) $actionlog->item->id, 'id' => (int) $actionlog->item->id,
'name' => ($actionlog->itemType()=='user') ? e($actionlog->item->getFullNameAttribute()) : e($actionlog->item->getDisplayNameAttribute()), 'name' => ($actionlog->itemType()=='user') ? e($actionlog->item->getFullNameAttribute()) : e($actionlog->item->getDisplayNameAttribute()),
'type' => e($actionlog->itemType()), 'type' => e($actionlog->itemType()),
'serial' =>e($actionlog->item->serial) ? e($actionlog->item->serial) : null
] : null, ] : null,
'location' => ($actionlog->location) ? [ 'location' => ($actionlog->location) ? [
'id' => (int) $actionlog->location->id, 'id' => (int) $actionlog->location->id,

View file

@ -22,6 +22,22 @@ class AssetModelsTransformer
public function transformAssetModel(AssetModel $assetmodel) public function transformAssetModel(AssetModel $assetmodel)
{ {
$default_field_values = array();
// Reach into the custom fields and models_custom_fields pivot table to find the default values for this model
if ($assetmodel->fieldset) {
foreach($assetmodel->fieldset->fields AS $field) {
$default_field_values[] = [
'name' => e($field->name),
'db_column_name' => e($field->db_column_name()),
'default_value' => ($field->defaultValue($assetmodel->id)) ? e($field->defaultValue($assetmodel->id)) : null,
'format' => e($field->format),
'required' => ($field->pivot->required == '1') ? true : false,
];
}
}
$array = [ $array = [
'id' => (int) $assetmodel->id, 'id' => (int) $assetmodel->id,
'name' => e($assetmodel->name), 'name' => e($assetmodel->name),
@ -44,6 +60,7 @@ class AssetModelsTransformer
'id' => (int) $assetmodel->fieldset->id, 'id' => (int) $assetmodel->fieldset->id,
'name'=> e($assetmodel->fieldset->name), 'name'=> e($assetmodel->fieldset->name),
] : null, ] : null,
'default_fieldset_values' => $default_field_values,
'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None', 'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None',
'requestable' => ($assetmodel->requestable == '1') ? true : false, 'requestable' => ($assetmodel->requestable == '1') ? true : false,
'notes' => e($assetmodel->notes), 'notes' => e($assetmodel->notes),

View file

@ -8,6 +8,7 @@ use App\Models\Setting;
use Gate; use Gate;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class AssetsTransformer class AssetsTransformer
{ {
public function transformAssets(Collection $assets, $total) public function transformAssets(Collection $assets, $total)
@ -34,6 +35,8 @@ class AssetsTransformer
'id' => (int) $asset->model->id, 'id' => (int) $asset->model->id,
'name'=> e($asset->model->name), 'name'=> e($asset->model->name),
] : null, ] : null,
'byod' => ($asset->byod ? true : false),
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'eol' => ($asset->purchase_date != '') ? Helper::getFormattedDateObject($asset->present()->eol_date(), 'date') : null, 'eol' => ($asset->purchase_date != '') ? Helper::getFormattedDateObject($asset->present()->eol_date(), 'date') : null,
'status_label' => ($asset->assetstatus) ? [ 'status_label' => ($asset->assetstatus) ? [
@ -80,6 +83,7 @@ class AssetsTransformer
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'), 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'),
'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'),
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'), 'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
'age' => $asset->purchase_date ? Helper::AgeFormat($asset->purchase_date) : '',
'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'), 'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'),
'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'), 'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost), 'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),
@ -131,7 +135,7 @@ class AssetsTransformer
$array['custom_fields'] = $fields_array; $array['custom_fields'] = $fields_array;
} }
} else { } else {
$array['custom_fields'] = []; $array['custom_fields'] = new \stdClass; // HACK to force generation of empty object instead of empty list
} }
$permissions_array['available_actions'] = [ $permissions_array['available_actions'] = [
@ -185,6 +189,7 @@ class AssetsTransformer
'name' => e($asset->assigned->getFullNameAttribute()), 'name' => e($asset->assigned->getFullNameAttribute()),
'first_name'=> e($asset->assigned->first_name), 'first_name'=> e($asset->assigned->first_name),
'last_name'=> ($asset->assigned->last_name) ? e($asset->assigned->last_name) : null, 'last_name'=> ($asset->assigned->last_name) ? e($asset->assigned->last_name) : null,
'email'=> ($asset->assigned->email) ? e($asset->assigned->email) : null,
'employee_number' => ($asset->assigned->employee_num) ? e($asset->assigned->employee_num) : null, 'employee_number' => ($asset->assigned->employee_num) ? e($asset->assigned->employee_num) : null,
'type' => 'user', 'type' => 'user',
] : null; ] : null;

View file

@ -22,6 +22,26 @@ class CategoriesTransformer
public function transformCategory(Category $category = null) public function transformCategory(Category $category = null)
{ {
// We only ever use item_count for categories in this transformer, so it makes sense to keep it
// simple and do this switch here.
switch ($category->category_type) {
case 'asset':
$category->item_count = $category->assets_count;
break;
case 'accessory':
$category->item_count = $category->accessories_count;
break;
case 'consumable':
$category->item_count = $category->consumables_count;
break;
case 'component':
$category->item_count = $category->components_count;
break;
default:
$category->item_count = 0;
}
if ($category) { if ($category) {
$array = [ $array = [
'id' => (int) $category->id, 'id' => (int) $category->id,
@ -33,7 +53,7 @@ class CategoriesTransformer
'eula' => ($category->getEula()), 'eula' => ($category->getEula()),
'checkin_email' => ($category->checkin_email == '1'), 'checkin_email' => ($category->checkin_email == '1'),
'require_acceptance' => ($category->require_acceptance == '1'), 'require_acceptance' => ($category->require_acceptance == '1'),
'item_count' => (int) $category->itemCount(), 'item_count' => (int) $category->item_count,
'assets_count' => (int) $category->assets_count, 'assets_count' => (int) $category->assets_count,
'accessories_count' => (int) $category->accessories_count, 'accessories_count' => (int) $category->accessories_count,
'consumables_count' => (int) $category->consumables_count, 'consumables_count' => (int) $category->consumables_count,

View file

@ -47,6 +47,7 @@ class CustomFieldsTransformer
'field_values_array' => ($field->field_values) ? explode("\r\n", e($field->field_values)) : null, 'field_values_array' => ($field->field_values) ? explode("\r\n", e($field->field_values)) : null,
'type' => e($field->element), 'type' => e($field->element),
'required' => (($field->pivot) && ($field->pivot->required=='1')) ? true : false, 'required' => (($field->pivot) && ($field->pivot->required=='1')) ? true : false,
'display_in_user_view' => ($field->display_in_user_view =='1') ? true : false,
'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'), 'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'),
]; ];

View file

@ -56,8 +56,8 @@ class UsersTransformer
'permissions' => $user->decodePermissions(), 'permissions' => $user->decodePermissions(),
'activated' => ($user->activated == '1') ? true : false, 'activated' => ($user->activated == '1') ? true : false,
'ldap_import' => ($user->ldap_import == '1') ? true : false, 'ldap_import' => ($user->ldap_import == '1') ? true : false,
'two_factor_activated' => ($user->two_factor_active()) ? true : false,
'two_factor_enrolled' => ($user->two_factor_active_and_enrolled()) ? true : false, 'two_factor_enrolled' => ($user->two_factor_active_and_enrolled()) ? true : false,
'two_factor_optin' => ($user->two_factor_active()) ? true : false,
'assets_count' => (int) $user->assets_count, 'assets_count' => (int) $user->assets_count,
'licenses_count' => (int) $user->licenses_count, 'licenses_count' => (int) $user->licenses_count,
'accessories_count' => (int) $user->accessories_count, 'accessories_count' => (int) $user->accessories_count,

View file

@ -77,6 +77,7 @@ abstract class Importer
'manager_first_name' => 'manager first name', 'manager_first_name' => 'manager first name',
'manager_last_name' => 'manager last name', 'manager_last_name' => 'manager last name',
'min_amt' => 'minimum quantity', 'min_amt' => 'minimum quantity',
'remote' => 'remote',
]; ];
/** /**
* Map of item fields->csv names * Map of item fields->csv names
@ -288,6 +289,7 @@ abstract class Importer
'department_id' => '', 'department_id' => '',
'username' => $this->findCsvMatch($row, 'username'), 'username' => $this->findCsvMatch($row, 'username'),
'activated' => $this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')), 'activated' => $this->fetchHumanBoolean($this->findCsvMatch($row, 'activated')),
'remote' => $this->fetchHumanBoolean(($this->findCsvMatch($row, 'remote'))),
]; ];
// Maybe we're lucky and the user already exists. // Maybe we're lucky and the user already exists.

View file

@ -57,6 +57,7 @@ class UserImporter extends ItemImporter
$this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num'); $this->item['employee_num'] = $this->findCsvMatch($row, 'employee_num');
$this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department')); $this->item['department_id'] = $this->createOrFetchDepartment($this->findCsvMatch($row, 'department'));
$this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name')); $this->item['manager_id'] = $this->fetchManager($this->findCsvMatch($row, 'manager_first_name'), $this->findCsvMatch($row, 'manager_last_name'));
$this->item['remote'] =($this->fetchHumanBoolean($this->findCsvMatch($row, 'remote')) ==1 ) ? '1' : 0;
$user_department = $this->findCsvMatch($row, 'department'); $user_department = $this->findCsvMatch($row, 'department');
if ($this->shouldUpdateField($user_department)) { if ($this->shouldUpdateField($user_department)) {

View file

@ -101,6 +101,23 @@ class Accessory extends SnipeModel
/**
* Establishes the accessories -> action logs -> uploads relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.1.13]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/** /**
* Establishes the accessory -> supplier relationship * Establishes the accessory -> supplier relationship
* *

View file

@ -95,6 +95,7 @@ class Asset extends Depreciable
'location_id' => 'integer', 'location_id' => 'integer',
'rtd_company_id' => 'integer', 'rtd_company_id' => 'integer',
'supplier_id' => 'integer', 'supplier_id' => 'integer',
'byod' => 'boolean',
]; ];
protected $rules = [ protected $rules = [
@ -106,7 +107,6 @@ class Asset extends Depreciable
'physical' => 'numeric|max:1|nullable', 'physical' => 'numeric|max:1|nullable',
'checkout_date' => 'date|max:10|min:10|nullable', 'checkout_date' => 'date|max:10|min:10|nullable',
'checkin_date' => 'date|max:10|min:10|nullable', 'checkin_date' => 'date|max:10|min:10|nullable',
'supplier_id' => 'exists:suppliers,id|numeric|nullable',
'location_id' => 'exists:locations,id|nullable', 'location_id' => 'exists:locations,id|nullable',
'rtd_location_id' => 'exists:locations,id|nullable', 'rtd_location_id' => 'exists:locations,id|nullable',
'asset_tag' => 'required|min:1|max:255|unique_undeleted', 'asset_tag' => 'required|min:1|max:255|unique_undeleted',
@ -144,6 +144,7 @@ class Asset extends Depreciable
'requestable', 'requestable',
'last_checkout', 'last_checkout',
'expected_checkin', 'expected_checkin',
'byod',
]; ];
use Searchable; use Searchable;
@ -315,14 +316,10 @@ class Asset extends Depreciable
} }
$this->last_checkout = $checkout_at; $this->last_checkout = $checkout_at;
$this->name = $name;
$this->assignedTo()->associate($target); $this->assignedTo()->associate($target);
if ($name != null) {
$this->name = $name;
}
if ($location != null) { if ($location != null) {
$this->location_id = $location; $this->location_id = $location;
} else { } else {

View file

@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use Watson\Validating\ValidatingTrait; use Watson\Validating\ValidatingTrait;
use App\Helpers\Helper; use App\Helpers\Helper;
use Illuminate\Support\Str;
/** /**
* Model for Categories. Categories are a higher-level group * Model for Categories. Categories are a higher-level group
@ -97,6 +98,7 @@ class Category extends SnipeModel
*/ */
public function isDeletable() public function isDeletable()
{ {
return Gate::allows('delete', $this) return Gate::allows('delete', $this)
&& ($this->itemCount() == 0); && ($this->itemCount() == 0);
} }
@ -150,7 +152,10 @@ class Category extends SnipeModel
} }
/** /**
* Get the number of items in the category * Get the number of items in the category. This should NEVER be used in
* a collection of categories, as you'll end up with an n+1 query problem.
*
* It should only be used in a single category context.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v2.0] * @since [v2.0]
@ -158,6 +163,11 @@ class Category extends SnipeModel
*/ */
public function itemCount() public function itemCount()
{ {
if (isset($this->{Str::plural($this->category_type).'_count'})) {
return $this->{Str::plural($this->category_type).'_count'};
}
switch ($this->category_type) { switch ($this->category_type) {
case 'asset': case 'asset':
return $this->assets()->count(); return $this->assets()->count();
@ -169,9 +179,10 @@ class Category extends SnipeModel
return $this->consumables()->count(); return $this->consumables()->count();
case 'license': case 'license':
return $this->licenses()->count(); return $this->licenses()->count();
default:
return 0;
} }
return '0';
} }
/** /**

View file

@ -21,7 +21,7 @@ class CheckoutAcceptance extends Model
{ {
// At this point the endpoint is the same for everything. // At this point the endpoint is the same for everything.
// In the future this may want to be adapted for individual notifications. // In the future this may want to be adapted for individual notifications.
return config('mail.reply_to.address'); return (config('mail.reply_to.address')) ? config('mail.reply_to.address') : '' ;
} }
/** /**

View file

@ -73,6 +73,10 @@ final class Company extends SnipeModel
} }
} }
/**
* Scoping table queries, determining if a logged in user is part of a company, and only allows
* that user to see items associated with that company
*/
private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null) private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null)
{ {
if (Auth::user()) { if (Auth::user()) {
@ -127,6 +131,11 @@ final class Company extends SnipeModel
return false; return false;
} elseif (! static::isFullMultipleCompanySupportEnabled()) { } elseif (! static::isFullMultipleCompanySupportEnabled()) {
return true; return true;
} elseif (!$companyable instanceof Company && !\Schema::hasColumn($companyable->getModel()->getTable(), 'company_id')) {
// This is primary for the gate:allows-check in location->isDeletable()
// Locations don't have a company_id so without this it isn't possible to delete locations with FullMultipleCompanySupport enabled
// because this function is called by SnipePermissionsPolicy->before()
return true;
} else { } else {
if (Auth::user()) { if (Auth::user()) {
$current_user_company_id = Auth::user()->company_id; $current_user_company_id = Auth::user()->company_id;

View file

@ -88,6 +88,24 @@ class Component extends SnipeModel
'location' => ['name'], 'location' => ['name'],
]; ];
/**
* Establishes the components -> action logs -> uploads relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.1.13]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/** /**
* Establishes the component -> location relationship * Establishes the component -> location relationship
* *

View file

@ -96,6 +96,24 @@ class Consumable extends SnipeModel
'manufacturer' => ['name'], 'manufacturer' => ['name'],
]; ];
/**
* Establishes the components -> action logs -> uploads relationship
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v6.1.13]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function uploads()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
}
/** /**
* Sets the attribute of whether or not the consumable is requestable * Sets the attribute of whether or not the consumable is requestable
* *

View file

@ -48,7 +48,11 @@ class CustomField extends Model
* *
* @var array * @var array
*/ */
protected $rules = []; protected $rules = [
'name' => 'required|unique:custom_fields',
'element' => 'required|in:text,listbox,textarea,checkbox,radio',
'field_encrypted' => 'nullable|boolean',
];
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -64,6 +68,7 @@ class CustomField extends Model
'help_text', 'help_text',
'show_in_email', 'show_in_email',
'is_unique', 'is_unique',
'display_in_user_view',
]; ];
/** /**
@ -356,15 +361,9 @@ class CustomField extends Model
public function validationRules($regex_format = null) public function validationRules($regex_format = null)
{ {
return [ return [
'name' => 'required|unique:custom_fields',
'element' => [
'required',
Rule::in(['text', 'listbox', 'textarea', 'checkbox', 'radio']),
],
'format' => [ 'format' => [
Rule::in(array_merge(array_keys(self::PREDEFINED_FORMATS), self::PREDEFINED_FORMATS, [$regex_format])), Rule::in(array_merge(array_keys(self::PREDEFINED_FORMATS), self::PREDEFINED_FORMATS, [$regex_format])),
], ]
'field_encrypted' => 'nullable|boolean',
]; ];
} }

View file

@ -29,7 +29,7 @@ class Department extends SnipeModel
]; ];
protected $rules = [ protected $rules = [
'name' => 'required|max:255', 'name' => 'required|max:255|is_unique_department',
'location_id' => 'numeric|nullable', 'location_id' => 'numeric|nullable',
'company_id' => 'numeric|nullable', 'company_id' => 'numeric|nullable',
'manager_id' => 'numeric|nullable', 'manager_id' => 'numeric|nullable',

View file

@ -123,12 +123,20 @@ class License extends Depreciable
static::created(function ($license) { static::created(function ($license) {
$newSeatCount = $license->getAttributes()['seats']; $newSeatCount = $license->getAttributes()['seats'];
return static::adjustSeatCount($license, $oldSeatCount = 0, $newSeatCount); return static::adjustSeatCount($license, 0, $newSeatCount);
}); });
// However, we listen for updating to be able to prevent the edit if we cannot delete enough seats. // However, we listen for updating to be able to prevent the edit if we cannot delete enough seats.
static::updating(function ($license) { static::updating(function ($license) {
$newSeatCount = $license->getAttributes()['seats']; $newSeatCount = $license->getAttributes()['seats'];
$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0; //$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0;
/*
That previous method *did* mostly work, but if you ever managed to get your $license->seats value out of whack
with your actual count of license_seats *records*, you would never manage to get back 'into whack'.
The below method actually grabs a count of existing license_seats records, so it will be more accurate.
This means that if your license_seats are out of whack, you can change the quantity and hit 'save' and it
will manage to 'true up' and make your counts line up correctly.
*/
$oldSeatCount = $license->license_seats_count;
return static::adjustSeatCount($license, $oldSeatCount, $newSeatCount); return static::adjustSeatCount($license, $oldSeatCount, $newSeatCount);
}); });

View file

@ -21,11 +21,10 @@ class Setting extends Model
use Notifiable, ValidatingTrait; use Notifiable, ValidatingTrait;
/** /**
* The app settings cache key name. * The cache property so that multiple invocations of this will only load the Settings record from disk only once
* * @var self
* @var string
*/ */
const APP_SETTINGS_KEY = 'snipeit_app_settings'; public static ?self $_cache = null;
/** /**
* The setup check cache key name. * The setup check cache key name.
@ -98,14 +97,15 @@ class Setting extends Model
*/ */
public static function getSettings(): ?self public static function getSettings(): ?self
{ {
return Cache::rememberForever(self::APP_SETTINGS_KEY, function () { if (!self::$_cache) {
// Need for setup as no tables exist // Need for setup as no tables exist
try { try {
return self::first(); self::$_cache = self::first();
} catch (\Throwable $th) { } catch (\Throwable $th) {
return null; return null;
} }
}); }
return self::$_cache;
} }
/** /**

View file

@ -41,8 +41,10 @@ class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
} }
); );
$config['validations'][$core.'externalId'] = 'string'; // not required, but supported mostly just for Okta // externalId support
$mappings['externalId'] = AttributeMapping::eloquent('scim_externalid'); $config['validations'][$core.'externalId'] = 'string|nullable'; // not required, but supported mostly just for Okta
// note that the mapping is *not* namespaced like the other $mappings
$config['mapping']['externalId'] = AttributeMapping::eloquent('scim_externalid');
$config['validations'][$core.'emails'] = 'nullable|array'; // emails are not required in Snipe-IT... $config['validations'][$core.'emails'] = 'nullable|array'; // emails are not required in Snipe-IT...
$config['validations'][$core.'emails.*.value'] = 'email'; // ...(had to remove the recommended 'required' here) $config['validations'][$core.'emails.*.value'] = 'email'; // ...(had to remove the recommended 'required' here)

View file

@ -61,6 +61,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
'remote', 'remote',
'start_date', 'start_date',
'end_date', 'end_date',
'scim_externalid'
]; ];
protected $casts = [ protected $casts = [
@ -337,6 +338,24 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id'); return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id');
} }
/**
* Establishes a count of all items assigned
*
* @author J. Vinsmoke
* @since [v6.1]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
Public function allAssignedCount() {
$assetsCount = $this->assets()->count();
$licensesCount = $this->licenses()->count();
$accessoriesCount = $this->accessories()->count();
$consumablesCount = $this->consumables()->count();
$totalCount = $assetsCount + $licensesCount + $accessoriesCount + $consumablesCount;
return (int) $totalCount;
}
/** /**
* Establishes the user -> actionlogs relationship * Establishes the user -> actionlogs relationship
* *
@ -567,6 +586,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
if ((Setting::getSettings()->two_factor_enabled == '1') && ($this->two_factor_optin == '1')) { if ((Setting::getSettings()->two_factor_enabled == '1') && ($this->two_factor_optin == '1')) {
return true; return true;
} }
// If the 2FA is required for everyone so is implicitly active // If the 2FA is required for everyone so is implicitly active
elseif (Setting::getSettings()->two_factor_enabled == '2') { elseif (Setting::getSettings()->two_factor_enabled == '2') {
return true; return true;
@ -575,18 +595,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return false; return false;
} }
/**
* Get the admin user who created this user
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.0.5]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function createdBy()
{
return $this->belongsTo(\App\Models\User::class, 'created_by')->withTrashed();
}
/** /**
* Check whether two-factor authorization is required and the user has activated it * Check whether two-factor authorization is required and the user has activated it
* and enrolled a device * and enrolled a device
@ -615,6 +623,19 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
} }
/**
* Get the admin user who created this user
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v6.0.5]
* @return \Illuminate\Database\Eloquent\Relations\Relation
*/
public function createdBy()
{
return $this->belongsTo(\App\Models\User::class, 'created_by')->withTrashed();
}
public function decodePermissions() public function decodePermissions()
{ {

View file

@ -19,11 +19,11 @@ class WelcomeNotification extends Notification
*/ */
public function __construct(array $content) public function __construct(array $content)
{ {
$this->_data['email'] = $content['email']; $this->_data['email'] = htmlspecialchars_decode($content['email']);
$this->_data['first_name'] = $content['first_name']; $this->_data['first_name'] = htmlspecialchars_decode($content['first_name']);
$this->_data['last_name'] = $content['last_name']; $this->_data['last_name'] = htmlspecialchars_decode($content['last_name']);
$this->_data['username'] = $content['username']; $this->_data['username'] = htmlspecialchars_decode($content['username']);
$this->_data['password'] = $content['password']; $this->_data['password'] = htmlspecialchars_decode($content['password']);
$this->_data['url'] = url('/'); $this->_data['url'] = url('/');
} }

View file

@ -16,7 +16,6 @@ class SettingObserver
*/ */
public function saved(Setting $setting) public function saved(Setting $setting)
{ {
Cache::forget(Setting::APP_SETTINGS_KEY);
Cache::forget(Setting::SETUP_CHECK_KEY); Cache::forget(Setting::SETUP_CHECK_KEY);
} }
} }

View file

@ -88,7 +88,7 @@ class AssetModelPresenter extends Presenter
'sortable' => true, 'sortable' => true,
'switchable' => true, 'switchable' => true,
'title' => trans('general.category'), 'title' => trans('general.category'),
'visible' => false, 'visible' => true,
'formatter' => 'categoriesLinkObjFormatter', 'formatter' => 'categoriesLinkObjFormatter',
], ],
[ [

View file

@ -140,6 +140,12 @@ class AssetPresenter extends Presenter
'visible' => false, 'visible' => false,
'title' => trans('general.purchase_date'), 'title' => trans('general.purchase_date'),
'formatter' => 'dateDisplayFormatter', 'formatter' => 'dateDisplayFormatter',
], [
'field' => 'age',
'searchable' => true,
'sortable' => true,
'visible' => false,
'title' => trans('general.age'),
], [ ], [
'field' => 'purchase_cost', 'field' => 'purchase_cost',
'searchable' => true, 'searchable' => true,
@ -244,6 +250,14 @@ class AssetPresenter extends Presenter
'visible' => false, 'visible' => false,
'title' => trans('general.next_audit_date'), 'title' => trans('general.next_audit_date'),
'formatter' => 'dateDisplayFormatter', 'formatter' => 'dateDisplayFormatter',
], [
'field' => 'byod',
'searchable' => false,
'sortable' => true,
'visible' => false,
'title' => trans('general.byod'),
'formatter' => 'trueFalseFormatter',
], ],
]; ];

View file

@ -70,47 +70,33 @@ class CategoryPresenter extends Presenter
'visible' => true, 'visible' => true,
'formatter' => 'trueFalseFormatter', 'formatter' => 'trueFalseFormatter',
], [ ], [
'field' => 'actions',
'searchable' => false,
'sortable' => false,
'switchable' => false,
'title' => trans('table.actions'),
],[
"field" => "use_default_eula", "field" => "use_default_eula",
"searchable" => false, "searchable" => false,
"sortable" => true, "sortable" => true,
"title" => trans('admin/categories/general.use_default_eula_column'), "title" => trans('admin/categories/general.use_default_eula_column'),
'visible' => true, 'visible' => true,
"formatter" => 'trueFalseFormatter', "formatter" => 'trueFalseFormatter',
],[ ], [
"field" => "checkin_email",
"searchable" => false,
"sortable" => true,
"class" => 'css-envelope',
"title" => 'Send Email',
"visible" => true,
"formatter" => 'trueFalseFormatter',
],[
"field" => "require_acceptance",
"searchable" => false,
"sortable" => true,
'formatter' => 'categoriesActionsFormatter',
],
[
'field' => 'created_at', 'field' => 'created_at',
'searchable' => true, 'searchable' => true,
'sortable' => true, 'sortable' => true,
'visible' => false, 'visible' => false,
'title' => trans('general.created_at'), 'title' => trans('general.created_at'),
'formatter' => 'dateDisplayFormatter', 'formatter' => 'dateDisplayFormatter',
], ], [
[
'field' => 'updated_at', 'field' => 'updated_at',
'searchable' => true, 'searchable' => true,
'sortable' => true, 'sortable' => true,
'visible' => false, 'visible' => false,
'title' => trans('general.updated_at'), 'title' => trans('general.updated_at'),
'formatter' => 'dateDisplayFormatter', 'formatter' => 'dateDisplayFormatter',
], [
'field' => 'actions',
'searchable' => false,
'sortable' => false,
'switchable' => false,
'title' => trans('table.actions'),
'formatter' => 'categoriesActionsFormatter',
], ],
]; ];

View file

@ -268,9 +268,9 @@ class UserPresenter extends Presenter
'formatter' => 'trueFalseFormatter', 'formatter' => 'trueFalseFormatter',
], ],
[ [
'field' => 'two_factor_activated', 'field' => 'two_factor_optin',
'searchable' => false, 'searchable' => false,
'sortable' => false, 'sortable' => true,
'switchable' => true, 'switchable' => true,
'title' => trans('admin/users/general.two_factor_active'), 'title' => trans('admin/users/general.two_factor_active'),
'visible' => false, 'visible' => false,

View file

@ -114,6 +114,24 @@ class AuthServiceProvider extends ServiceProvider
} }
}); });
Gate::define('accessories.files', function ($user) {
if ($user->hasAccess('accessories.files')) {
return true;
}
});
Gate::define('components.files', function ($user) {
if ($user->hasAccess('components.files')) {
return true;
}
});
Gate::define('consumables.files', function ($user) {
if ($user->hasAccess('consumables.files')) {
return true;
}
});
// Can the user import CSVs? // Can the user import CSVs?
Gate::define('import', function ($user) { Gate::define('import', function ($user) {
if ($user->hasAccess('import')) { if ($user->hasAccess('import')) {
@ -159,6 +177,10 @@ class AuthServiceProvider extends ServiceProvider
return $user->hasAccess('self.checkout_assets'); return $user->hasAccess('self.checkout_assets');
}); });
Gate::define('self.view_purchase_cost', function ($user) {
return $user->hasAccess('self.view_purchase_cost');
});
// This is largely used to determine whether to display the gear icon sidenav // This is largely used to determine whether to display the gear icon sidenav
// in the left-side navigation // in the left-side navigation
Gate::define('backend.interact', function ($user) { Gate::define('backend.interact', function ($user) {

View file

@ -2,8 +2,10 @@
namespace App\Providers; namespace App\Providers;
use App\Models\Department;
use DB; use DB;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rule;
use Validator; use Validator;
/** /**
@ -213,6 +215,23 @@ class ValidationServiceProvider extends ServiceProvider
return true; return true;
} }
}); });
Validator::extend('is_unique_department', function ($attribute, $value, $parameters, $validator) {
$data = $validator->getData();
if ((array_key_exists('location_id', $data) && $data['location_id'] != null) && (array_key_exists('company_id', $data) && $data['company_id'] != null)) {
$count = Department::where('name', $data['name'])
->where('location_id', $data['location_id'])
->where('company_id', $data['company_id'])
->whereNotNull('company_id')
->whereNotNull('location_id')
->count('name');
return $count < 1;
}
else {
return true;
}
});
} }
/** /**

16
composer.lock generated
View file

@ -78,19 +78,19 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/grokability/laravel-scim-server.git", "url": "https://github.com/grokability/laravel-scim-server.git",
"reference": "9e7a8fd51a7380bc18ca1f01256574d75a2c6b49" "reference": "2c7ecc450eee59234e059ec2e7724b2d8f3a8369"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/9e7a8fd51a7380bc18ca1f01256574d75a2c6b49", "url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/2c7ecc450eee59234e059ec2e7724b2d8f3a8369",
"reference": "9e7a8fd51a7380bc18ca1f01256574d75a2c6b49", "reference": "2c7ecc450eee59234e059ec2e7724b2d8f3a8369",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"illuminate/console": "^6.0|^7.0|^8.0", "illuminate/console": "^6.0|^7.0|^8.0|^9.0",
"illuminate/database": "^6.0|^7.0|^8.0", "illuminate/database": "^6.0|^7.0|^8.0|^9.0",
"illuminate/support": "^6.0|^7.0|^8.0", "illuminate/support": "^6.0|^7.0|^8.0|^9.0",
"php": "^7.4|^8.0", "php": "^7.0|^8.0",
"tmilos/scim-filter-parser": "^1.3", "tmilos/scim-filter-parser": "^1.3",
"tmilos/scim-schema": "^0.1.0" "tmilos/scim-schema": "^0.1.0"
}, },
@ -133,7 +133,7 @@
"support": { "support": {
"source": "https://github.com/grokability/laravel-scim-server/tree/master" "source": "https://github.com/grokability/laravel-scim-server/tree/master"
}, },
"time": "2022-10-06T00:42:37+00:00" "time": "2022-11-22T20:26:54+00:00"
}, },
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",

View file

@ -4,7 +4,7 @@ use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler; use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler; use Monolog\Handler\SyslogUdpHandler;
return [ $config = [
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -35,6 +35,8 @@ return [
*/ */
'channels' => [ 'channels' => [
// This will get overwritten to 'single' AND 'rollbar' in the code at the bottom of this file
// if a ROLLBAR_TOKEN is given in the .env file
'stack' => [ 'stack' => [
'driver' => 'stack', 'driver' => 'stack',
'channels' => ['single'], 'channels' => ['single'],
@ -104,7 +106,23 @@ return [
'scimtrace' => [ 'scimtrace' => [
'driver' => 'single', 'driver' => 'single',
'path' => storage_path('logs/scim.log') 'path' => storage_path('logs/scim.log')
] ],
'rollbar' => [
'driver' => 'monolog',
'handler' => \Rollbar\Laravel\MonologHandler::class,
'access_token' => env('ROLLBAR_TOKEN'),
'level' => env('ROLLBAR_LEVEL', 'error'),
],
], ],
]; ];
// Only add rollbar if the .env has a rollbar token
if ((env('APP_ENV')=='production') && (env('ROLLBAR_TOKEN'))) {
$config['channels']['stack']['channels'] = ['single', 'rollbar'];
}
return $config;

View file

@ -145,6 +145,13 @@ return [
'note' => '', 'note' => '',
'display' => true, 'display' => true,
], ],
[
'permission' => 'accessories.files',
'label' => 'View and Modify Accessory Files',
'note' => '',
'display' => true,
],
], ],
'Consumables' => [ 'Consumables' => [
@ -178,6 +185,12 @@ return [
'note' => '', 'note' => '',
'display' => true, 'display' => true,
], ],
[
'permission' => 'consumables.files',
'label' => 'View and Modify Consumable Files',
'note' => '',
'display' => true,
],
], ],
@ -264,6 +277,12 @@ return [
'note' => '', 'note' => '',
'display' => true, 'display' => true,
], ],
[
'permission' => 'components.files',
'label' => 'View and Modify Component Files',
'note' => '',
'display' => true,
],
], ],
@ -626,6 +645,13 @@ return [
'display' => true, 'display' => true,
], ],
[
'permission' => 'self.view_purchase_cost',
'label' => 'View Purchase-Cost Column',
'note' => 'This user can see the purchase cost column of items assigned to them.',
'display' => true,
],
], ],
]; ];

View file

@ -1,6 +1,8 @@
<?php <?php
return [ return [
"trace" => env("SCIM_TRACE",false),
// below, if we ever get 'sure' that we can change this default to 'true' we should
"omit_main_schema_in_return" => env('SCIM_STANDARDS_COMPLIANCE', false),
"publish_routes" => false, "publish_routes" => false,
"trace" => env("SCIM_TRACE",false)
]; ];

View file

@ -1,10 +1,10 @@
<?php <?php
return array ( return array (
'app_version' => 'v6.0.12', 'app_version' => 'v6.0.14',
'full_app_version' => 'v6.0.12 - build 8876-gbc4c6abe0', 'full_app_version' => 'v6.0.14 - build 9161-g799c9c910',
'build_version' => '8876', 'build_version' => '9161',
'prerelease_version' => '', 'prerelease_version' => '',
'hash_version' => 'gbc4c6abe0', 'hash_version' => 'g799c9c910',
'full_hash' => 'v6.0.12-50-gbc4c6abe0', 'full_hash' => 'v6.0.14-117-g799c9c910',
'branch' => 'develop', 'branch' => 'master',
); );

View file

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddsLdapDefaultGroupToSettingsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->integer('ldap_default_group')
->after('ldap_basedn')->default(null);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn('ldap_default_group');
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDisplayToUserInCustomFields extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('custom_fields', function (Blueprint $table) {
$table->boolean('display_in_user_view')->nullable()->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('custom_fields', function (Blueprint $table) {
$table->dropColumn('display_in_user_view');
});
}
}

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class FixNullableMigrationForSettings extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->string('ldap_default_group')->nullable()->default(null)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// nothing to do here - this is a hotfix
}
}

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddByodToAssets extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('assets', function (Blueprint $table) {
$table->boolean('byod')->nullable()->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('assets', function (Blueprint $table) {
if (Schema::hasColumn('assets', 'byod')) {
$table->dropColumn('byod');
}
});
}
}

360
package-lock.json generated
View file

@ -1329,9 +1329,9 @@
"dev": true "dev": true
}, },
"@fortawesome/fontawesome-free": { "@fortawesome/fontawesome-free": {
"version": "6.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.1.tgz",
"integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ==" "integrity": "sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A=="
}, },
"@jridgewell/gen-mapping": { "@jridgewell/gen-mapping": {
"version": "0.1.1", "version": "0.1.1",
@ -3588,9 +3588,9 @@
} }
}, },
"camelcase": { "camelcase": {
"version": "5.3.1", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
}, },
"caniuse-api": { "caniuse-api": {
"version": "3.0.0", "version": "3.0.0",
@ -4244,75 +4244,60 @@
} }
}, },
"css-loader": { "css-loader": {
"version": "3.6.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz",
"integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==",
"requires": { "requires": {
"camelcase": "^5.3.1", "camelcase": "^6.0.0",
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"icss-utils": "^4.1.1", "icss-utils": "^4.1.1",
"loader-utils": "^1.2.3", "loader-utils": "^2.0.0",
"normalize-path": "^3.0.0",
"postcss": "^7.0.32", "postcss": "^7.0.32",
"postcss-modules-extract-imports": "^2.0.0", "postcss-modules-extract-imports": "^2.0.0",
"postcss-modules-local-by-default": "^3.0.2", "postcss-modules-local-by-default": "^3.0.3",
"postcss-modules-scope": "^2.2.0", "postcss-modules-scope": "^2.2.0",
"postcss-modules-values": "^3.0.0", "postcss-modules-values": "^3.0.0",
"postcss-value-parser": "^4.1.0", "postcss-value-parser": "^4.1.0",
"schema-utils": "^2.7.0", "schema-utils": "^2.7.1",
"semver": "^6.3.0" "semver": "^7.3.2"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "loader-utils": {
"version": "3.2.1", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"requires": { "requires": {
"color-convert": "^1.9.0" "big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
} }
}, },
"chalk": { "picocolors": {
"version": "2.4.2", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
}, },
"postcss": { "postcss": {
"version": "7.0.36", "version": "7.0.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"requires": { "requires": {
"chalk": "^2.4.2", "picocolors": "^0.2.1",
"source-map": "^0.6.1", "source-map": "^0.6.1"
"supports-color": "^6.1.0" }
},
"semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"requires": {
"lru-cache": "^6.0.0"
} }
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"requires": {
"has-flag": "^3.0.0"
}
} }
} }
}, },
@ -4936,7 +4921,8 @@
"escape-string-regexp": { "escape-string-regexp": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
}, },
"escodegen": { "escodegen": {
"version": "1.14.3", "version": "1.14.3",
@ -15028,7 +15014,8 @@
"has-flag": { "has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
}, },
"has-symbols": { "has-symbols": {
"version": "1.0.1", "version": "1.0.1",
@ -15350,56 +15337,24 @@
"postcss": "^7.0.14" "postcss": "^7.0.14"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "picocolors": {
"version": "3.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
}, },
"postcss": { "postcss": {
"version": "7.0.36", "version": "7.0.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"requires": { "requires": {
"chalk": "^2.4.2", "picocolors": "^0.2.1",
"source-map": "^0.6.1", "source-map": "^0.6.1"
"supports-color": "^6.1.0"
} }
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"requires": {
"has-flag": "^3.0.0"
}
} }
} }
}, },
@ -15867,8 +15822,7 @@
"json5": { "json5": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
"dev": true
}, },
"jsonfile": { "jsonfile": {
"version": "6.1.0", "version": "6.1.0",
@ -16453,7 +16407,6 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": { "requires": {
"yallist": "^4.0.0" "yallist": "^4.0.0"
} }
@ -17011,7 +16964,8 @@
"normalize-path": { "normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=" "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=",
"dev": true
}, },
"normalize-range": { "normalize-range": {
"version": "0.1.2", "version": "0.1.2",
@ -17691,56 +17645,24 @@
"postcss": "^7.0.5" "postcss": "^7.0.5"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "picocolors": {
"version": "3.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
}, },
"postcss": { "postcss": {
"version": "7.0.36", "version": "7.0.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"requires": { "requires": {
"chalk": "^2.4.2", "picocolors": "^0.2.1",
"source-map": "^0.6.1", "source-map": "^0.6.1"
"supports-color": "^6.1.0"
} }
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"requires": {
"has-flag": "^3.0.0"
}
} }
} }
}, },
@ -17755,56 +17677,24 @@
"postcss-value-parser": "^4.1.0" "postcss-value-parser": "^4.1.0"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "picocolors": {
"version": "3.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
}, },
"postcss": { "postcss": {
"version": "7.0.36", "version": "7.0.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"requires": { "requires": {
"chalk": "^2.4.2", "picocolors": "^0.2.1",
"source-map": "^0.6.1", "source-map": "^0.6.1"
"supports-color": "^6.1.0"
} }
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"requires": {
"has-flag": "^3.0.0"
}
} }
} }
}, },
@ -17817,56 +17707,24 @@
"postcss-selector-parser": "^6.0.0" "postcss-selector-parser": "^6.0.0"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "picocolors": {
"version": "3.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
}, },
"postcss": { "postcss": {
"version": "7.0.36", "version": "7.0.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"requires": { "requires": {
"chalk": "^2.4.2", "picocolors": "^0.2.1",
"source-map": "^0.6.1", "source-map": "^0.6.1"
"supports-color": "^6.1.0"
} }
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"requires": {
"has-flag": "^3.0.0"
}
} }
} }
}, },
@ -17879,56 +17737,24 @@
"postcss": "^7.0.6" "postcss": "^7.0.6"
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "picocolors": {
"version": "3.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
}, },
"postcss": { "postcss": {
"version": "7.0.36", "version": "7.0.39",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
"integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
"requires": { "requires": {
"chalk": "^2.4.2", "picocolors": "^0.2.1",
"source-map": "^0.6.1", "source-map": "^0.6.1"
"supports-color": "^6.1.0"
} }
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"requires": {
"has-flag": "^3.0.0"
}
} }
} }
}, },
@ -18714,7 +18540,8 @@
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}, },
"send": { "send": {
"version": "0.18.0", "version": "0.18.0",
@ -20418,8 +20245,7 @@
"yallist": { "yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
"dev": true
}, },
"yaml": { "yaml": {
"version": "1.10.2", "version": "1.10.2",

View file

@ -24,7 +24,7 @@
"vue-template-compiler": "2.4.4" "vue-template-compiler": "2.4.4"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.2.0", "@fortawesome/fontawesome-free": "^6.2.1",
"acorn": "^8.8.0", "acorn": "^8.8.0",
"acorn-import-assertions": "^1.8.0", "acorn-import-assertions": "^1.8.0",
"admin-lte": "^2.4.18", "admin-lte": "^2.4.18",
@ -37,7 +37,7 @@
"bootstrap-less": "^3.3.8", "bootstrap-less": "^3.3.8",
"bootstrap-table": "1.20.2", "bootstrap-table": "1.20.2",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
"css-loader": "^3.6.0", "css-loader": "^4.0.0",
"ekko-lightbox": "^5.1.1", "ekko-lightbox": "^5.1.1",
"icheck": "^1.0.2", "icheck": "^1.0.2",
"imagemin": "^8.0.1", "imagemin": "^8.0.1",
@ -48,7 +48,7 @@
"jquery.iframe-transport": "^1.0.0", "jquery.iframe-transport": "^1.0.0",
"jspdf-autotable": "^3.5.24", "jspdf-autotable": "^3.5.24",
"less": "^4.1.2", "less": "^4.1.2",
"less-loader": "^5.0.0", "less-loader": "^5.0",
"list.js": "^1.5.0", "list.js": "^1.5.0",
"papaparse": "^4.3.3", "papaparse": "^4.3.3",
"select2": "4.0.13", "select2": "4.0.13",

View file

@ -968,4 +968,9 @@ th.css-accessory > .th-inner::before {
margin-top: 51px; margin-top: 51px;
} }
} }
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

View file

@ -601,4 +601,9 @@ th.css-accessory > .th-inner::before {
margin-top: 51px; margin-top: 51px;
} }
} }
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

View file

@ -23830,6 +23830,11 @@ th.css-accessory > .th-inner::before {
margin-top: 51px; margin-top: 51px;
} }
} }
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.select2-container { .select2-container {
@ -24917,4 +24922,9 @@ th.css-accessory > .th-inner::before {
margin-top: 51px; margin-top: 51px;
} }
} }
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

View file

@ -217,6 +217,9 @@ a:visited {
.navbar-nav > li > a:link { .navbar-nav > li > a:link {
color: var(--nav-link); color: var(--nav-link);
} }
.far fa-life-ring {
color: var(--link);
}
.modal-content { .modal-content {
background-color: var(--back-main); background-color: var(--back-main);
color: var(--text-main); color: var(--text-main);

View file

@ -217,6 +217,9 @@ a:visited {
.navbar-nav > li > a:link { .navbar-nav > li > a:link {
color: var(--nav-link); color: var(--nav-link);
} }
.far fa-life-ring {
color: var(--link);
}
.modal-content { .modal-content {
background-color: var(--back-main); background-color: var(--back-main);
color: var(--text-main); color: var(--text-main);

View file

@ -199,6 +199,9 @@ a.btn-danger:visited {
.btn-danger.btn-sm.disabled { .btn-danger.btn-sm.disabled {
color: #FFF; color: #FFF;
} }
.far fa-life-ring {
color: var(--link);
}
.sidebar-toggle-mobile { .sidebar-toggle-mobile {
color: #FFF !important; color: #FFF !important;
} }

View file

@ -199,6 +199,9 @@ a.btn-danger:visited {
.btn-danger.btn-sm.disabled { .btn-danger.btn-sm.disabled {
color: #FFF; color: #FFF;
} }
.far fa-life-ring {
color: var(--link);
}
.sidebar-toggle-mobile { .sidebar-toggle-mobile {
color: #FFF !important; color: #FFF !important;
} }

View file

@ -212,6 +212,9 @@ a:visited {
.navbar-nav > li > a:link { .navbar-nav > li > a:link {
color: var(--nav-link); color: var(--nav-link);
} }
.far fa-life-ring {
color: var(--link);
}
.modal-content { .modal-content {
background-color: var(--back-main); background-color: var(--back-main);
color: var(--text-main); color: var(--text-main);

View file

@ -212,6 +212,9 @@ a:visited {
.navbar-nav > li > a:link { .navbar-nav > li > a:link {
color: var(--nav-link); color: var(--nav-link);
} }
.far fa-life-ring {
color: var(--link);
}
.modal-content { .modal-content {
background-color: var(--back-main); background-color: var(--back-main);
color: var(--text-main); color: var(--text-main);

View file

@ -123,7 +123,7 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
.skin-blue.layout-top-nav .main-header > .logo .logo-variant { .skin-blue.layout-top-nav .main-header > .logo .logo-variant {
background-color: none; background-color: unset;
} }
.btn.btn-primary, .btn.btn-primary,
btn-sm.btn-primary, btn-sm.btn-primary,
@ -218,6 +218,9 @@ a:hover {
.text-primary { .text-primary {
color: #23536f; color: #23536f;
} }
.far fa-life-ring {
color: var(--link);
}
.fixed-table-container tbody .selected td { .fixed-table-container tbody .selected td {
background-color: #fff8af; background-color: #fff8af;
} }

View file

@ -123,7 +123,7 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
.skin-blue.layout-top-nav .main-header > .logo .logo-variant { .skin-blue.layout-top-nav .main-header > .logo .logo-variant {
background-color: none; background-color: unset;
} }
.btn.btn-primary, .btn.btn-primary,
btn-sm.btn-primary, btn-sm.btn-primary,
@ -218,6 +218,9 @@ a:hover {
.text-primary { .text-primary {
color: #23536f; color: #23536f;
} }
.far fa-life-ring {
color: var(--link);
}
.fixed-table-container tbody .selected td { .fixed-table-container tbody .selected td {
background-color: #fff8af; background-color: #fff8af;
} }

View file

@ -205,6 +205,9 @@ a:visited {
.bootstrap-table .fixed-table-container .table thead th .sortable { .bootstrap-table .fixed-table-container .table thead th .sortable {
color: var(--nav-link); color: var(--nav-link);
} }
.far fa-life-ring {
color: var(--link);
}
.thead, .thead,
.navbar-nav > li > a:link { .navbar-nav > li > a:link {
color: var(--nav-link); color: var(--nav-link);

View file

@ -205,6 +205,9 @@ a:visited {
.bootstrap-table .fixed-table-container .table thead th .sortable { .bootstrap-table .fixed-table-container .table thead th .sortable {
color: var(--nav-link); color: var(--nav-link);
} }
.far fa-life-ring {
color: var(--link);
}
.thead, .thead,
.navbar-nav > li > a:link { .navbar-nav > li > a:link {
color: var(--nav-link); color: var(--nav-link);

View file

@ -195,6 +195,9 @@ a:visited {
.text-primary { .text-primary {
color: #004023; color: #004023;
} }
.far fa-life-ring {
color: var(--link);
}
.fixed-table-container tbody .selected td { .fixed-table-container tbody .selected td {
background-color: #fff8af; background-color: #fff8af;
} }

View file

@ -195,6 +195,9 @@ a:visited {
.text-primary { .text-primary {
color: #004023; color: #004023;
} }
.far fa-life-ring {
color: var(--link);
}
.fixed-table-container tbody .selected td { .fixed-table-container tbody .selected td {
background-color: #fff8af; background-color: #fff8af;
} }

View file

@ -196,6 +196,9 @@ li.dropdown-item-marker {
background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);
border-color: var(--header); border-color: var(--header);
} }
.far fa-life-ring {
color: var(--link);
}
.bootstrap-table .fixed-table-container .table thead th .sortable { .bootstrap-table .fixed-table-container .table thead th .sortable {
color: var(--nav-link); color: var(--nav-link);
} }

View file

@ -196,6 +196,9 @@ li.dropdown-item-marker {
background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);
border-color: var(--header); border-color: var(--header);
} }
.far fa-life-ring {
color: var(--link);
}
.bootstrap-table .fixed-table-container .table thead th .sortable { .bootstrap-table .fixed-table-container .table thead th .sortable {
color: var(--nav-link); color: var(--nav-link);
} }

View file

@ -190,6 +190,9 @@ a.btn-warning:visited,
a.btn-danger:visited { a.btn-danger:visited {
color: #FFF; color: #FFF;
} }
.far fa-life-ring {
color: var(--link);
}
.fixed-table-container tbody .selected td { .fixed-table-container tbody .selected td {
background-color: #fff8af; background-color: #fff8af;
} }

View file

@ -190,6 +190,9 @@ a.btn-warning:visited,
a.btn-danger:visited { a.btn-danger:visited {
color: #FFF; color: #FFF;
} }
.far fa-life-ring {
color: var(--link);
}
.fixed-table-container tbody .selected td { .fixed-table-container tbody .selected td {
background-color: #fff8af; background-color: #fff8af;
} }

View file

@ -223,6 +223,9 @@ a:visited {
a:link { a:link {
color: var(--link); color: var(--link);
} }
.far fa-life-ring {
color: var(--link);
}
.btn-primary.hover { .btn-primary.hover {
color: var(--nav-link); color: var(--nav-link);
} }

View file

@ -223,6 +223,9 @@ a:visited {
a:link { a:link {
color: var(--link); color: var(--link);
} }
.far fa-life-ring {
color: var(--link);
}
.btn-primary.hover { .btn-primary.hover {
color: var(--nav-link); color: var(--nav-link);
} }

View file

@ -196,6 +196,9 @@ a.btn-danger:visited {
.select2-container--default .select2-selection--multiple .select2-selection__choice { .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #605ca8; background-color: #605ca8;
} }
.far fa-life-ring {
color: var(--link);
}
.search-highlight, .search-highlight,
.search-highlight:hover { .search-highlight:hover {
background-color: #e9d15b; background-color: #e9d15b;

View file

@ -196,6 +196,9 @@ a.btn-danger:visited {
.select2-container--default .select2-selection--multiple .select2-selection__choice { .select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #605ca8; background-color: #605ca8;
} }
.far fa-life-ring {
color: var(--link);
}
.search-highlight, .search-highlight,
.search-highlight:hover { .search-highlight:hover {
background-color: #e9d15b; background-color: #e9d15b;

View file

@ -226,6 +226,9 @@ a:visited {
a:hover { a:hover {
color: var(--hover-link); color: var(--hover-link);
} }
.far fa-life-ring {
color: var(--link);
}
.btn-primary.hover { .btn-primary.hover {
color: var(--nav-link); color: var(--nav-link);
} }

View file

@ -226,6 +226,9 @@ a:visited {
a:hover { a:hover {
color: var(--hover-link); color: var(--hover-link);
} }
.far fa-life-ring {
color: var(--link);
}
.btn-primary.hover { .btn-primary.hover {
color: var(--nav-link); color: var(--nav-link);
} }

View file

@ -207,4 +207,7 @@ a.btn-danger:visited {
.search-highlight:hover { .search-highlight:hover {
background-color: #e9d15b; background-color: #e9d15b;
} }
.far fa-life-ring {
color: var(--link);
}

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