Compare commits

...

61 Commits

Author SHA1 Message Date
snipe 7b34cf1a31 Bumped version 2019-05-21 19:17:56 -07:00
snipe 21ec670531 Updated language strings 2019-05-21 19:17:00 -07:00
snipe b2eacb147b Fixed #7046 - added user website url back into UI 2019-05-21 18:55:12 -07:00
snipe 729e3eb70d Updated email strings 2019-05-21 18:43:00 -07:00
snipe 978533b2f4 Fixed XSS vulnerability when creating a new categories, etc via modal on create
Same fix as before, because of the weird select2 post-parsing ajax behavior
2019-05-21 18:29:50 -07:00
snipe 0358d13ddb Fixed #7044 - API update deleted custom fields if they are not re-presented 2019-05-20 11:49:18 -07:00
snipe c944304444 Updated packages
- Updating erusev/parsedown (v1.7.2 => 1.7.3): Downloading (100%)
  - Updating squizlabs/php_codesniffer (3.4.1 => 3.4.2): Downloading (100%)
  - Updating symfony/polyfill-mbstring (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/var-dumper (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating league/flysystem (1.0.50 => 1.0.51): Downloading (100%)
  - Updating symfony/translation (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating nesbot/carbon (1.36.2 => 1.37.1): Downloading (100%)
  - Updating symfony/debug (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/console (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/finder (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/polyfill-ctype (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/polyfill-php70 (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/http-foundation (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/event-dispatcher (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/http-kernel (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/process (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/routing (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/polyfill-util (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/polyfill-php56 (v1.10.0 => v1.11.0): Downloading (100%)
  - Updating symfony/psr-http-message-bridge (v1.1.1 => v1.1.2): Downloading (failed)
Downloading (100%)
  - Updating rollbar/rollbar (v1.7.5 => v1.8.1): Downloading (100%)
  - Updating symfony/yaml (v3.4.23 => v3.4.27): Downloading (100%)
  - Updating symfony/browser-kit (v3.4.23 => v3.4.27): Downloading (100%)
2019-05-20 10:10:46 -07:00
Bob Clough 096393389c Fixes #5054: LDAP users deactivated for none-ad (#7032)
When using none-AD ldap, users are automatically deactivated every LDAP
sync.  This commit changes the behaviour so that if the active flag isn't set,
the users are enabled.

Fixed #5054, at least for 4.X
2019-05-16 09:31:55 -07:00
snipe 9eb7b668d1 Fixed #6880 - correctly encrypt encrypted fields via the API 2019-05-15 19:33:30 -07:00
snipe 6728089106 Fixed #6883 - remove escaping of fields on LDAP import 2019-05-15 19:15:41 -07:00
snipe 33b59d7bed Bumped version 2019-05-15 16:45:07 -07:00
snipe 888bdbdb68 Added ability to update groups via API
Fixes [ch9139]
2019-05-15 16:39:34 -07:00
snipe d67c931f6a Import locations from CSV via command line (#7021)
* Added import locations command

* Small fixes to location importer

* Added country, LDAP OU

* Cleaned up comments, added more clarification to what the script does
2019-05-13 02:27:19 -07:00
snipe dbdc511eff Merge branch 'master' of https://github.com/snipe/snipe-it 2019-05-08 09:23:58 -04:00
snipe f47b960566 Added API middleware to API routes to enable throttling
TODO: Figure out how to make this costumizable without touching the code
2019-05-08 09:23:54 -04:00
snipe d016076806 Fixed #6956 - viewKeys policy inconsistent (#7009)
* Fixed #6956 - Added additional gates show showing/hiding license keys

* Modified gate to allow user to see licenses if they can create or edit the license as well
2019-05-08 08:14:49 -04:00
snipe 23fa5d0bf4 Fixed #7003 - crash when warranty months or purchase date is null 2019-05-07 15:33:57 -04:00
Joris van Eijden 486c708911 Leave the activated state for users alone in normal LDAP synchronisation. (#6988) 2019-05-06 09:40:53 -04:00
snipe e5c2d77c7d Fixes #6204 - added email alerts and web/API access to assets due for audits (#6992)
* Added upcoming audit report

TODO: Fid diff/threshold math

* Added route to list overdue / upcoming assets via API

* Controller/API methods for due/overdue audits

We could probably skip this and just handle it via view in the routes…

* Added query scopes for due and overdue audits

* Added audit due console command to kernel

* Added ability to pass audit specs to main API asset search method

* Added audit presenter

* Added bootstrap-tables presenter formatter to display an audit button

* Added gated sidenav items to left nav

* Added audit due/overdue blades

* Cleanup on audit due/overdue console command

* Added language strings for audit views

* Fixed :threshold placeholder

* Removed unused setting variable

* Fixed next audit date math

* Added scope for both overdue and upcoming

* Derp. Wrong version

* Bumped version

(I will release this version officially tomorrow)
2019-05-05 22:32:52 -04:00
snipe ce16eae508 Merge branch 'master' of https://github.com/snipe/snipe-it 2019-05-02 15:20:52 -07:00
snipe dc73dbfbfd Fixed #6911 - note must be a string on license checkin 2019-05-02 15:20:47 -07:00
snipe dae26e0378 Remove “Imported from LDAP” note override 2019-04-18 17:56:08 -04:00
snipe 1bb1f7342f Fixed #6922 - date_add crashing if EOL is null 2019-04-18 15:49:59 -04:00
snipe 420e8bc85a Allow phone number to be changed in Profile 2019-04-18 14:13:50 -04:00
snipe a521523d45 Truncate imports table on seed 2019-04-18 14:13:33 -04:00
snipe 25884a893e Bumped version 2019-03-20 02:41:33 -07:00
snipe d1e9fbfa24 Updated compoer 2019-03-20 02:37:44 -07:00
snipe da015ec4a8 Fixed #6834 and #6402 - use inline QR code generation for 2FA (#6840)
* Fixed  #6834 and #6402 - use inline QR code generation for

* Update auth controllers to use translations

* Updated composer lock

* Added comments

* Moar comments

* Typo
2019-03-20 01:24:31 -07:00
snipe 1451b4f45d Merge branch 'master' of https://github.com/snipe/snipe-it 2019-03-18 20:51:11 -07:00
snipe b6da68a69c Bumped version 2019-03-18 20:50:47 -07:00
snipe dee92cfc6c Fixes XSS vulnerabilities (#6831)
* Properly escape log_meta values

* Vue syntax fix to allow npm run dev to work again

* Janky fix for Select2 bug

* Compiled production assets

* Escape user’s last name in API

* Removed duplicate alertClass

* Compiled production assets
2019-03-18 20:49:32 -07:00
snipe dec77890bd Merge branch 'master' of https://github.com/snipe/snipe-it 2019-03-18 11:59:48 -07:00
snipe 0e1289f12f Fixes #6821 - fixed 2 fa active for users list (#6822)
* Fixed #6821 - confusing UI for 2FA when 2FA is universally enforced

I also updated the language in the user’s listing table to clarify what “activated” means

* Added login enabled info to user view

* Clarified comments

* Added info about 2FA on user profile

Because why not

* Added nowrap to table, and added 2FA reset for superadmins
2019-03-18 11:59:02 -07:00
snipe 7b33f95e83 Fixes/import permissions mask (#6826)
* Check for empty headers in import

* Added import permission

* Fixed model path in docblock

* Added import gate to default blade

* Check if the user is an admin OR idf they have import permissions

* Walked back that admin permission

Since admins are bound by full company support, it makes less sense to let admins have this permission by default, versus having them specifically designated to the import permission
2019-03-18 11:58:08 -07:00
snipe ab6744dfba Merge branch 'master' of https://github.com/snipe/snipe-it 2019-03-14 15:38:20 -07:00
snipe 0fd940ffa4 Check for empty headers in import 2019-03-14 15:38:07 -07:00
Tim Farmer 5893e25b43 Label bugs in alerts settings (#6808)
* Label bugs in alerts settings

Found another label bug where the alert_email label should be alerts_enabled.

* Update alerts.blade.php
2019-03-14 10:53:05 -07:00
snipe 7c3bbe3097 Fixes #6776 hungary date format (#6823)
* Add @timothyfarmer as a contributor

* Fixed #6776 - added hungary time format
2019-03-13 19:39:38 -07:00
snipe 858d382e26 Changed logging to info level for LDAP 2019-03-13 15:14:03 -07:00
snipe de16fee00a Change image unlink error log to info from error 2019-03-13 12:22:12 -07:00
Tim Farmer 7deab0f53b Label bugs in general settings (#6801)
* checkbox label bug

The checkbox label in general settings for require_accept_signature was set to full_multiple_companies_support instead so when you click the label for require accept signature it would check the wrong box.

* Update general.blade.php

* Update general.blade.php

Another label that is wrong in general settings
2019-03-08 12:24:24 -08:00
Jonathon Reinhart e59ec8b27f Run Laravel schedule in docker image using supervisord (#6606)
* docker: Rename /entrypoint.sh to /startup.sh

This script is not configured as the docker image ENTRYPOINT, thus it is
misleading to name it so.

* docker: Terminate supervisord if a process enters the FATAL state

By terminating PID 1, this will also terminate the Docker container.

* docker: Use supervisord to start up apache and cron

Note that this uses `apache2ctl -DFOREGROUND` rather than manually
sourcing /etc/apache2/envvars and running apache2, as recommended at
https://advancedweb.hu/2018/07/03/supervisor_docker/.

* docker: Add artisan schedule:run to crontab

This also switches to executing /var/www/html/artisan directly.

* docker: Run artisan schedule:run directly from supervisor

This has the following benefits over using cron:
- Cron doesn't need to be installed
- Docker-provided environment variables are preserved
- It's easy and explicit to run as the docker user
2019-03-07 12:42:00 -08:00
snipe 6d98bd6846 Fixed error if item requested or request was deleted (#6786)
ch628
2019-03-05 23:47:36 -08:00
snipe 58768e5aee Added ability to search consumables by item number (#6785)
Fixes ch1086
2019-03-05 23:21:22 -08:00
snipe 28a450ea25 Added ability to do full name search in user dropdown selectlist (#6784) 2019-03-05 21:13:39 -08:00
snipe 1393f44070 Create FUNDING.yml 2019-03-04 23:57:17 -08:00
snipe 8016939f31 Merge branch 'master' of https://github.com/snipe/snipe-it 2019-03-01 17:39:55 -08:00
snipe c1ad2f9376 Added link to Marksman - A Windows agent for Snipe-IT
Per https://github.com/Scope-IT/marksman/issues/9#issuecomment-468275142
2019-03-01 17:39:50 -08:00
snipe 9575cd2651 Add accessories endpoint to user API (#6775) 2019-03-01 17:21:03 -08:00
snipe cf086b711e Merge branch 'master' of https://github.com/snipe/snipe-it 2019-02-22 13:24:58 -08:00
snipe 53db96edad Bumped minor version 2019-02-22 13:24:45 -08:00
snipe 3b62c4a83a Fixes/integrity constraint (#6754)
* Migration to fix nullables

This should fix an issue introduced in 90cddb7aee where we’re passing null instead of an empty string (necessary to nullify values via the API)

* Removed asset migration - serial was already fixed
2019-02-22 13:20:42 -08:00
snipe 5f3147cf36 Added support for enum that was added :( 2019-02-20 21:04:03 -08:00
snipe 738896bdc2 Make serial nullable
Guessing this is new due to later versions of mysql
2019-02-20 20:47:14 -08:00
snipe e2834fab90 Bumped minor version 2019-02-16 11:48:20 -08:00
snipe d687e1d762 Fixed #6725 - revert auro_increment_prefix to string
This was mis-migrated as boolean.

It’s essentially a duplicate migration, as it is fixed in-place on the existing migration, and then a new migratio was created to handle anyone who already upgraded.
2019-02-16 11:37:05 -08:00
snipe 6256abddf2 Bumped point release 2019-02-14 15:07:11 -08:00
snipe b26fbf986f Fixed issue where offset could be greater than total items, resulting in “No results” confusion 2019-02-14 14:49:08 -08:00
snipe 5c9b1ed43a Fixed #6676 - consumables API not respecting category id 2019-02-14 14:48:43 -08:00
snipe 14eb6b387b Possible fix for #6710 - explicitly make auto_increment_prefix nullable (#6716)
* Possible fix for #6710 - explicitly make auto_increment_prefix nullable

* Added migration to make auto_increment_prefix explicitly nullable
2019-02-14 12:46:09 -08:00
snipe 35ebe33e4e Fixed #6703 - fixes password confirmation (#6711)
* Fixed #6703 - fixes password confirmation

* Removed debugging

* Fixed tests

* I guess we use 10 as the settings for password min in tests

* One more try to fix tests - confirmation won’t validate until password validates
2019-02-13 23:01:19 -08:00
223 changed files with 11262 additions and 1091 deletions
+9
View File
@@ -1650,6 +1650,15 @@
"contributions": [
"code"
]
},
{
"login": "timothyfarmer",
"name": "Tim Farmer",
"avatar_url": "https://avatars1.githubusercontent.com/u/7632599?v=4",
"profile": "https://github.com/timothyfarmer",
"contributions": [
"code"
]
}
]
}
+5
View File
@@ -0,0 +1,5 @@
# You can add one username per supported platform and one custom link
# patreon: # Replace with your Patreon username
# open_collective: # Replace with your Open Collective username
# ko_fi: # Replace with your Ko-fi username
custom: https://snipeitapp.com/donate
+8 -12
View File
@@ -18,9 +18,8 @@ patch \
curl \
vim \
git \
cron \
mysql-client \
cron \
supervisor \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@@ -69,7 +68,9 @@ RUN \
&& rm -r "/var/www/html/storage/app/backups" && ln -fs "/var/lib/snipeit/dumps" "/var/www/html/storage/app/backups" \
&& mkdir "/var/lib/snipeit/keys" && ln -fs "/var/lib/snipeit/keys/oauth-private.key" "/var/www/html/storage/oauth-private.key" \
&& ln -fs "/var/lib/snipeit/keys/oauth-public.key" "/var/www/html/storage/oauth-public.key" \
&& chown docker "/var/lib/snipeit/keys/"
&& chown docker "/var/lib/snipeit/keys/" \
&& chmod +x /var/www/html/artisan \
&& echo "Finished setting up application in /var/www/html"
############## DEPENDENCIES via COMPOSER ###################
@@ -96,16 +97,11 @@ VOLUME ["/var/lib/snipeit"]
##### START SERVER
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
COPY docker/startup.sh docker/supervisord.conf /
COPY docker/supervisor-exit-event-listener /usr/bin/supervisor-exit-event-listener
RUN chmod +x /startup.sh /usr/bin/supervisor-exit-event-listener
# Add Tini
ENV TINI_VERSION v0.14.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
CMD ["/entrypoint.sh"]
CMD ["/startup.sh"]
EXPOSE 80
EXPOSE 443
+3 -2
View File
@@ -1,5 +1,5 @@
[![Build Status](https://travis-ci.org/snipe/snipe-it.svg?branch=master)](https://travis-ci.org/snipe/snipe-it) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-180-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
[![All Contributors](https://img.shields.io/badge/all_contributors-181-orange.svg?style=flat-square)](#contributors) [![Open Source Helpers](https://www.codetriage.com/snipe/snipe-it/badges/users.svg)](https://www.codetriage.com/snipe/snipe-it)
## Snipe-IT - Open Source Asset Management System
@@ -59,6 +59,7 @@ Since the release of the JSON REST API, several third-party developers have been
- [InQRy](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
- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-II instance
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
As these were created by third-parties, Snipe-IT cannot provide support for these project, and you should contact the developers directly if you need assistance. Additionally, Snipe-IT makes no guarantees as to the reliability, accuracy or maintainability of these libraries. Use at your own risk. :)
@@ -95,7 +96,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars2.githubusercontent.com/u/982885?v=4" width="110px;"/><br /><sub>Martin Stub</sub>](http://martinstub.dk)<br />[🌍](#translation-stubben "Translation") | [<img src="https://avatars2.githubusercontent.com/u/28959963?v=4" width="110px;"/><br /><sub>Meyer Flavio</sub>](https://github.com/meyerf99)<br />[🌍](#translation-meyerf99 "Translation") | [<img src="https://avatars3.githubusercontent.com/u/796443?v=4" width="110px;"/><br /><sub>Micael Rodrigues</sub>](https://github.com/MicaelRodrigues)<br />[🌍](#translation-MicaelRodrigues "Translation") | [<img src="https://avatars0.githubusercontent.com/u/10481331?v=4" width="110px;"/><br /><sub>Mikael Rasmussen</sub>](http://rubixy.com/)<br />[🌍](#translation-mikaelssen "Translation") | [<img src="https://avatars1.githubusercontent.com/u/1544552?v=4" width="110px;"/><br /><sub>IxFail</sub>](https://github.com/IxFail)<br />[🌍](#translation-IxFail "Translation") | [<img src="https://avatars3.githubusercontent.com/u/18483118?v=4" width="110px;"/><br /><sub>Mohammed Fota</sub>](http://www.mohammedfota.com)<br />[🌍](#translation-MohammedFota "Translation") | [<img src="https://avatars0.githubusercontent.com/u/227080?v=4" width="110px;"/><br /><sub>Moayad Alserihi</sub>](https://github.com/omego)<br />[🌍](#translation-omego "Translation") |
| [<img src="https://avatars0.githubusercontent.com/u/1680266?v=4" width="110px;"/><br /><sub>saymd</sub>](https://github.com/saymd)<br />[🌍](#translation-saymd "Translation") | [<img src="https://avatars0.githubusercontent.com/u/1826808?v=4" width="110px;"/><br /><sub>Patrik Larsson</sub>](https://nordsken.se)<br />[🌍](#translation-pooot "Translation") | [<img src="https://avatars1.githubusercontent.com/u/20584746?v=4" width="110px;"/><br /><sub>drcryo</sub>](https://github.com/drcryo)<br />[🌍](#translation-drcryo "Translation") | [<img src="https://avatars1.githubusercontent.com/u/19408004?v=4" width="110px;"/><br /><sub>pawel1615</sub>](https://github.com/pawel1615)<br />[🌍](#translation-pawel1615 "Translation") | [<img src="https://avatars2.githubusercontent.com/u/23340468?v=4" width="110px;"/><br /><sub>bodrovics</sub>](https://github.com/bodrovics)<br />[🌍](#translation-bodrovics "Translation") | [<img src="https://avatars0.githubusercontent.com/u/3257654?v=4" width="110px;"/><br /><sub>priatna</sub>](https://github.com/priatna)<br />[🌍](#translation-priatna "Translation") | [<img src="https://avatars1.githubusercontent.com/u/5358374?v=4" width="110px;"/><br /><sub>Fan Jiang</sub>](https://amayume.net)<br />[🌍](#translation-ProfFan "Translation") |
| [<img src="https://avatars1.githubusercontent.com/u/22555451?v=4" width="110px;"/><br /><sub>ragnarcx</sub>](https://github.com/ragnarcx)<br />[🌍](#translation-ragnarcx "Translation") | [<img src="https://avatars2.githubusercontent.com/u/18654582?v=4" width="110px;"/><br /><sub>Rein van Haaren</sub>](http://www.reinvanhaaren.nl/)<br />[🌍](#translation-reinvanhaaren "Translation") | [<img src="https://avatars1.githubusercontent.com/u/386672?v=4" width="110px;"/><br /><sub>Teguh Dwicaksana</sub>](http://dheche.songolimo.net)<br />[🌍](#translation-dheche "Translation") | [<img src="https://avatars2.githubusercontent.com/u/2572552?v=4" width="110px;"/><br /><sub>fraccie</sub>](https://github.com/FRaccie)<br />[🌍](#translation-FRaccie "Translation") | [<img src="https://avatars0.githubusercontent.com/u/35182720?v=4" width="110px;"/><br /><sub>vinzruzell</sub>](https://github.com/vinzruzell)<br />[🌍](#translation-vinzruzell "Translation") | [<img src="https://avatars1.githubusercontent.com/u/7883603?v=4" width="110px;"/><br /><sub>Kevin Austin</sub>](http://kevinaustin.com)<br />[🌍](#translation-vipsystem "Translation") | [<img src="https://avatars3.githubusercontent.com/u/3861828?v=4" width="110px;"/><br /><sub>Wira Sandy</sub>](http://azuraweb.xyz)<br />[🌍](#translation-wira-sandy "Translation") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/8663789?v=4" width="110px;"/><br /><sub>Илья</sub>](https://github.com/GrayHoax)<br />[🌍](#translation-GrayHoax "Translation") | [<img src="https://avatars3.githubusercontent.com/u/30119111?v=4" width="110px;"/><br /><sub>GodUseVPN</sub>](https://github.com/godusevpn)<br />[🌍](#translation-godusevpn "Translation") | [<img src="https://avatars1.githubusercontent.com/u/745576?v=4" width="110px;"/><br /><sub>周周</sub>](https://github.com/EngrZhou)<br />[🌍](#translation-EngrZhou "Translation") | [<img src="https://avatars3.githubusercontent.com/u/1631095?v=4" width="110px;"/><br /><sub>Sam</sub>](https://github.com/takuy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [<img src="https://avatars1.githubusercontent.com/u/264022?v=4" width="110px;"/><br /><sub>Azerothian</sub>](https://www.illisian.com.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [<img src="https://avatars1.githubusercontent.com/u/7632599?v=4" width="110px;"/><br /><sub>Tim Farmer</sub>](https://github.com/timothyfarmer)<br />[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
+130
View File
@@ -0,0 +1,130 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use League\Csv\Reader;
use App\Models\Location;
class ImportLocations extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:import-locations {filename}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import locations and their parents';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (!ini_get("auto_detect_line_endings")) {
ini_set("auto_detect_line_endings", '1');
}
$filename = $this->argument('filename');
$csv = Reader::createFromPath(storage_path('private_uploads/imports/').$filename, 'r');
$this->info('Attempting to process: '.storage_path('private_uploads/imports/').$filename);
$csv->setOffset(1); //because we don't want to insert the header
$results = $csv->fetchAssoc();
// Import parent location names first if they don't exist
foreach ($results as $parent_index => $parent_row) {
$parent_name = trim($parent_row['Parent Name']);
// First create any parents if they don't exist
if ($parent_name!='') {
// Save parent location name
// This creates a sort of name-stub that we'll update later on in this script
$parent_location = Location::firstOrCreate(array('name' => $parent_name));
$this->info('Parent for '.$parent_row['Name'].' is '.$parent_name.'. Attempting to save '.$parent_name.'.');
// Check if the record was updated or created.
// This is mostly for clearer debugging.
if ($parent_location->exists) {
$this->info('- Parent location '.$parent_name.' already exists.');
} else {
$this->info('- Parent location '.$parent_name.' was created.');
}
} else {
$this->info('- No parent location for '.$parent_row['Name'].' provided.');
}
}
// Loop through ALL records and add/update them if there are additional fields
// besides name
foreach ($results as $index => $row) {
// Set the location attributes to save
$location = Location::firstOrNew(array('name' => trim($row['Name'])));
$location->name = trim($row['Name']);
$location->currency = trim($row['Currency']);
$location->address = trim($row['Address 1']);
$location->address2 = trim($row['Address 2']);
$location->city = trim($row['City']);
$location->state = trim($row['State']);
$location->zip = trim($row['Zip']);
$location->country = trim($row['Country']);
$location->ldap_ou = trim($row['OU']);
$this->info('Checking location: '.$location->name);
// If a parent name nis provided, we created it earlier in the script,
// so let's grab that ID
if ($parent_name) {
$parent = Location::where('name', '=', $parent_name)->first();
$location->parent_id = $parent->id;
$this->info('Parent ID: '.$parent->id);
}
// Make sure the more advanced (non-name) fields pass validation
if (($location->isValid()) && ($location->save())) {
// Check if the record was updated or created.
// This is mostly for clearer debugging.
if ($location->exists) {
$this->info('Location ' . $location->name . ' already exists. Updating...');
} else {
$this->info('- Location '.$location->name.' was created. ');
}
// If there's a validation error, display that
} else {
$this->error('- Non-parent Location '.$location->name.' could not be created: '.$location->getErrors() );
}
}
}
}
+28 -27
View File
@@ -61,16 +61,16 @@ class LdapSync extends Command
$json_summary = [ "error" => true, "error_message" => $e->getMessage(), "summary" => [] ];
$this->info(json_encode($json_summary));
}
LOG::error($e);
LOG::info($e);
return [];
}
$summary = array();
try {
try {
if ($this->option('base_dn') != '') {
$search_base = $this->option('base_dn');
LOG::debug('Importing users from specified base DN: \"'.$search_base.'\".');
LOG::debug('Importing users from specified base DN: \"'.$search_base.'\".');
} else {
$search_base = null;
}
@@ -80,7 +80,7 @@ class LdapSync extends Command
$json_summary = [ "error" => true, "error_message" => $e->getMessage(), "summary" => [] ];
$this->info(json_encode($json_summary));
}
LOG::error($e);
LOG::info($e);
return [];
}
@@ -106,7 +106,7 @@ class LdapSync extends Command
// Retrieve locations with a mapped OU, and sort them from the shallowest to deepest OU (see #3993)
$ldap_ou_locations = Location::where('ldap_ou', '!=', '')->get()->toArray();
$ldap_ou_lengths = array();
foreach ($ldap_ou_locations as $location) {
$ldap_ou_lengths[] = strlen($location["ldap_ou"]);
}
@@ -168,34 +168,36 @@ class LdapSync extends Command
$item["ldap_location_override"] = isset($results[$i]["ldap_location_override"]) ? $results[$i]["ldap_location_override"]:"";
$item["location_id"] = isset($results[$i]["location_id"]) ? $results[$i]["location_id"]:"";
// This is active directory, not regular LDAP
if ( array_key_exists('useraccountcontrol', $results[$i]) ) {
$enabled_accounts = [
'512', '544', '66048', '66080', '262656', '262688', '328192', '328224'
];
$item['activated'] = ( in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts) ) ? 1 : 0;
// Fall through to LDAP
$user = User::where('username', $item["username"])->first();
if ($user) {
// Updating an existing user.
$item["createorupdate"] = 'updated';
} else {
$item['activated'] = 0;
}
// User exists
$item["createorupdate"] = 'updated';
if (!$user = User::where('username', $item["username"])->first()) {
// Creating a new user.
$user = new User;
$user->password = $pass;
$user->activated = 0;
$item["createorupdate"] = 'created';
}
// Create the user if they don't exist.
$user->first_name = e($item["firstname"]);
$user->last_name = e($item["lastname"]);
$user->username = e($item["username"]);
$user->email = e($item["email"]);
$user->first_name = $item["firstname"];
$user->last_name = $item["lastname"];
$user->username = $item["username"];
$user->email = $item["email"];
$user->employee_num = e($item["employee_number"]);
$user->activated = $item['activated'];
// Sync activated state for Active Directory.
if ( array_key_exists('useraccountcontrol', $results[$i]) ) {
$enabled_accounts = [
'512', '544', '66048', '66080', '262656', '262688', '328192', '328224'
];
$user->activated = ( in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts) ) ? 1 : 0;
}
// If we're not using AD, and there isn't an activated flag set, activate all users
elseif (empty($ldap_result_active_flag)) {
$user->activated = 1;
}
if ($item['ldap_location_override'] == true) {
$user->location_id = $item['location_id'];
@@ -209,7 +211,6 @@ class LdapSync extends Command
}
$user->notes = 'Imported from LDAP';
$user->ldap_import = 1;
$errors = '';
@@ -0,0 +1,91 @@
<?php
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\License;
use App\Models\Setting;
use App\Notifications\ExpiringAssetsNotification;
use App\Models\Recipients;
use DB;
use Illuminate\Console\Command;
use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon;
class SendUpcomingAuditReport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'snipeit:upcoming-audits';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send email/slack notifications for upcoming asset audits.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$settings = Setting::getSettings();
if (($settings->alert_email != '') && ($settings->audit_warning_days) && ($settings->alerts_enabled == 1)) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item, $key) {
return new \App\Models\Recipients\AlertRecipient($item);
});
// Assets due for auditing
$assets = Asset::whereNotNull('next_audit_date')
->DueOrOverdueForAudit($settings)
->orderBy('last_audit_date', 'asc')->get();
if ($assets->count() > 0) {
$this->info(trans_choice('mail.upcoming-audits', $assets->count(),
['count' => $assets->count(), 'threshold' => $settings->audit_warning_days]));
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$this->info('Audit report sent to '.$settings->alert_email);
} else {
$this->info('No assets to be audited. No report sent.');
}
} elseif ($settings->alert_email=='') {
$this->error('Could not send email. No alert email configured in settings');
} elseif (!$settings->audit_warning_days) {
$this->error('No audit warning days set in Admin Notifications. No mail will be sent.');
} elseif ($settings->alerts_enabled!=1) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
} else {
$this->error('Something went wrong. :( ');
$this->error('Admin Notifications Email Setting: '.$settings->alert_email);
$this->error('Admin Audit Warning Setting: '.$settings->audit_warning_days);
$this->error('Admin Alerts Emnabled: '.$settings->alerts_enabled);
}
}
}
+4
View File
@@ -2,6 +2,7 @@
namespace App\Console;
use App\Console\Commands\RestoreDeletedUsers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -31,6 +32,8 @@ class Kernel extends ConsoleKernel
Commands\RegenerateAssetTags::class,
Commands\SyncAssetCounters::class,
Commands\RestoreDeletedUsers::class,
Commands\SendUpcomingAuditReport::class,
Commands\ImportLocations::class,
];
/**
@@ -47,6 +50,7 @@ class Kernel extends ConsoleKernel
$schedule->command('snipeit:expected-checkin')->daily();
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
}
protected function commands()
@@ -46,7 +46,7 @@ class AccessoriesController extends Controller
$accessories->where('supplier_id','=',$request->input('supplier_id'));
}
$offset = $request->input('offset', 0);
$offset = (($accessories) && (request('offset') > $accessories->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -44,7 +44,7 @@ class AssetMaintenancesController extends Controller
$maintenances->where('asset_id', '=', $request->input('asset_id'));
}
$offset = request('offset', 0);
$offset = (($maintenances) && (request('offset') > $maintenances->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = [
@@ -60,7 +60,7 @@ class AssetModelsController extends Controller
$assetmodels->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$offset = (($assetmodels) && (request('offset') > $assetmodels->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at';
@@ -179,7 +179,7 @@ class AssetModelsController extends Controller
try {
unlink(public_path().'/uploads/models/'.$assetmodel->image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
+31 -4
View File
@@ -52,7 +52,7 @@ class AssetsController extends Controller
* @since [v4.0]
* @return JsonResponse
*/
public function index(Request $request)
public function index(Request $request, $audit = null)
{
$this->authorize('index', Asset::class);
@@ -144,10 +144,25 @@ class AssetsController extends Controller
$request->has('order_number') ? $assets = $assets->where('assets.order_number', '=', e($request->get('order_number'))) : '';
$offset = request('offset', 0);
$offset = (($assets) && (request('offset') > $assets->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
// This is used by the audit reporting routes
if (Gate::allows('audit', Asset::class)) {
switch ($audit) {
case 'due':
$assets->DueOrOverdueForAudit($settings);
break;
case 'overdue':
$assets->overdueForAudit($settings);
break;
}
}
// This is used by the sidenav, mostly
// We switched from using query scopes here because of a Laravel bug
@@ -433,7 +448,13 @@ class AssetsController extends Controller
$model = AssetModel::find($request->get('model_id'));
if ($model->fieldset) {
foreach ($model->fieldset->fields as $field) {
$asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug(), null));
if ($field->field_encrypted=='1') {
if (Gate::allows('admin')) {
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
}
} else {
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
}
}
}
@@ -483,7 +504,13 @@ class AssetsController extends Controller
if (($model = AssetModel::find($asset->model_id)) && (isset($model->fieldset))) {
foreach ($model->fieldset->fields as $field) {
if ($request->has($field->convertUnicodeDbSlug())) {
$asset->{$field->convertUnicodeDbSlug()} = e($request->input($field->convertUnicodeDbSlug()));
if ($field->field_encrypted=='1') {
if (Gate::allows('admin')) {
$asset->{$field->convertUnicodeDbSlug()} = \Crypt::encrypt($request->input($field->convertUnicodeDbSlug()));
}
} else {
$asset->{$field->convertUnicodeDbSlug()} = $request->input($field->convertUnicodeDbSlug());
}
}
}
}
@@ -30,7 +30,7 @@ class CategoriesController extends Controller
$categories = $categories->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$offset = (($categories) && (request('offset') > $categories->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count';
@@ -41,7 +41,7 @@ class CompaniesController extends Controller
$companies->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$offset = (($companies) && (request('offset') > $companies->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -43,7 +43,7 @@ class ComponentsController extends Controller
$components->where('location_id','=',$request->input('location_id'));
}
$offset = request('offset', 0);
$offset = (($components) && (request('offset') > $components->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['id','name','min_amt','order_number','serial','purchase_date','purchase_cost','company','category','qty','location','image'];
@@ -35,12 +35,16 @@ class ConsumablesController extends Controller
$consumables->where('company_id','=',$request->input('company_id'));
}
if ($request->has('category_id')) {
$consumables->where('category_id','=',$request->input('category_id'));
}
if ($request->has('manufacturer_id')) {
$consumables->where('manufacturer_id','=',$request->input('manufacturer_id'));
}
$offset = request('offset', 0);
$offset = (($consumables) && (request('offset') > $consumables->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
$allowed_columns = ['id','name','order_number','min_amt','purchase_date','purchase_cost','company','category','model_number', 'item_no', 'manufacturer','location','qty','image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -39,7 +39,7 @@ class DepartmentsController extends Controller
$departments = $departments->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$offset = (($departments) && (request('offset') > $departments->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -28,7 +28,7 @@ class DepreciationsController extends Controller
$depreciations = $depreciations->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$offset = (($depreciations) && (request('offset') > $depreciations->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -28,7 +28,7 @@ class GroupsController extends Controller
$groups = $groups->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$offset = (($groups) && (request('offset') > $groups->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -25,7 +25,7 @@ class ImportController extends Controller
*/
public function index()
{
//
$this->authorize('import');
$imports = Import::latest()->get();
return (new ImportsTransformer)->transformImports($imports);
@@ -39,10 +39,8 @@ class ImportController extends Controller
*/
public function store()
{
//
if (!Company::isCurrentUserAuthorized()) {
return redirect()->route('hardware.index')->with('error', trans('general.insufficient_permissions'));
} elseif (!config('app.lock_passwords')) {
$this->authorize('import');
if (!config('app.lock_passwords')) {
$files = Input::file('files');
$path = config('app.private_uploads').'/imports';
$results = [];
@@ -119,7 +117,7 @@ class ImportController extends Controller
*/
public function process(ItemImportRequest $request, $import_id)
{
$this->authorize('create', Asset::class);
$this->authorize('import');
// Run a backup immediately before processing
Artisan::call('backup:run');
$errors = $request->import(Import::find($import_id));
@@ -162,7 +160,7 @@ class ImportController extends Controller
*/
public function destroy($import_id)
{
$this->authorize('create', Asset::class);
$this->authorize('import');
$import = Import::find($import_id);
try {
unlink(config('app.private_uploads').'/imports/'.$import->file_path);
@@ -82,7 +82,7 @@ class LicensesController extends Controller
}
$offset = request('offset', 0);
$offset = (($licenses) && (request('offset') > $licenses->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -227,7 +227,8 @@ class LicensesController extends Controller
$seats = LicenseSeat::where('license_id', $licenseId)->with('license', 'user', 'asset');
$offset = request('offset', 0);
$offset = (($seats) && (request('offset') > $seats->count())) ? 0 : request('offset', 0);
$limit = request('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -51,7 +51,7 @@ class LocationsController extends Controller
$offset = $request->input('offset', 0);
$offset = (($locations) && (request('offset') > $locations->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -39,7 +39,7 @@ class ManufacturersController extends Controller
$offset = request('offset', 0);
$offset = (($manufacturers) && (request('offset') > $manufacturers->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
+14 -9
View File
@@ -27,15 +27,20 @@ class ProfileController extends Controller
foreach ($checkoutRequests as $checkoutRequest) {
$results['rows'][] = [
'image' => $checkoutRequest->itemRequested()->present()->getImageUrl(),
'name' => $checkoutRequest->itemRequested()->present()->name(),
'type' => $checkoutRequest->itemType(),
'qty' => $checkoutRequest->quantity,
'location' => ($checkoutRequest->location()) ? $checkoutRequest->location()->name : null,
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
];
// Make sure the asset and request still exist
if ($checkoutRequest && $checkoutRequest->itemRequested()) {
$results['rows'][] = [
'image' => $checkoutRequest->itemRequested()->present()->getImageUrl(),
'name' => $checkoutRequest->itemRequested()->present()->name(),
'type' => $checkoutRequest->itemType(),
'qty' => $checkoutRequest->quantity,
'location' => ($checkoutRequest->location()) ? $checkoutRequest->location()->name : null,
'expected_checkin' => Helper::getFormattedDateObject($checkoutRequest->itemRequested()->expected_checkin, 'datetime'),
'request_date' => Helper::getFormattedDateObject($checkoutRequest->created_at, 'datetime'),
];
}
}
return $results;
}
@@ -30,7 +30,7 @@ class StatuslabelsController extends Controller
$statuslabels = $statuslabels->TextSearch($request->input('search'));
}
$offset = $request->input('offset', 0);
$offset = (($statuslabels) && (request('offset') > $statuslabels->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
@@ -33,7 +33,7 @@ class SuppliersController extends Controller
$suppliers = $suppliers->TextSearch($request->input('search'));
}
$offset = request('offset', 0);
$offset = (($suppliers) && (request('offset') > $suppliers->count())) ? 0 : request('offset', 0);
$limit = $request->input('limit', 50);
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
+28 -3
View File
@@ -12,6 +12,7 @@ use App\Http\Requests\SaveUserRequest;
use App\Models\Asset;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\AccessoriesTransformer;
class UsersController extends Controller
{
@@ -51,6 +52,7 @@ class UsersController extends Controller
'users.phone',
'users.state',
'users.two_factor_enrolled',
'users.two_factor_optin',
'users.updated_at',
'users.username',
'users.zip',
@@ -85,7 +87,7 @@ class UsersController extends Controller
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$offset = request('offset', 0);
$offset = (($users) && (request('offset') > $users->count())) ? 0 : request('offset', 0);
$limit = request('limit', 20);
switch ($request->input('sort')) {
@@ -147,8 +149,7 @@ class UsersController extends Controller
$users = Company::scopeCompanyables($users);
if ($request->has('search')) {
$users = $users->where('first_name', 'LIKE', '%'.$request->get('search').'%')
->orWhere('last_name', 'LIKE', '%'.$request->get('search').'%')
$users = $users->SimpleNameSearch($request->get('search'))
->orWhere('username', 'LIKE', '%'.$request->get('search').'%')
->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%');
}
@@ -255,6 +256,13 @@ class UsersController extends Controller
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
if ($user->save()) {
if ($request->has('groups')) {
$user->groups()->sync($request->input('groups'));
} else {
$user->groups()->sync(array());
}
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
}
@@ -302,6 +310,23 @@ class UsersController extends Controller
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
/**
* Return JSON containing a list of accessories assigned to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.6.14]
* @param $userId
* @return string JSON
*/
public function accessories($id)
{
$this->authorize('view', User::class);
$user = User::findOrFail($id);
$this->authorize('view', Accessory::class);
$accessories = $user->accessories;
return (new AccessoriesTransformer)->transformAccessories($accessories, $accessories->count());
}
/**
* Reset the user's two-factor status
*
@@ -209,7 +209,7 @@ class AssetModelsController extends Controller
try {
unlink(app('models_upload_path').$old_image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
@@ -246,7 +246,7 @@ class AssetModelsController extends Controller
try {
unlink(public_path().'/uploads/models/'.$model->image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
+14 -2
View File
@@ -326,7 +326,7 @@ class AssetsController extends Controller
unlink(public_path().'/uploads/assets/'.$asset->image);
$asset->image = '';
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
@@ -744,6 +744,18 @@ class AssetsController extends Controller
return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list');
}
public function dueForAudit()
{
$this->authorize('audit', Asset::class);
return view('hardware/audit-due');
}
public function overdueForAudit()
{
$this->authorize('audit', Asset::class);
return view('hardware/audit-overdue');
}
public function auditStore(AssetFileRequest $request, $id)
{
@@ -781,7 +793,7 @@ class AssetsController extends Controller
$filename = 'audit-'.$asset->id.'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$file->move($destinationPath, $filename);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
+54 -30
View File
@@ -209,26 +209,33 @@ class LoginController extends Controller
public function getTwoFactorEnroll()
{
// Make sure the user is logged in
if (!Auth::check()) {
return redirect()->route('login')->with('error', 'You must be logged in.');
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
}
$settings = Setting::getSettings();
$user = Auth::user();
$google2fa = app()->make('PragmaRX\Google2FA\Contracts\Google2FA');
if ($user->two_factor_secret=='') {
$user->two_factor_secret = $google2fa->generateSecretKey(32);
$user->save();
// We wouldn't normally see this page if 2FA isn't enforced via the
// \App\Http\Middleware\CheckForTwoFactor middleware AND if a device isn't enrolled,
// but let's check check anyway in case there's a browser history or back button thing.
// While you can access this page directly, enrolling a device when 2FA isn't enforced
// won't cause any harm.
if (($user->two_factor_secret!='') && ($user->two_factor_enrolled==1)) {
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.already_enrolled'));
}
$google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey();
$user->two_factor_secret = $secret;
$user->save();
$google2fa_url = $google2fa->getQRCodeGoogleUrl(
urlencode(Setting::getSettings()->site_name),
urlencode($user->username),
$user->two_factor_secret
);
return view('auth.two_factor_enroll')->with('google2fa_url', $google2fa_url);
$barcode = new \Com\Tecnick\Barcode\Barcode();
$barcode_obj = $barcode->getBarcodeObj('QRCODE', 'otpauth://totp/'.urlencode($settings->site_name).':'.urlencode($user->username).'?secret='.urlencode($secret).'&issuer=Snipe-IT&period=30', 300, 300, 'black', array(-2, -2, -2, -2));
return view('auth.two_factor_enroll')->with('barcode_obj', $barcode_obj);
}
@@ -240,6 +247,20 @@ class LoginController extends Controller
*/
public function getTwoFactorAuth()
{
// Check that the user is logged in
if (!Auth::check()) {
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
}
$user = Auth::user();
// Check whether there is a device enrolled.
// This *should* be handled via the \App\Http\Middleware\CheckForTwoFactor middleware
// but we're just making sure (in case someone edited the database directly, etc)
if (($user->two_factor_secret=='') || ($user->two_factor_enrolled!=1)) {
return redirect()->route('two-factor-enroll');
}
return view('auth.two_factor');
}
@@ -252,22 +273,25 @@ class LoginController extends Controller
{
if (!Auth::check()) {
return redirect()->route('login')->with('error', 'You must be logged in.');
return redirect()->route('login')->with('error', trans('auth/general.login_prompt'));
}
if (!$request->has('two_factor_secret')) {
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required'));
}
$user = Auth::user();
$secret = $request->get('two_factor_secret');
$google2fa = app()->make('PragmaRX\Google2FA\Contracts\Google2FA');
$valid = $google2fa->verifyKey($user->two_factor_secret, $secret);
$google2fa = new Google2FA();
$secret = $request->input('two_factor_secret');
if ($valid) {
if ($google2fa->verifyKey($user->two_factor_secret, $secret)) {
$user->two_factor_enrolled = 1;
$user->save();
$request->session()->put('2fa_authed', 'true');
return redirect()->route('home')->with('success', 'You are logged in!');
}
return redirect()->route('two-factor')->with('error', 'Invalid two-factor code');
return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.invalid_code'));
}
@@ -290,7 +314,7 @@ class LoginController extends Controller
return redirect()->away($customLogoutUrl);
}
return redirect()->route('login')->with('success', 'You have successfully logged out!');
return redirect()->route('login')->with('success', trans('auth/general.logout.success'));
}
@@ -315,11 +339,11 @@ class LoginController extends Controller
}
/**
* Redirect the user after determining they are locked out.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
* Redirect the user after determining they are locked out.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendLockoutResponse(Request $request)
{
$seconds = $this->limiter()->availableIn(
@@ -330,18 +354,18 @@ class LoginController extends Controller
$message = \Lang::get('auth/message.throttle', ['minutes' => $minutes]);
return redirect()->back()
return redirect()->back()
->withInput($request->only($this->username(), 'remember'))
->withErrors([$this->username() => $message]);
}
/**
* Override the lockout time and duration
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
* Override the lockout time and duration
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request)
{
$lockoutTime = config('auth.throttle.lockout_duration');
@@ -179,7 +179,7 @@ class CategoriesController extends Controller
try {
unlink(app('categories_upload_path').$old_image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
+1 -1
View File
@@ -148,7 +148,7 @@ final class CompaniesController extends Controller
try {
unlink(app('companies_upload_path').$old_image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
@@ -191,7 +191,7 @@ class DepartmentsController extends Controller
try {
unlink(app('departments_upload_path').$old_image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ class ImportsController extends Controller
{
public function index()
{
$this->authorize('create', Asset::class);
$this->authorize('import');
$imports = Import::latest()->get();
$imports = (new ImportsTransformer)->transformImports($imports);
return view('importer/import')->with('imports', $imports);
+4 -15
View File
@@ -179,7 +179,9 @@ class LicensesController extends Controller
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0);
$license->serial = $request->input('serial');
if (Gate::allows('viewKeys', $license)) {
$license->serial = $request->input('serial');
}
$license->termination_date = $request->input('termination_date');
$license->seats = e($request->input('seats'));
$license->manufacturer_id = $request->input('manufacturer_id');
@@ -413,20 +415,7 @@ class LicensesController extends Controller
return redirect()->back()->withInput();
}
// Declare the rules for the form validation
$rules = array(
'note' => 'string',
'notes' => 'string',
);
// Create a new validator instance from our validation rules
$validator = Validator::make(Input::all(), $rules);
// If validation fails, we'll exit the operation now.
if ($validator->fails()) {
// Ooops.. something went wrong
return redirect()->back()->withInput()->withErrors($validator);
}
$return_to = User::find($licenseSeat->assigned_to);
if (!$return_to) {
$return_to = Asset::find($licenseSeat->asset_id);
@@ -438,7 +427,7 @@ class LicensesController extends Controller
// Was the asset updated?
if ($licenseSeat->save()) {
$licenseSeat->logCheckin($return_to, e(request('note')));
$licenseSeat->logCheckin($license, e(request('note')));
if ($backTo=='user') {
return redirect()->route("users.show", $return_to->id)->with('success', trans('admin/licenses/message.checkin.success'));
}
+1 -1
View File
@@ -200,7 +200,7 @@ class LocationsController extends Controller
try {
unlink(app('locations_upload_path').$old_image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
@@ -170,7 +170,7 @@ class ManufacturersController extends Controller
try {
unlink(app('manufacturers_upload_path').$old_image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
@@ -207,7 +207,7 @@ class ManufacturersController extends Controller
try {
unlink(public_path().'/uploads/manufacturers/'.$manufacturer->image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
+4 -2
View File
@@ -49,6 +49,9 @@ class ProfileController extends Controller
$user->last_name = $request->input('last_name');
$user->website = $request->input('website');
$user->gravatar = $request->input('gravatar');
$user->phone = $request->input('phone');
if (!config('app.lock_passwords')) {
$user->locale = $request->input('locale', 'en');
@@ -125,8 +128,7 @@ class ProfileController extends Controller
$rules = array(
'current_password' => 'required',
'password' => Setting::passwordComplexityRulesSaving('store'),
'password_confirm' => 'required|same:password',
'password' => Setting::passwordComplexityRulesSaving('store').'|confirmed',
);
$validator = \Validator::make($request->all(), $rules);
+1 -1
View File
@@ -174,7 +174,7 @@ class SuppliersController extends Controller
try {
unlink(app('suppliers_upload_path').$old_image);
} catch (\Exception $e) {
\Log::error($e);
\Log::info($e);
}
}
+1
View File
@@ -278,6 +278,7 @@ class UsersController extends Controller
$user->activated = $request->input('activated', 0);
$user->jobtitle = $request->input('jobtitle', null);
$user->phone = $request->input('phone');
$user->website = $request->input('website', null);
$user->location_id = $request->input('location_id', null);
$user->company_id = Company::getIdForUser($request->input('company_id', null));
$user->manager_id = $request->input('manager_id', null);
+14
View File
@@ -43,6 +43,20 @@ class ItemImportRequest extends FormRequest
$import->save();
$fieldMappings=[];
if ($import->field_map) {
// This checks to make sure the field header has been mapped.
// If it hasn't been, it will throw an array_flip error
foreach ($import->field_map as $field => $fieldValue) {
$errorMessage = null;
if(is_null($fieldValue)){
$errorMessage = 'All import fields must be mapped.';
$this->errorCallback($import, $field, $errorMessage);
return $this->errors;
}
}
// We submit as csv field: column, but the importer is happier if we flip it here.
$fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER);
// dd($fieldMappings);
+3 -5
View File
@@ -37,7 +37,7 @@ class SaveUserRequest extends Request
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
if ($this->request->get('ldap_import') == false)
{
$rules['password'] = Setting::passwordComplexityRulesSaving('store');
$rules['password'] = Setting::passwordComplexityRulesSaving('store').'|confirmed';
}
break;
}
@@ -46,7 +46,7 @@ class SaveUserRequest extends Request
case 'PUT':
$rules['first_name'] = 'required|string|min:1';
$rules['username'] = 'required_unless:ldap_import,1|string|min:1';
$rules['password'] = Setting::passwordComplexityRulesSaving('update');
$rules['password'] = Setting::passwordComplexityRulesSaving('update').'|confirmed';
break;
// Save only what's passed
@@ -58,9 +58,7 @@ class SaveUserRequest extends Request
default:break;
}
$rules['password_confirm'] = 'sometimes|required_with:password';
return $rules;
}
+1 -2
View File
@@ -29,8 +29,7 @@ class SetupUserRequest extends Request
'last_name' => 'required|string|min:1',
'username' => 'required|string|min:2|unique:users,username,NULL,deleted_at',
'email' => 'email|unique:users,email',
'password' => 'required|min:6',
'password_confirm' => 'required|min:6|same:password',
'password' => 'required|min:6|confirmed',
'email_domain' => 'required|min:4',
];
}
@@ -26,6 +26,18 @@ class ActionlogsTransformer
if ($actionlog->filename!='') {
$icon = e(\App\Helpers\Helper::filetype_icon($actionlog->filename));
}
// This is necessary since we can't escape special characters within a JSON object
if (($actionlog->log_meta) && ($actionlog->log_meta!='')) {
$meta_array = json_decode($actionlog->log_meta);
foreach ($meta_array as $key => $value) {
foreach ($value as $meta_key => $meta_value) {
$clean_meta[$key][$meta_key] = e($meta_value);
}
}
}
$array = [
'id' => (int) $actionlog->id,
'icon' => $icon,
@@ -64,7 +76,7 @@ class ActionlogsTransformer
'note' => ($actionlog->note) ? e($actionlog->note): null,
'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null,
'log_meta' => ($actionlog->log_meta) ? json_decode($actionlog->log_meta): null,
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
];
+4 -1
View File
@@ -24,7 +24,7 @@ class UsersTransformer
$array = [
'id' => (int) $user->id,
'avatar' => e($user->present()->gravatar),
'name' => e($user->first_name).' '.($user->last_name),
'name' => e($user->first_name).' '.e($user->last_name),
'first_name' => e($user->first_name),
'last_name' => e($user->last_name),
'username' => e($user->username),
@@ -35,6 +35,7 @@ class UsersTransformer
] : null,
'jobtitle' => ($user->jobtitle) ? e($user->jobtitle) : null,
'phone' => ($user->phone) ? e($user->phone) : null,
'website' => ($user->website) ? e($user->website) : null,
'address' => ($user->address) ? e($user->address) : null,
'city' => ($user->city) ? e($user->city) : null,
'state' => ($user->state) ? e($user->state) : null,
@@ -53,6 +54,8 @@ class UsersTransformer
'permissions' => $user->decodePermissions(),
'activated' => ($user->activated =='1') ? true : false,
'two_factor_activated' => ($user->two_factor_active()) ? true : false,
'two_factor_enrolled' => ($user->two_factor_active_and_enrolled()) ? true : false,
'assets_count' => (int) $user->assets_count,
'licenses_count' => (int) $user->licenses_count,
'accessories_count' => (int) $user->accessories_count,
+77
View File
@@ -796,6 +796,83 @@ class Asset extends Depreciable
});
}
/**
* Query builder scope for Assets that are due for auditing, based on the assets.next_audit_date
* and settings.audit_warning_days.
*
* This is/will be used in the artisan command snipeit:upcoming-audits and also
* for an upcoming API call for retrieving a report on assets that will need to be audited.
*
* Due for audit soon:
* next_audit_date greater than or equal to now (must be in the future)
* and (next_audit_date - threshold days) <= now ()
*
* Example:
* next_audit_date = May 4, 2025
* threshold for alerts = 30 days
* now = May 4, 2019
*
* @author A. Gianotto <snipe@snipe.net>
* @since v4.6.16
* @param Setting $settings
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeDueForAudit($query, $settings)
{
return $query->whereNotNull('assets.next_audit_date')
->where('assets.next_audit_date', '>=', Carbon::now())
->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $settings->audit_warning_days DAY) <= '".Carbon::now()."'")
->where('assets.archived', '=', 0)
->NotArchived();
}
/**
* Query builder scope for Assets that are OVERDUE for auditing, based on the assets.next_audit_date
* and settings.audit_warning_days. It checks to see if assets.next audit_date is before now
*
* This is/will be used in the artisan command snipeit:upcoming-audits and also
* for an upcoming API call for retrieving a report on overdue assets.
*
* @author A. Gianotto <snipe@snipe.net>
* @since v4.6.16
* @param Setting $settings
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOverdueForAudit($query)
{
return $query->whereNotNull('assets.next_audit_date')
->where('assets.next_audit_date', '<', Carbon::now())
->where('assets.archived', '=', 0)
->NotArchived();
}
/**
* Query builder scope for Assets that are due for auditing OR overdue, based on the assets.next_audit_date
* and settings.audit_warning_days.
*
* This is/will be used in the artisan command snipeit:upcoming-audits and also
* for an upcoming API call for retrieving a report on assets that will need to be audited.
*
* @author A. Gianotto <snipe@snipe.net>
* @since v4.6.16
* @param Setting $settings
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeDueOrOverdueForAudit($query, $settings)
{
return $query->whereNotNull('assets.next_audit_date')
->whereRaw("DATE_SUB(assets.next_audit_date, INTERVAL $settings->audit_warning_days DAY) <= '".Carbon::now()."'")
->where('assets.archived', '=', 0)
->NotArchived();
}
/**
* Query builder scope for Archived assets
*
+1 -1
View File
@@ -76,7 +76,7 @@ class Consumable extends SnipeModel
*
* @var array
*/
protected $searchableAttributes = ['name', 'order_number', 'purchase_cost', 'purchase_date'];
protected $searchableAttributes = ['name', 'order_number', 'purchase_cost', 'purchase_date', 'item_no'];
/**
* The relations and their attributes that should be included when searching the model.
+64 -5
View File
@@ -69,6 +69,7 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
'email' => 'email|nullable',
'password' => 'required|min:6',
'locale' => 'max:10|nullable',
'website' => 'url|nullable',
];
use Searchable;
@@ -389,7 +390,11 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
}
/**
* Check whether two-factor authorization is required and the user has activated it
* Check whether two-factor authorization is requiredfor this user
*
* 0 = 2FA disabled
* 1 = 2FA optional
* 2 = 2FA universally required
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
@@ -398,10 +403,45 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
*/
public function two_factor_active () {
if (Setting::getSettings()->two_factor_enabled !='0') {
if (($this->two_factor_optin =='1') && ($this->two_factor_enrolled)) {
return true;
}
// If the 2FA is optional and the user has opted in
if ((Setting::getSettings()->two_factor_enabled =='1') && ($this->two_factor_optin =='1'))
{
return true;
}
// If the 2FA is required for everyone so is implicitly active
elseif (Setting::getSettings()->two_factor_enabled =='2')
{
return true;
}
return false;
}
/**
* Check whether two-factor authorization is required and the user has activated it
* and enrolled a device
*
* 0 = 2FA disabled
* 1 = 2FA optional
* 2 = 2FA universally required
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.6.14]
*
* @return bool
*/
public function two_factor_active_and_enrolled () {
// If the 2FA is optional and the user has opted in and is enrolled
if ((Setting::getSettings()->two_factor_enabled =='1') && ($this->two_factor_optin =='1') && ($this->two_factor_enrolled =='1'))
{
return true;
}
// If the 2FA is required for everyone and the user has enrolled
elseif ((Setting::getSettings()->two_factor_enabled =='2') && ($this->two_factor_enrolled))
{
return true;
}
return false;
@@ -413,6 +453,25 @@ class User extends SnipeModel implements AuthenticatableContract, CanResetPasswo
return json_decode($this->permissions, true);
}
/**
* Query builder scope to search user by name with spaces in it.
* We don't use the advancedTextSearch() scope because that searches
* all of the relations as well, which is more than what we need.
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param array $terms The search terms
* @return \Illuminate\Database\Query\Builder
*/
public function scopeSimpleNameSearch($query, $search) {
$query = $query->where('first_name', 'LIKE', '%'.$search.'%')
->orWhere('last_name', 'LIKE', '%'.$search.'%')
->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$search%", "%$search%"]);
return $query;
}
/**
* Run additional, advanced searches.
*
@@ -0,0 +1,67 @@
<?php
namespace App\Notifications;
use App\Models\Setting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class SendUpcomingAuditNotification extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($params, $threshold)
{
$this->assets = $params;
$this->threshold = $threshold;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return $notifyBy = ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$message = (new MailMessage)->markdown('notifications.markdown.upcoming-audits',
[
'assets' => $this->assets,
'threshold' => $this->threshold,
])
->subject(trans_choice('mail.upcoming-audits', $this->assets->count(), ['count' => $this->assets->count(), 'threshold' => $this->threshold]));
return $message;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
+19 -8
View File
@@ -13,16 +13,27 @@ class LicensePolicy extends CheckoutablePermissionsPolicy
return 'licenses';
}
/**
* Determine whether the user can view license keys
*
* @param \App\Models\User $user
* @param \App\Models\License $license
* @return mixed
*/
/**
* Determine whether the user can view license keys.
* This gets a little tricky, UX/logic-wise. If a user has the ability
* to create a license (which requires a product key), shouldn't they
* have the ability to see the product key as well?
*
* Example: I create the license, realize I need to change
* something (maybe I got the product key wrong), and now I can never
* see/edit that product key.
*
* @see https://github.com/snipe/snipe-it/issues/6956
* @param \App\Models\User $user
* @param \App\Models\License $license
* @return mixed
*/
public function viewKeys(User $user, License $license = null)
{
return $user->hasAccess('licenses.keys');
if ($user->hasAccess('licenses.keys') || $user->hasAccess('licenses.create') || $user->hasAccess('licenses.edit')) {
return true;
}
return false;
}
}
+7 -5
View File
@@ -53,7 +53,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can view the accessory.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function view(User $user, $item = null)
@@ -64,7 +64,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can create accessories.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function create(User $user)
@@ -75,7 +75,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can update the accessory.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function update(User $user, $item = null)
@@ -86,7 +86,7 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can delete the accessory.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function delete(User $user, $item = null)
@@ -97,11 +97,13 @@ abstract class SnipePermissionsPolicy
/**
* Determine whether the user can manage the accessory.
*
* @param \App\User $user
* @param \App\Models\User $user
* @return mixed
*/
public function manage(User $user, $item = null)
{
return $user->hasAccess($this->columnName().'.edit');
}
}
+273
View File
@@ -0,0 +1,273 @@
<?php
namespace App\Presenters;
use App\Models\CustomField;
use DateTime;
/**
* Class AssetPresenter
* @package App\Presenters
*/
class AssetAuditPresenter extends Presenter
{
/**
* Json Column Layout for bootstrap table
* @return string
*/
public static function dataTableLayout()
{
$layout = [
[
"field" => "id",
"searchable" => false,
"sortable" => true,
"switchable" => true,
"title" => trans('general.id'),
"visible" => false
], [
"field" => "company",
"searchable" => true,
"sortable" => true,
"switchable" => true,
"title" => trans('general.company'),
"visible" => false,
"formatter" => 'assetCompanyObjFilterFormatter'
], [
"field" => "name",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/form.name'),
"visible" => true,
"formatter" => "hardwareLinkFormatter"
], [
"field" => "image",
"searchable" => false,
"sortable" => true,
"switchable" => true,
"title" => trans('admin/hardware/table.image'),
"visible" => false,
"formatter" => "imageFormatter"
], [
"field" => "asset_tag",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/table.asset_tag'),
"visible" => true,
"formatter" => "hardwareLinkFormatter"
], [
"field" => "serial",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/form.serial'),
"visible" => true,
"formatter" => "hardwareLinkFormatter"
], [
"field" => "model",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/form.model'),
"visible" => true,
"formatter" => "modelsLinkObjFormatter"
], [
"field" => "model_number",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/models/table.modelnumber'),
"visible" => false
], [
"field" => "category",
"searchable" => true,
"sortable" => true,
"title" => trans('general.category'),
"visible" => false,
"formatter" => "categoriesLinkObjFormatter"
], [
"field" => "status_label",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/table.status'),
"visible" => true,
"formatter" => "statuslabelsLinkObjFormatter"
], [
"field" => "assigned_to",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/form.checkedout_to'),
"visible" => true,
"formatter" => "polymorphicItemFormatter"
], [
"field" => "location",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/table.location'),
"visible" => true,
"formatter" => "deployedLocationFormatter"
], [
"field" => "rtd_location",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/form.default_location'),
"visible" => false,
"formatter" => "deployedLocationFormatter"
], [
"field" => "manufacturer",
"searchable" => true,
"sortable" => true,
"title" => trans('general.manufacturer'),
"visible" => false,
"formatter" => "manufacturersLinkObjFormatter"
], [
"field" => "purchase_date",
"searchable" => true,
"sortable" => true,
"visible" => false,
"title" => trans('general.purchase_date'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "purchase_cost",
"searchable" => true,
"sortable" => true,
"visible" => false,
"title" => trans('general.purchase_cost'),
"footerFormatter" => 'sumFormatter',
], [
"field" => "order_number",
"searchable" => true,
"sortable" => true,
"visible" => false,
"title" => trans('general.order_number'),
'formatter' => "orderNumberObjFilterFormatter"
], [
"field" => "eol",
"searchable" => false,
"sortable" => false,
"visible" => false,
"title" => trans('general.eol'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "warranty_months",
"searchable" => true,
"sortable" => true,
"visible" => false,
"title" => trans('admin/hardware/form.warranty')
],[
"field" => "warranty_expires",
"searchable" => false,
"sortable" => false,
"visible" => false,
"title" => trans('admin/hardware/form.warranty_expires'),
"formatter" => "dateDisplayFormatter"
],[
"field" => "notes",
"searchable" => true,
"sortable" => true,
"visible" => false,
"title" => trans('general.notes'),
], [
"field" => "checkout_counter",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('general.checkouts_count')
],[
"field" => "checkin_counter",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('general.checkins_count')
], [
"field" => "requests_counter",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('general.user_requests_count')
], [
"field" => "created_at",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('general.created_at'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "updated_at",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('general.updated_at'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "last_checkout",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('admin/hardware/table.checkout_date'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "expected_checkin",
"searchable" => false,
"sortable" => true,
"visible" => false,
"title" => trans('admin/hardware/form.expected_checkin'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "last_audit_date",
"searchable" => false,
"sortable" => true,
"visible" => true,
"title" => trans('general.last_audit'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "next_audit_date",
"searchable" => false,
"sortable" => true,
"visible" => true,
"title" => trans('general.next_audit_date'),
"formatter" => "dateDisplayFormatter"
],
];
// This looks complicated, but we have to confirm that the custom fields exist in custom fieldsets
// *and* those fieldsets are associated with models, otherwise we'll trigger
// javascript errors on the bootstrap tables side of things, since we're asking for properties
// on fields that will never be passed through the REST API since they're not associated with
// models. We only pass the fieldsets that pertain to each asset (via their model) so that we
// don't junk up the REST API with tons of custom fields that don't apply
$fields = CustomField::whereHas('fieldset', function ($query) {
$query->whereHas('models');
})->get();
foreach ($fields as $field) {
$layout[] = [
"field" => 'custom_fields.'.$field->convertUnicodeDbSlug(),
"searchable" => true,
"sortable" => true,
"visible" => false,
"switchable" => true,
"title" => ($field->field_encrypted=='1') ?'<i class="fa fa-lock"></i> '.e($field->name) : e($field->name),
"formatter" => "customFieldsFormatter"
];
}
$layout[] = [
"field" => "actions",
"searchable" => false,
"sortable" => false,
"switchable" => false,
"title" => trans('table.actions'),
"formatter" => "hardwareAuditFormatter",
];
return json_encode($layout);
}
}
+8 -4
View File
@@ -391,7 +391,7 @@ class AssetPresenter extends Presenter
public function eol_date()
{
if (( $this->purchase_date ) && ( $this->model )) {
if (( $this->purchase_date ) && ( $this->model ) && ($this->model->model->eol) ) {
$date = date_create($this->purchase_date);
date_add($date, date_interval_create_from_date_string($this->model->model->eol . ' months'));
return date_format($date, 'Y-m-d');
@@ -492,9 +492,13 @@ class AssetPresenter extends Presenter
*/
public function warrantee_expires()
{
$date = date_create($this->purchase_date);
date_add($date, date_interval_create_from_date_string($this->warranty_months . ' months'));
return date_format($date, 'Y-m-d');
if (($this->purchase_date) && ($this->warranty_months)) {
$date = date_create($this->purchase_date);
date_add($date, date_interval_create_from_date_string($this->warranty_months . ' months'));
return date_format($date, 'Y-m-d');
}
return false;
}
/**
+3 -3
View File
@@ -226,14 +226,14 @@ class UserPresenter extends Presenter
[
"field" => "two_factor_enrolled",
"searchable" => false,
"sortable" => false,
"sortable" => true,
"switchable" => true,
"title" => trans('admin/users/general.two_factor_enrolled'),
"visible" => false,
'formatter' => 'trueFalseFormatter'
],
[
"field" => "two_factor_active",
"field" => "two_factor_activated",
"searchable" => false,
"sortable" => false,
"switchable" => true,
@@ -246,7 +246,7 @@ class UserPresenter extends Presenter
"searchable" => false,
"sortable" => true,
"switchable" => true,
"title" => trans('general.activated'),
"title" => trans('general.login_enabled'),
"visible" => true,
'formatter' => 'trueFalseFormatter'
],
+8
View File
@@ -113,6 +113,14 @@ class AuthServiceProvider extends ServiceProvider
});
// Can the user import CSVs?
Gate::define('import', function ($user) {
if ($user->hasAccess('import') ) {
return true;
}
});
# -----------------------------------------
# Reports
# -----------------------------------------
+3 -2
View File
@@ -13,7 +13,7 @@
"doctrine/inflector": "1.1.*",
"doctrine/instantiator": "1.0.*",
"eduardokum/laravel-mail-auto-embed": "^1.0",
"erusev/parsedown": "^1.6",
"erusev/parsedown": "^1.7.3",
"fideloper/proxy": "^3.3",
"intervention/image": "^2.3",
"javiereguiluz/easyslugger": "^1.0",
@@ -27,7 +27,8 @@
"patchwork/utf8": "~1.2",
"phpdocumentor/reflection-docblock": "3.2.2",
"phpspec/prophecy": "1.6.2",
"pragmarx/google2fa": "^1.0",
"pragmarx/google2fa": "^5.0",
"pragmarx/google2fa-laravel": "^0.3.0",
"predis/predis": "^1.1",
"rollbar/rollbar-laravel": "2.4.1",
"schuppo/password-strength": "~1.5",
Generated
+394 -266
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -291,7 +291,7 @@ return [
Collective\Html\HtmlServiceProvider::class,
Spatie\Backup\BackupServiceProvider::class,
Fideloper\Proxy\TrustedProxyServiceProvider::class,
PragmaRX\Google2FA\Vendor\Laravel\ServiceProvider::class,
PragmaRX\Google2FALaravel\ServiceProvider::class,
Laravel\Passport\PassportServiceProvider::class,
Laravel\Tinker\TinkerServiceProvider::class,
Unicodeveloper\DumbPassword\DumbPasswordServiceProvider::class,
+9
View File
@@ -27,6 +27,15 @@ return array(
)
),
'CSV Import' => array(
array(
'permission' => 'import',
'label' => '',
'note' => 'This will allow users to import even if access to users, assets, etc is denied elsewhere.',
'display' => true,
)
),
'Reports' => array(
array(
'permission' => 'reports.view',
+5 -5
View File
@@ -1,10 +1,10 @@
<?php
return array (
'app_version' => 'v4.6.10',
'full_app_version' => 'v4.6.10 - build 3975-gaa1e06f02',
'build_version' => '3975',
'app_version' => 'v4.6.18',
'full_app_version' => 'v4.6.18 - build 4036-g21ec67053',
'build_version' => '4036',
'prerelease_version' => '',
'hash_version' => 'gaa1e06f02',
'full_hash' => 'v4.6.10-5-gaa1e06f02',
'hash_version' => 'g21ec67053',
'full_hash' => 'v4.6.18-9-g21ec67053',
'branch' => 'master',
);
@@ -15,7 +15,7 @@ class AddPrefixToSettings extends Migration {
//
Schema::table('settings', function(Blueprint $table) {
$table->string('auto_increment_prefix')->default(0);
$table->string('auto_increment_prefix')->nullable()->default(NULL);
});
}
@@ -0,0 +1,30 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class ChangeAutoIncrementPrefixToNullable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->string('auto_increment_prefix')->nullable()->default(null)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
@@ -0,0 +1,30 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AutoIncrementBackToString extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->string('auto_increment_prefix')->nullable()->default(null)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
@@ -0,0 +1,33 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class MakeSerialNullable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform();
$platform->registerDoctrineTypeMapping('enum', 'string');
Schema::table('assets', function (Blueprint $table) {
$table->string('serial')->nullable()->default(null)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
@@ -0,0 +1,64 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class MakeFieldsNullableForIntegrity extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('locations', function (Blueprint $table) {
$table->string('city')->nullable()->default(null)->change();
$table->string('state')->nullable()->default(null)->change();
$table->string('country')->nullable()->default(null)->change();
$table->integer('user_id')->nullable()->default(null)->change();
$table->string('address')->nullable()->default(null)->change();
$table->string('address2')->nullable()->default(null)->change();
});
Schema::table('users', function (Blueprint $table) {
$table->string('last_name')->nullable()->default(null)->change();
});
Schema::table('suppliers', function (Blueprint $table) {
$table->integer('user_id')->nullable()->default(null)->change();
});
Schema::table('status_labels', function (Blueprint $table) {
$table->integer('user_id')->nullable()->default(null)->change();
});
Schema::table('models', function (Blueprint $table) {
$table->integer('user_id')->nullable()->default(null)->change();
$table->integer('manufacturer_id')->nullable()->default(null)->change();
$table->integer('category_id')->nullable()->default(null)->change();
});
Schema::table('licenses', function (Blueprint $table) {
$table->integer('user_id')->nullable()->default(null)->change();
$table->boolean('maintained')->nullable()->default(null)->change();
});
Schema::table('depreciations', function (Blueprint $table) {
$table->integer('user_id')->nullable()->default(null)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
+3
View File
@@ -43,5 +43,8 @@ class DatabaseSeeder extends Seeder
\Log::info($output);
Model::reguard();
DB::table('imports')->truncate();
}
}
+2 -3
View File
@@ -5,7 +5,7 @@ if [ -z "$APP_KEY" ]
then
echo "Please re-run this container with an environment variable \$APP_KEY"
echo "An example APP_KEY you could use is: "
php artisan key:generate --show
/var/www/html/artisan key:generate --show
exit
fi
@@ -47,5 +47,4 @@ then
cp -ax /var/www/html/vendor/laravel/passport/database/migrations/* /var/www/html/database/migrations/
fi
. /etc/apache2/envvars
exec apache2 -DNO_DETACH < /dev/null
exec supervisord -c /supervisord.conf
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env python
# A supervisor event listener which terminates supervisord if any of its child
# processes enter the FATAL state.
# https://stackoverflow.com/a/37527488/119527
import os
import signal
from supervisor import childutils
def main():
while True:
headers, payload = childutils.listener.wait()
childutils.listener.ok()
if headers['eventname'] != 'PROCESS_STATE_FATAL':
continue
os.kill(os.getppid(), signal.SIGTERM)
if __name__ == "__main__":
main()
+27
View File
@@ -0,0 +1,27 @@
[supervisord]
nodaemon=true
[program:apache]
; https://advancedweb.hu/2018/07/03/supervisor_docker/
command=apache2ctl -DFOREGROUND
killasgroup=true
stopasgroup=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:run_schedule]
; Simply run the Laravel command scheduler every minute
command=/bin/bash -c "while true; do /var/www/html/artisan schedule:run; sleep 1m; done"
user=docker
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
; https://stackoverflow.com/a/37527488/119527
[eventlistener:exit_on_any_fatal]
command=supervisor-exit-event-listener
events=PROCESS_STATE_FATAL
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+8047 -26
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+26 -26
View File
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -1,14 +1,14 @@
{
"/js/build/vue.js": "/js/build/vue.js?id=af0a53aa1b89d0e19039",
"/js/build/vue.js": "/js/build/vue.js?id=96f90510b797ac27a94b",
"/css/AdminLTE.css": "/css/AdminLTE.css?id=5e72463a66acbcc740d5",
"/css/app.css": "/css/app.css?id=407edb63cc6b6dc62405",
"/css/overrides.css": "/css/overrides.css?id=2d81c3704393bac77011",
"/js/build/vue.js.map": "/js/build/vue.js.map?id=79fce5e6515d8a4cc760",
"/js/build/vue.js.map": "/js/build/vue.js.map?id=423f16f63b86abd6b196",
"/css/AdminLTE.css.map": "/css/AdminLTE.css.map?id=0be7790b84909dca6a0a",
"/css/app.css.map": "/css/app.css.map?id=96b5c985e860716e6a16",
"/css/overrides.css.map": "/css/overrides.css.map?id=f7ce9ca49027594ac402",
"/css/dist/all.css": "/css/dist/all.css?id=98db4e9b7650453c8b00",
"/js/dist/all.js": "/js/dist/all.js?id=a3a656ed6316d4c4efe7",
"/js/dist/all.js": "/js/dist/all.js?id=269c4bb386c5ae0dcce4",
"/css/build/all.css": "/css/build/all.css?id=98db4e9b7650453c8b00",
"/js/build/all.js": "/js/build/all.js?id=a3a656ed6316d4c4efe7"
}
"/js/build/all.js": "/js/build/all.js?id=269c4bb386c5ae0dcce4"
}
@@ -40,9 +40,8 @@ tr {
</div>
</div>
</div>
<div class="alert col-md-12"
<div class="alert col-md-12" style="text-align:left"
:class="alertClass"
style="text-align:left"
v-if="statusText">
{{ this.statusText }}
</div>
@@ -84,7 +83,6 @@ tr {
<div class="alert col-md-12" style="padding-top: 20px;"
:class="alertClass"
style="text-align:left"
v-if="statusText">
{{ this.statusText }}
</div>
+12 -1
View File
@@ -260,7 +260,18 @@ $(document).ready(function () {
}
function formatDataSelection (datalist) {
return datalist.text;
// This a heinous workaround for a known bug in Select2.
// Without this, the rich selectlists are vulnerable to XSS.
// Many thanks to @uberbrady for this fix. It ain't pretty,
// but it resolves the issue until Select2 addresses it on their end.
//
// Bug was reported in 2016 :{
// https://github.com/select2/select2/issues/4587
return datalist.text.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
// This handles the radio button selectors for the checkout-to-foo options
+4 -1
View File
@@ -171,5 +171,8 @@ function formatDatalist (datalist) {
}
function formatDataSelection (datalist) {
return datalist.text;
return datalist.text.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
+6 -6
View File
@@ -5,7 +5,7 @@ return array(
'ad_domain' => 'مجال الدليل النشط',
'ad_domain_help' => 'هذا هو أحيانا نفس نطاق البريد الإلكتروني الخاص بك، ولكن ليس دائما.',
'admin_cc_email' => 'نسخة اضافية للبريد الإكتروني',
'admin_cc_email_help' => 'If you would like to send a copy of checkin/checkout emails that are sent to users to an additional email account, enter it here. Otherwise leave this field blank.',
'admin_cc_email_help' => 'إذا كنت ترغب في إرسال نسخة من رسائل البريد الإلكتروني لتسجيل الدخول / الخروج التي يتم إرسالها إلى المستخدمين إلى حساب بريد إلكتروني إضافي، فقم بإدخالها هنا. خلاف ذلك، اترك هذه الخانة فارغة.',
'is_ad' => 'هذا هو ملقم أكتيف ديركتوري',
'alert_email' => 'إرسال تنبيهات إلى',
'alerts_enabled' => 'التنبيهان ممكنه',
@@ -27,8 +27,8 @@ return array(
'custom_css_help' => 'أدخل أي تخصيصات CSS ترغب في استخدامها. لا تقم باضافة &lt;style&gt;&lt;/style&gt;.',
'custom_forgot_pass_url' => 'رابط مخصص لاعادة تعيين كلمة المرور',
'custom_forgot_pass_url_help' => 'يحل هذا محل الرابط الخاص باعادة تعيين كلمة المرور على شاشة تسجيل الدخول، وهو مفيد لتوجيه الأشخاص إلى وظيفة إعادة تعيين كلمة مرور من خلال LDAP الداخلي أو المستضاف. وهذا سوف يعطل وظيفة إعادة تعيين كلمة مرور المحلية.',
'dashboard_message' => 'Dashboard Message',
'dashboard_message_help' => 'This text will appear on the dashboard for anyone with permission to view the dashboard.',
'dashboard_message' => 'رسالة لوحة المعلومات',
'dashboard_message_help' => 'سيظهر هذا النص على لوحة التحكم لأي شخص لديه صلاحية عرض لوحة التحكم.',
'default_currency' => 'العملة الافتراضية',
'default_eula_text' => 'اتفاقية ترخيص المستخدم النهائي الافتراضية',
'default_language' => 'اللغة الافتراضية',
@@ -80,11 +80,11 @@ return array(
'load_remote_help_text' => 'هذا قنص إيت تثبيت يمكن تحميل البرامج النصية من العالم الخارجي.',
'login_note' => 'تسجيل الدخول ملاحظة',
'login_note_help' => 'اختيارياً تضمين بعض الجمل على شاشة تسجيل الدخول الخاصة بك، على سبيل المثال لمساعدة الناس الذين وجدوا أحد الأجهزة المفقودة أو المسروقة. يقبل هذا الحقل <a href="https://help.github.com/articles/github-flavored-markdown/">بتطبيق نمط الكتابة من Github</a>',
'login_remote_user_text' => 'Remote User login options',
'login_remote_user_enabled_text' => 'Enable Login with Remote User Header',
'login_remote_user_text' => 'خيارات تسجيل دخول المستخدم عن بعد',
'login_remote_user_enabled_text' => 'تمكين تسجيل الدخول باستخدام الخانة الرئيسية للمستخدم عن بعد',
'login_remote_user_enabled_help' => 'This option enables Authentication via the REMOTE_USER header according to the "Common Gateway Interface (rfc3875)"',
'login_common_disabled_text' => 'تعطيل آليات المصادقة الأخرى',
'login_common_disabled_help' => 'This option disables other authentication mechanisms. Just enable this option if you are sure that your REMOTE_USER login is already working',
'login_common_disabled_help' => 'يعمل هذا الخيار على تعطيل آليات المصادقة الأخرى. ما عليك سوى تمكين هذا الخيار إذا كنت متأكدًا من أن تسجيل الدخول إلى المستخدم_عن_بعد يعمل بالفعل',
'login_remote_user_custom_logout_url_text' => 'عنوان صفحة مخصص لتسجيل الخروج',
'login_remote_user_custom_logout_url_help' => 'If a url is provided here, users will get redirected to this URL after the user logs out of Snipe-IT. This is useful to close the user sessions of your Authentication provider correctly.',
'logo' => 'شعار',
+1 -1
View File
@@ -88,7 +88,7 @@ return array(
'login_remote_user_custom_logout_url_text' => 'Персонализиран адрес за изход',
'login_remote_user_custom_logout_url_help' => 'If a url is provided here, users will get redirected to this URL after the user logs out of Snipe-IT. This is useful to close the user sessions of your Authentication provider correctly.',
'logo' => 'Лого',
'logo_print_assets' => 'Use in Print',
'logo_print_assets' => 'Използвай при пчат',
'logo_print_assets_help' => 'Use branding on printable asset lists ',
'full_multiple_companies_support_help_text' => 'Ограничаване на потребителите (включително административните) до активите на собствената им компания.',
'full_multiple_companies_support_text' => 'Поддръжка на множество компании',
+1 -1
View File
@@ -29,7 +29,7 @@ return array(
'months' => 'Monate',
'name' => 'Asset Name',
'notes' => 'Notizen',
'order' => 'Artikelnummer',
'order' => 'Auftragsnummer',
'qr' => 'QR-Code',
'requestable' => 'Benutzer dürfen dieses Asset anfordern',
'select_statustype' => 'Status Typ auswählen',
+2 -2
View File
@@ -145,7 +145,7 @@
'no_results' => 'Keine Treffer.',
'no' => 'Nein',
'notes' => 'Notizen',
'order_number' => 'Artikelnummer',
'order_number' => 'Auftragsnummer',
'page_menu' => 'zeige _MENU_ Einträge',
'pagination_info' => 'Zeige _START_ bis _END_ von _TOTAL_ Einträgen',
'pending' => 'Ausstehende',
@@ -215,7 +215,7 @@
'users' => 'Benutzer',
'viewassets' => 'Zugeordnete Assets anzeigen',
'website' => 'Webseite',
'welcome' => 'Wilkommen, :name',
'welcome' => 'Willkommen, :name',
'years' => 'Jahre',
'yes' => 'Ja',
'zip' => 'Postleitzahl',
+1 -1
View File
@@ -66,6 +66,6 @@ return array(
'user' => 'Benutzer',
'username' => 'Benutzername',
'welcome' => 'Wilkommen, :name',
'welcome_to' => 'Willkommen bei: Web!',
'welcome_to' => 'Willkommen bei :web!',
'your_credentials' => 'Ihre Snipe-IT Anmeldedaten',
);
@@ -5,8 +5,8 @@ return array(
'about_categories' => 'Kategori membantu mengatur barang-barang anda. Beberapa contoh kategori seperti &quot;Desktop&quot;, &quot;Laptop&quot;, &quot;Mobile Phones&quot;, &quot;Tablet&quot;, dan sebagainya, namun Anda tetap dapat menggunakan kategori dengan cara apapun yang masuk akal bagi Anda.',
'asset_categories' => 'Kategori Aset',
'category_name' => 'Nama Kategori',
'checkin_email' => 'Send email to user on checkin/checkout.',
'checkin_email_notification' => 'This user will be sent an email on checkin/checkout.',
'checkin_email' => 'Kirim email ke pengguna saat checkin/checkout.',
'checkin_email_notification' => 'Pengguna ini akan dikirimi email saat checkin/checkout.',
'clone' => 'Salin Kategori',
'create' => 'Buat Kategori',
'edit' => 'Edit Kategori',
@@ -29,5 +29,5 @@ return array(
'create_fieldset' => 'Atur bidang baru',
'create_field' => 'Kostum field baru',
'value_encrypted' => 'Nilai field ini dienkripsi dalam database. Hanya pengguna admin yang bisa melihat nilai dekripsi',
'show_in_email' => 'Include the value of this field in checkout emails sent to the user? Encrypted fields cannot be included in emails.',
'show_in_email' => 'Sertakan nilai bidang ini dalam semua email keluar yang dikirim ke pengguna? Bidang yang terenkripsi tidak dapat disertakan dalam email.',
);
@@ -5,7 +5,7 @@ return array(
'about_assets_text' => 'Aset adalah item yang dilacak dengan nomor seri atau tag aset. Mereka cenderung menjadi item nilai lebih tinggi di mana mengidentifikasi item tertentu yang penting.',
'archived' => 'Diarsipkan',
'asset' => 'Aset',
'bulk_checkout' => 'Checkout Assets',
'bulk_checkout' => 'Pengeluaran Aset',
'checkin' => 'Mendaftar aset',
'checkout' => 'Periksa aset',
'clone' => 'Gandakan aset',
@@ -16,8 +16,8 @@ return array(
),
'restore' => array(
'error' => 'Manufacturer was not restored, please try again',
'success' => 'Manufacturer restored successfully.'
'error' => 'Pabrikan tidak dapat dipulihkan, silakan coba kembali',
'success' => 'Pabrikan berhasil dipulihkan.'
),
'delete' => array(
@@ -14,5 +14,5 @@ return array(
'view_models' => 'Lihat model',
'fieldset' => 'Fieldset',
'no_custom_field' => 'Tidak ada bidang khusus',
'add_default_values' => 'Add default values',
'add_default_values' => 'Tambah nilai default',
);
+22 -22
View File
@@ -4,8 +4,8 @@ return array(
'ad' => 'Direktori Berkas',
'ad_domain' => 'Domain Direktori Aktif',
'ad_domain_help' => 'Terkadang ini sama dengan domain email anda, tapi tidak selalu.',
'admin_cc_email' => 'CC Email',
'admin_cc_email_help' => 'If you would like to send a copy of checkin/checkout emails that are sent to users to an additional email account, enter it here. Otherwise leave this field blank.',
'admin_cc_email' => 'Tembusan Email',
'admin_cc_email_help' => 'Jika Anda ingin mengirim salinan email checkin / checkout yang dikirimkan ke pengguna akun email tambahan, masukkan di sini. Jika tidak, biarkan bidang ini kosong.',
'is_ad' => 'Ini adalah server aktif direktori',
'alert_email' => 'Kirim peringatan kepada',
'alerts_enabled' => 'Peringatan Email Diaktifkan',
@@ -80,16 +80,16 @@ return array(
'load_remote_help_text' => 'Instalasi Snipe-IT ini bisa membuat skrip dari dunia luar.',
'login_note' => 'Catatan Masuk',
'login_note_help' => 'Opsional memasukkan beberapa kalimat di layar masuk anda, misalnya membantu orang-orang yang telah menemukan perangkat yang hilang atau dicuri. Bidang ini menerima <a href="https://help.github.com/articles/github-flavored-markdown/">Rasa penurunan Github</a>',
'login_remote_user_text' => 'Remote User login options',
'login_remote_user_enabled_text' => 'Enable Login with Remote User Header',
'login_remote_user_enabled_help' => 'This option enables Authentication via the REMOTE_USER header according to the "Common Gateway Interface (rfc3875)"',
'login_common_disabled_text' => 'Disable other authentication mechanisms',
'login_common_disabled_help' => 'This option disables other authentication mechanisms. Just enable this option if you are sure that your REMOTE_USER login is already working',
'login_remote_user_custom_logout_url_text' => 'Custom logout URL',
'login_remote_user_custom_logout_url_help' => 'If a url is provided here, users will get redirected to this URL after the user logs out of Snipe-IT. This is useful to close the user sessions of your Authentication provider correctly.',
'login_remote_user_text' => 'Opsi masuk untuk Pengguna Jarak Jauh',
'login_remote_user_enabled_text' => 'Aktifkan Login dengan Remote User Header',
'login_remote_user_enabled_help' => 'Opsi ini memungkinkan Otentikasi melalui header REMOTE_USER sesuai dengan "Common Gateway Interface (rfc3875)"',
'login_common_disabled_text' => 'Nonaktifkan mekanisme autentikasi lainnya',
'login_common_disabled_help' => 'Opsi ini menonaktifkan mekanisme otentikasi lainnya. Cukup aktifkan opsi ini jika Anda yakin bahwa login REMOTE_USER Anda sudah berfungsi',
'login_remote_user_custom_logout_url_text' => 'Penyesuaian URL logout',
'login_remote_user_custom_logout_url_help' => 'Jika diisi, pengguna akan dialihkan ke URL ini setelah keluar atau logout. Berfungsi untuk menutup sesi pengguna dengan sempurna.',
'logo' => 'Logo',
'logo_print_assets' => 'Use in Print',
'logo_print_assets_help' => 'Use branding on printable asset lists ',
'logo_print_assets' => 'Gunakan saat mencetak',
'logo_print_assets_help' => 'Menggunakan branding Anda pada semua hasil cetak daftar aset ',
'full_multiple_companies_support_help_text' => 'Membatasi pengguna (termasuk admin) yang ditugaskan ke perusahaan untuk aset perusahaan mereka.',
'full_multiple_companies_support_text' => 'Dukungan Banyak Perusahaan',
'show_in_model_list' => 'Tunjukkan di Model dalam urutan ke bawah',
@@ -111,21 +111,21 @@ return array(
'show_alerts_in_menu' => 'Tampilkan peringatan di menu atas',
'show_archived_in_list' => 'Aset yang di Arsipkan',
'show_archived_in_list_text' => 'Tampilkan aset yang di arsipkan dalam daftar "semua aset"',
'show_images_in_email' => 'Show images in emails',
'show_images_in_email_help' => 'Uncheck this box if your Snipe-IT installation is behind a VPN or closed network and users outside the network will not be able to load images served from this installation in their emails.',
'show_images_in_email' => 'Tampilkan gambar dalam email',
'show_images_in_email_help' => 'Hapus centang kotak ini jika instalasi Snipe-IT Anda berada di belakang VPN atau jaringan tertutup dan pengguna di luar jaringan tidak akan dapat memuat gambar yang disajikan dari instalasi ini di email mereka.',
'site_name' => 'Nama Situs',
'slack_botname' => 'Nama Bot Slack',
'slack_channel' => 'Saluran Slack',
'slack_endpoint' => 'Titik Akhir Slack',
'slack_integration' => 'Pengaturan Slack',
'slack_integration_help' => 'Integrasi Slack bersifat opsional, namun titik akhir dan saluran diperlukan jika anda ingin menggunakannya. Untuk mengkonfigurasi integrasi Slack, anda harus terlebih dahulu<a href=":slack_link" target="_new">buat webhook masuk</a> di akun slack anda.',
'slack_integration_help_button' => 'Once you have saved your Slack information, a test button will appear.',
'slack_test_help' => 'Test whether your Slack integration is configured correctly. YOU MUST SAVE YOUR UPDATED SLACK SETTINGS FIRST.',
'slack_integration_help_button' => 'Setelah Anda menyimpan informasi Slack Anda, tombol tes akan muncul.',
'slack_test_help' => 'Uji apakah integrasi Slack Anda dikonfigurasi dengan benar. ANDA HARUS MENYIMPAN SETELAN PENGATURAN KESELAMATAN ANDA PERTAMA.',
'snipe_version' => 'Versi Snipe-IT',
'support_footer' => 'Link footer pendukung ',
'support_footer_help' => 'Tentukan siapa yang melihat tautan ke informasi pendukung dan panduan pengguna Snipe-IT',
'version_footer' => 'Version in Footer ',
'version_footer_help' => 'Specify who sees the Snipe-IT version and build number.',
'version_footer' => 'Versi pada Footer ',
'version_footer_help' => 'Tentukan siapa saja yang dapat melihat versi dan build number dari Snipe-IT.',
'system' => 'Informasi Sistem',
'update' => 'Perbarui Setelan',
'value' => 'Jumlah',
@@ -136,9 +136,9 @@ return array(
'label_dimensions' => 'Dimensi label (inci)',
'next_auto_tag_base' => 'Berikut auto-kenaikan',
'page_padding' => 'Margin halaman (inci)',
'privacy_policy_link' => 'Link to Privacy Policy',
'privacy_policy' => 'Privacy Policy',
'privacy_policy_link_help' => 'If a url is included here, a link to your privacy policy will be included in the app footer and in any emails that the system sends out, in compliance with GDPR. ',
'privacy_policy_link' => 'Tautan ke Kebijakan Privasi',
'privacy_policy' => 'Kebijakan Privasi',
'privacy_policy_link_help' => 'Jika url disertakan di sini, tautan ke kebijakan privasi Anda akan dimasukkan dalam footer aplikasi dan di email apa pun yang dikirimkan sistem, sesuai dengan GDPR. ',
'purge' => 'Bersihkan Arsip yang Dihapus',
'labels_display_bgutter' => 'Beri label saluran bawah',
'labels_display_sgutter' => 'Beri label sisi saluran',
@@ -181,7 +181,7 @@ return array(
'bottom' => 'bawah',
'vertical' => 'tegak lurus',
'horizontal' => 'mendatar',
'unique_serial' => 'Unique serial numbers',
'unique_serial_help_text' => 'Checking this box will enforce a uniqueness constraint on asset serials',
'unique_serial' => 'Nomor seri unik',
'unique_serial_help_text' => 'Menandai boks ini akan membuat adanya batasan keunikan pada tiap nomor seri aset',
'zerofill_count' => 'Panjang tag aset, termasuk isian kosong',
);
@@ -5,8 +5,8 @@ return array(
'archived' => 'Diarsipkan',
'create' => 'Buat label status',
'color' => 'Warna bagan',
'default_label' => 'Default Label',
'default_label_help' => 'This is used to ensure your most commonly used status labels appear at the top of the select box when creating/editing assets.',
'default_label' => 'Label standar',
'default_label_help' => 'Ini digunakan untuk memastikan label status yang paling sering digunakan muncul di bagian atas kotak pilih saat membuat / mengedit aset.',
'deployable' => 'Bisa disebarkan',
'info' => 'Label status digunakan untuk menggambarkan berbagai keadaan aset Anda. Mungkin ada perbaikan, kehilangan / pencurian, dll. Anda dapat membuat label status baru untuk aset yang dapat dideplesikan, tertunda dan diarsipkan.',
'name' => 'Nama status',
+2 -2
View File
@@ -2,8 +2,8 @@
return array(
'activated_help_text' => 'This user can login',
'activated_disabled_help_text' => 'You cannot edit activation status for your own account.',
'activated_help_text' => 'Pengguna ini dapat masuk',
'activated_disabled_help_text' => 'Anda tidak dapat menyunting status aktivasi akun anda sendiri.',
'assets_user' => 'Aset ditetapkan untuk :name',
'bulk_update_warn' => 'Anda akan sunting properti dari :user_count pengguna. Silahkan catat anda tidak dapat menggubah atribut pengguna anda sendiri menggunakan formulir ini, dan harus membuat penyuntingan untuk pengguna anda sendiri secara pribadi.',
'bulk_update_help' => 'Formulir ini membolehkan anda perbarui beberapa pengguna sekaligus. Hanya mengisi bidang yang anda perlu ubah. Setiap bidang yang kosong akan tetap tidak berubah.',
+1 -1
View File
@@ -12,5 +12,5 @@ return array(
'submit' => 'Kirim',
'upload' => 'Unggah',
'select_file' => 'Pilih Berkas...',
'select_files' => 'Select Files...',
'select_files' => 'Pilih file...',
);
+58 -58
View File
@@ -39,9 +39,9 @@
'checkin' => 'Mendaftar',
'checkin_from' => 'Checkin dari',
'checkout' => 'Checkout',
'checkouts_count' => 'Checkouts',
'checkins_count' => 'Checkins',
'user_requests_count' => 'Requests',
'checkouts_count' => 'Keluar',
'checkins_count' => 'Masuk',
'user_requests_count' => 'Permintaan',
'city' => 'Kota',
'click_here' => 'Klik disini',
'clear_selection' => 'Hapus yang dipilih',
@@ -68,7 +68,7 @@
'debug_warning' => 'Peringatan!',
'debug_warning_text' => 'Aplikasi ini berjalan dalam mode produksi dengan debugging diaktifkan. Hal ini dapat mengekspos data sensitif jika aplikasi Anda dapat diakses oleh dunia luar. Nonaktifkan mode debug dengan menetapkan nilai <code>APP_DEBUG</code> value in your <code>.env</code> file to <code>false</code>.',
'delete' => 'Hapus',
'delete_confirm' => 'Are you sure you wish to delete :item?',
'delete_confirm' => 'Apa Anda yakin untuk menghapus :item?',
'deleted' => 'Dihapus',
'delete_seats' => 'Tempat dihapus',
'departments' => 'Departemen',
@@ -86,81 +86,81 @@
'filastname_format' => 'Nama Depan Nama Belakang (jane.smith@example.com)',
'firstname_lastname_format' => 'Nama Depan Nama Belakang (jane.smith@example.com)',
'firstname_lastname_underscore_format' => 'Nama Depan Nama Belakang (jane.smith@example.com)',
'lastnamefirstinitial_format' => 'Last Name First Initial (smithj@example.com)',
'lastnamefirstinitial_format' => 'Nama Depan First Initial (smithj@example.com)',
'first' => 'Pertama',
'first_name' => 'Nama Pertama',
'first_name_format' => 'Nama Depan (jane@example.com)',
'files' => 'Berkas',
'file_name' => 'Berkas',
'file_uploads' => 'Unggah berkas',
'generate' => 'Generate',
'github_markdown' => 'This field accepts <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>.',
'groups' => 'Groups',
'gravatar_email' => 'Gravatar Email Address',
'generate' => 'Hasilkan',
'github_markdown' => 'Bidang ini mengizinkan penggunaan <a href="https://help.github.com/articles/github-flavored-markdown/">kode markup milik Github</a>.',
'groups' => 'Kelompok',
'gravatar_email' => 'Alamat Email Gravatar',
'history' => 'Riwayat',
'history_for' => 'Riwayat untuk',
'id' => 'ID',
'image' => 'Gambar',
'image_delete' => 'Hapus Gambar',
'image_upload' => 'Unggah Gambar',
'image_filetypes_help' => 'Accepted filetypes are jpg, png, gif, and svg. Max upload size allowed is :size.',
'image_filetypes_help' => 'Tipe file yang diterima adalah jpg, png, gif, dan svg. Maksimal ukuran foto diizinkan adalah :size.',
'import' => 'Impor',
'import-history' => 'Riwayat Impor',
'asset_maintenance' => 'Asset Maintenance',
'asset_maintenance_report' => 'Asset Maintenance Report',
'asset_maintenances' => 'Asset Maintenances',
'asset_maintenance' => 'Pemeliharaan Aset',
'asset_maintenance_report' => 'Laporan Pemeliharaan Aset',
'asset_maintenances' => 'Pemeliharaan Aset',
'item' => 'Item',
'insufficient_permissions' => 'Insufficient permissions!',
'insufficient_permissions' => 'Tidak ada hak akses!',
'language' => 'Bahasa',
'last' => 'Last',
'last_login' => 'Last Login',
'last_name' => 'Last Name',
'license' => 'License',
'license_report' => 'License Report',
'licenses_available' => 'licenses available',
'licenses' => 'Licenses',
'list_all' => 'List All',
'loading' => 'Loading',
'lock_passwords' => 'This field cannot be edited in this installation.',
'feature_disabled' => 'This feature has been disabled for the demo installation.',
'location' => 'Location',
'locations' => 'Locations',
'logout' => 'Logout',
'lookup_by_tag' => 'Lookup by Asset Tag',
'maintenances' => 'Maintenances',
'manufacturer' => 'Manufacturer',
'manufacturers' => 'Manufacturers',
'markdown' => 'This field allows <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>.',
'min_amt' => 'Min. QTY',
'min_amt_help' => 'Minimum number of items that should be available before an alert gets triggered.',
'model_no' => 'Model No.',
'months' => 'months',
'moreinfo' => 'More Info',
'name' => 'Name',
'next' => 'Next',
'next_audit_date' => 'Next Audit Date',
'last_audit' => 'Last Audit',
'last' => 'Terakhir',
'last_login' => 'Login terakhir',
'last_name' => 'Nama Belakang',
'license' => 'Lisensi',
'license_report' => 'Laporan Lisensi',
'licenses_available' => 'lisensi yang tersedia',
'licenses' => 'Lisensi',
'list_all' => 'Tampilkan semua',
'loading' => 'Memuat',
'lock_passwords' => 'Kolom ini tidak dapat disunting dalam instalasi ini.',
'feature_disabled' => 'Fitur ini telah dinonaktifkan untuk instalasi demo.',
'location' => 'Lokasi',
'locations' => 'Lokasi',
'logout' => 'Keluar',
'lookup_by_tag' => 'Mencari berdasarkan tag aset',
'maintenances' => 'Pemeliharaan',
'manufacturer' => 'Pabrikan',
'manufacturers' => 'Pabrikan',
'markdown' => 'Bidang ini mengizinkan penggunaan <a href="https://help.github.com/articles/github-flavored-markdown/">kode markup milik Github</a>.',
'min_amt' => 'Jml Min.',
'min_amt_help' => 'Jumlah item minimal yang tersedia sebelum pemberitahuan diaktifkan.',
'model_no' => 'No. Model',
'months' => 'bulan',
'moreinfo' => 'Info Lengkap',
'name' => 'Nama',
'next' => 'Selanjutnya',
'next_audit_date' => 'Tanggal Audit berikutnya',
'last_audit' => 'Audit Terakhir',
'new' => 'baru!',
'no_depreciation' => 'No Depreciation',
'no_depreciation' => 'Tidak ada penyusutan',
'no_results' => 'Tidak ada Hasil.',
'no' => 'Tidak',
'notes' => 'Catatan',
'order_number' => 'Order Number',
'page_menu' => 'Showing _MENU_ items',
'pagination_info' => 'Showing _START_ to _END_ of _TOTAL_ items',
'order_number' => 'Nomor Pemesanan',
'page_menu' => 'Menampilkan item _MENU_',
'pagination_info' => 'Menampilkan item _START_ to _END_ of _TOTAL_',
'pending' => 'Tertunda',
'people' => 'Orang',
'per_page' => 'Hasil per Halaman',
'previous' => 'Sebelumnya',
'processing' => 'Processing',
'processing' => 'Memproses',
'profile' => 'Profil anda',
'purchase_cost' => 'Harga pembelian',
'purchase_date' => 'Tanggal Pembelian',
'qty' => 'QTY',
'quantity' => 'Quantity',
'ready_to_deploy' => 'Ready to Deploy',
'qty' => 'JML',
'quantity' => 'Jumlah',
'ready_to_deploy' => 'Siap digunakan',
'recent_activity' => 'Aktifitas tebaru',
'remove_company' => 'Remove Company Association',
'remove_company' => 'Hapus Asosiasi Perusahaan',
'reports' => 'Laporan',
'restored' => 'dikembalikan',
'requested' => 'Diminta',
@@ -169,10 +169,10 @@
'select' => 'Pilih',
'search' => 'Cari',
'select_category' => 'Pilih kategori',
'select_department' => 'Select a Department',
'select_depreciation' => 'Select a Depreciation Type',
'select_department' => 'Pilih Departemen',
'select_depreciation' => 'Pilih Jenis Penyusutan',
'select_location' => 'Pilih lokasi',
'select_manufacturer' => 'Select a Manufacturer',
'select_manufacturer' => 'Pilih Produsen',
'select_model' => 'Pilih sebuah model',
'select_supplier' => 'Pilih pabrikan',
'select_user' => 'Pilih pengguna',
@@ -181,11 +181,11 @@
'select_company' => 'Pilih perusahaan',
'select_asset' => 'Pilih aset',
'settings' => 'Pengaturan',
'show_deleted' => 'Show Deleted',
'show_current' => 'Show Current',
'show_deleted' => 'Tampilkan yang Dihapus',
'show_current' => 'Tampilkan Saat Ini',
'sign_in' => 'Masuk',
'signature' => 'Tanda tangan',
'skin' => 'Skin',
'skin' => 'Tema',
'some_features_disabled' => 'DEMO MODE: Beberapa fitur dinonaktifkan untuk penginstalan ini.',
'site_name' => 'Nama Situs',
'state' => 'Negara',
@@ -206,7 +206,7 @@
'unknown_admin' => 'Admin tidak diketahui',
'username_format' => 'Format nama pengguna',
'update' => 'Perbarui',
'upload_filetypes_help' => 'Allowed filetypes are png, gif, jpg, jpeg, doc, docx, pdf, xls, txt, lic, zip, and rar. Max upload size allowed is :size.',
'upload_filetypes_help' => 'Tipe file yang diizinkan adalah png, gif, jpg, jpeg, doc, docx, pdf, txt, zip, dan rar. Maksimal ukuran file upload adalah :size.',
'uploaded' => 'Unggah',
'user' => 'Pengguna',
'accepted' => 'diterima',
+6 -6
View File
@@ -21,7 +21,7 @@ return array(
'Confirm_Asset_Checkin' => 'Konfirmasikan Checkin Aset.',
'Confirm_Accessory_Checkin' => 'Konfirmasikan Checkin Aksesori.',
'Confirm_accessory_delivery' => 'Konfirmasikan pengiriman aksesori.',
'Confirm_license_delivery' => 'Confirm license delivery.',
'Confirm_license_delivery' => 'Konfirmasi lisensi pengiriman.',
'Confirm_asset_delivery' => 'Konfirmasikan pengiriman aset.',
'Confirm_consumable_delivery' => 'Konfirmasikan pengiriman consumable.',
'current_QTY' => 'QTY saat ini',
@@ -58,13 +58,13 @@ return array(
'test_email' => 'Coba Email dari Snipe-IT',
'test_mail_text' => 'Ini adalah uji coba dari Snipe-IT Asset Management System. Jika Anda mendapatkan ini, mail sedang bekerja :)',
'the_following_item' => 'Item berikut telah diperiksa: ',
'low_inventory_alert' => 'There is :count item that is below minimum inventory or will soon be low.|There are :count items that are below minimum inventory or will soon be low.',
'assets_warrantee_alert' => 'There is :count asset with a warrantee expiring in the next :threshold days.|There are :count assets with warrantees expiring in the next :threshold days.',
'license_expiring_alert' => 'There is :count license expiring in the next :threshold days.|There are :count licenses expiring in the next :threshold days.',
'low_inventory_alert' => 'Ada :count item yang di bawah minimum persediaan atau akan segera habis.|Ada :count item yang di bawah minimum persediaan atau akan segera habis.',
'assets_warrantee_alert' => 'Ada :count aset yang masa garansinya tinggal :threshold hari lagi.|Ada :count aset yang garansinya tinggal :threshold hari lagi.',
'license_expiring_alert' => 'Ada :count lisensi yang masa berlakunya akan habis dalam :threshold hari.|Ada :count lisensi yang masa berlakunya akan habis dalam :threshold hari.',
'to_reset' => 'Untuk atur ulang kata sandi situs web: anda, lengkapi formulir ini:',
'type' => 'Jenis',
'user' => 'User',
'username' => 'Username',
'user' => 'Pengguna',
'username' => 'Nama Pengguna',
'welcome' => 'Selamat datang :nama',
'welcome_to' => 'Selamat datang di :Web!',
'your_credentials' => 'Kredensial Snipe-IT Anda',
+1 -1
View File
@@ -2,6 +2,6 @@
return [
'sent' => 'Tautan kata sandi anda sudah dikirim!',
'user' => 'No matching active user found with that email.',
'user' => 'Tidak ada satupun pengguna aktif yang menggunakan email ini.',
];
+19 -19
View File
@@ -69,26 +69,26 @@ return array(
'present' => 'Bidang atribut harus ada.',
'valid_regex' => 'Ini bukan regex yang valid. ',
'regex' => 'Format atribut tidak valid.',
'required' => 'The :attribute field is required.',
'required_if' => 'The :attribute field is required when :other is :value.',
'required_unless' => 'The :attribute field is required unless :other is in :values.',
'required_with' => 'The :attribute field is required when :values is present.',
'required_with_all' => 'The :attribute field is required when :values is present.',
'required_without' => 'The :attribute field is required when :values is not present.',
'required_without_all' => 'The :attribute field is required when none of :values are present.',
'same' => 'The :attribute and :other must match.',
'required' => 'Bidang :attribute harus diisi.',
'required_if' => 'Kolom :attribute wajib di-isi ketika :other nya :value.',
'required_unless' => 'Kolom :attribute wajib di-isi kecuali :other nya :value.',
'required_with' => 'Kolom :attribute wajib di-isi ketika :values terisi.',
'required_with_all' => 'Kolom :attribute wajib di-isi ketika :values terisi.',
'required_without' => 'Kolom :attribute wajib di-isi ketika :values kosong.',
'required_without_all' => 'Kolom :attribute wajib di-isi jika tidak ada :values yang terisi.',
'same' => ':attribute dan :other harus mirip.',
'size' => [
'numeric' => 'The :attribute must be :size.',
'file' => 'The :attribute must be :size kilobytes.',
'string' => 'The :attribute must be :size characters.',
'array' => 'The :attribute must contain :size items.',
'numeric' => ':attribute harus :size.',
'file' => ':attribute harus berukuran :size kilobytes.',
'string' => ':attribute harus memiliki :size karakter.',
'array' => ':attribute harus memiliki sebanyak :size item.',
],
'string' => 'The :attribute must be a string.',
'timezone' => 'The :attribute must be a valid zone.',
'unique' => 'The :attribute has already been taken.',
'uploaded' => 'The :attribute failed to upload.',
'url' => 'The :attribute format is invalid.',
"unique_undeleted" => "The :attribute must be unique.",
'string' => ':attribute haruslah sebuah string.',
'timezone' => ':attribute haruslah sebuah zone yang valid.',
'unique' => ':attribute sudah pernah digunakan.',
'uploaded' => ':attribute gagal di-upload.',
'url' => 'Format :attribute tidaklah benar.',
"unique_undeleted" => ":attribute haruslah unik.",
/*
|--------------------------------------------------------------------------
@@ -114,7 +114,7 @@ return array(
*/
'custom' => [
'alpha_space' => "The :attribute field contains a character that is not allowed.",
'alpha_space' => "Kolom :attribute mengandung karakter yang dilarang.",
"email_array" => "Satu atau lebih alamat surel tidak valid.",
"hashed_pass" => "Kata sandi anda saat ini salah",
'dumbpwd' => 'Kata sandi itu terlalu umum.',
+12
View File
@@ -9,11 +9,23 @@ return array(
'account_banned' => 'This user account is banned.',
'throttle' => 'Too many failed login attempts. Please try again in :seconds seconds.',
'two_factor' => array(
'already_enrolled' => 'Your device is already enrolled.',
'success' => 'You have successfully logged in.',
'code_required' => 'Two-factor code is required.',
'invalid_code' => 'Two-factor code is invalid.',
),
'signin' => array(
'error' => 'There was a problem while trying to log you in, please try again.',
'success' => 'You have successfully logged in.',
),
'logout' => array(
'error' => 'There was a problem while trying to log you out, please try again.',
'success' => 'You have successfully logged out.',
),
'signup' => array(
'error' => 'There was a problem while trying to create your account, please try again.',
'success' => 'Account sucessfully created.',
+5 -2
View File
@@ -219,6 +219,9 @@
'years' => 'years',
'yes' => 'Yes',
'zip' => 'Zip',
'noimage' => 'No image uploaded or image not found.',
'token_expired' => 'Your form session has expired. Please try again.',
'noimage' => 'No image uploaded or image not found.',
'token_expired' => 'Your form session has expired. Please try again.',
'login_enabled' => 'Login Enabled',
'audit_due' => 'Due for Audit',
'audit_overdue' => 'Overdue for Audit',
];
+8 -6
View File
@@ -18,12 +18,12 @@ return array(
'click_to_confirm' => 'Please click on the following link to confirm your :web account:',
'click_on_the_link_accessory' => 'Please click on the link at the bottom to confirm that you have received the accessory.',
'click_on_the_link_asset' => 'Please click on the link at the bottom to confirm that you have received the asset.',
'Confirm_Asset_Checkin' => 'Confirm Asset Checkin.',
'Confirm_Accessory_Checkin' => 'Confirm Accessory Checkin.',
'Confirm_accessory_delivery' => 'Confirm accessory delivery.',
'Confirm_license_delivery' => 'Confirm license delivery.',
'Confirm_asset_delivery' => 'Confirm asset delivery.',
'Confirm_consumable_delivery' => 'Confirm consumable delivery.',
'Confirm_Asset_Checkin' => 'Asset checkin confirmation',
'Confirm_Accessory_Checkin' => 'Accessory checkin confirmation',
'Confirm_accessory_delivery' => 'Accessory delivery confirmation',
'Confirm_license_delivery' => 'License delivery confirmation',
'Confirm_asset_delivery' => 'Asset delivery confirmation',
'Confirm_consumable_delivery' => 'Consumable delivery confirmation',
'current_QTY' => 'Current QTY',
'Days' => 'Days',
'days' => 'Days',
@@ -64,9 +64,11 @@ return array(
'license_expiring_alert' => 'There is :count license expiring in the next :threshold days.|There are :count licenses expiring in the next :threshold days.',
'to_reset' => 'To reset your :web password, complete this form:',
'type' => 'Type',
'upcoming-audits' => 'There is :count asset that is coming up for audit within :threshold days.|There are :count assets that are coming up for audit within :threshold days.',
'user' => 'User',
'username' => 'Username',
'welcome' => 'Welcome :name',
'welcome_to' => 'Welcome to :web!',
'your_credentials' => 'Your Snipe-IT credentials',
);

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