diff --git a/.all-contributorsrc b/.all-contributorsrc index d7624a4314..ba9f54a99b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -2590,10 +2590,10 @@ ] }, { - "login": "QveenSi", + "login": "qveensi", "name": "Yevhenii Huzii", "avatar_url": "https://avatars.githubusercontent.com/u/19945501?v=4", - "profile": "https://github.com/QveenSi", + "profile": "https://github.com/qveensi", "contributions": [ "code" ] @@ -2607,15 +2607,6 @@ "code" ] }, - { - "login": "QveenSi", - "name": "Yevhenii Huzii", - "avatar_url": "https://avatars.githubusercontent.com/u/19945501?v=4", - "profile": "https://github.com/QveenSi", - "contributions": [ - "code" - ] - }, { "login": "chrisweirich", "name": "Christian Weirich", @@ -3136,6 +3127,1059 @@ "contributions": [ "doc" ] + }, + { + "login": "FlorentDotMe", + "name": "Florent Bervas", + "avatar_url": "https://avatars.githubusercontent.com/u/292081?v=4", + "profile": "http://spoontux.net", + "contributions": [ + "code" + ] + }, + { + "login": "dbakan", + "name": "Daniel Albertsen", + "avatar_url": "https://avatars.githubusercontent.com/u/4498077?v=4", + "profile": "https://ditscheri.com", + "contributions": [ + "code" + ] + }, + { + "login": "r-xyz", + "name": "r-xyz", + "avatar_url": "https://avatars.githubusercontent.com/u/100710244?v=4", + "profile": "https://github.com/r-xyz", + "contributions": [ + "code" + ] + }, + { + "login": "DrekiDegga", + "name": "Steven Mainor", + "avatar_url": "https://avatars.githubusercontent.com/u/47491036?v=4", + "profile": "https://github.com/DrekiDegga", + "contributions": [ + "code" + ] + }, + { + "login": "arne-kroeger", + "name": "arne-kroeger", + "avatar_url": "https://avatars.githubusercontent.com/u/65785975?v=4", + "profile": "https://github.com/arne-kroeger", + "contributions": [ + "code" + ] + }, + { + "login": "Glukose1", + "name": "Glukose1", + "avatar_url": "https://avatars.githubusercontent.com/u/167117705?v=4", + "profile": "https://github.com/Glukose1", + "contributions": [ + "code" + ] + }, + { + "login": "Scarzy", + "name": "Scarzy", + "avatar_url": "https://avatars.githubusercontent.com/u/1197791?v=4", + "profile": "https://github.com/Scarzy", + "contributions": [ + "code" + ] + }, + { + "login": "setpill", + "name": "setpill", + "avatar_url": "https://avatars.githubusercontent.com/u/37372069?v=4", + "profile": "https://github.com/setpill", + "contributions": [ + "code" + ] + }, + { + "login": "swift2512", + "name": "swift2512", + "avatar_url": "https://avatars.githubusercontent.com/u/3755203?v=4", + "profile": "https://github.com/swift2512", + "contributions": [ + "bug" + ] + }, + { + "login": "DarrenRainey", + "name": "Darren Rainey", + "avatar_url": "https://avatars.githubusercontent.com/u/6136439?v=4", + "profile": "https://darrenraineys.co.uk", + "contributions": [ + "code" + ] + }, + { + "login": "maciej-poleszczyk", + "name": "maciej-poleszczyk", + "avatar_url": "https://avatars.githubusercontent.com/u/133033121?v=4", + "profile": "https://github.com/maciej-poleszczyk", + "contributions": [ + "code" + ] + }, + { + "login": "sgross-emlix", + "name": "Sebastian Groß", + "avatar_url": "https://avatars.githubusercontent.com/u/143394709?v=4", + "profile": "https://github.com/sgross-emlix", + "contributions": [ + "code" + ] + }, + { + "login": "AnouarTouati", + "name": "Anouar Touati", + "avatar_url": "https://avatars.githubusercontent.com/u/41107778?v=4", + "profile": "https://github.com/AnouarTouati", + "contributions": [ + "code" + ] + }, + { + "login": "aHVzY2g", + "name": "aHVzY2g", + "avatar_url": "https://avatars.githubusercontent.com/u/25596663?v=4", + "profile": "https://github.com/aHVzY2g", + "contributions": [ + "code" + ] + }, + { + "login": "brlin-tw", + "name": "林博仁 Buo-ren Lin", + "avatar_url": "https://avatars.githubusercontent.com/u/13408130?v=4", + "profile": "https://brlin.me", + "contributions": [ + "code" + ] + }, + { + "login": "addex12", + "name": "Adugna Gizaw", + "avatar_url": "https://avatars.githubusercontent.com/u/18550946?v=4", + "profile": "https://orbalia.pythonanywhere.com/", + "contributions": [ + "translation" + ] + }, + { + "login": "jostrander", + "name": "Jesse Ostrander", + "avatar_url": "https://avatars.githubusercontent.com/u/760989?v=4", + "profile": "https://github.com/jostrander", + "contributions": [ + "code" + ] + }, + { + "login": "azmcnutt", + "name": "James M", + "avatar_url": "https://avatars.githubusercontent.com/u/31522486?v=4", + "profile": "https://github.com/azmcnutt", + "contributions": [ + "code" + ] + }, + { + "login": "Fiala06", + "name": "Fiala06", + "avatar_url": "https://avatars.githubusercontent.com/u/5183146?v=4", + "profile": "https://github.com/Fiala06", + "contributions": [ + "code" + ] + }, + { + "login": "ntaylor-86", + "name": "Nathan Taylor", + "avatar_url": "https://avatars.githubusercontent.com/u/28693782?v=4", + "profile": "https://github.com/ntaylor-86", + "contributions": [ + "code" + ] + }, + { + "login": "fvollmer", + "name": "fvollmer", + "avatar_url": "https://avatars.githubusercontent.com/u/16699443?v=4", + "profile": "https://github.com/fvollmer", + "contributions": [ + "code" + ] + }, + { + "login": "36864", + "name": "36864", + "avatar_url": "https://avatars.githubusercontent.com/u/109086466?v=4", + "profile": "https://github.com/36864", + "contributions": [ + "code" + ] + }, + { + "login": "CloCkWeRX", + "name": "Daniel O'Connor", + "avatar_url": "https://avatars.githubusercontent.com/u/365751?v=4", + "profile": "http://clockwerx.blogspot.com/", + "contributions": [ + "code" + ] + }, + { + "login": "BeatSpark", + "name": "BeatSpark", + "avatar_url": "https://avatars.githubusercontent.com/u/102852568?v=4", + "profile": "https://github.com/BeatSpark", + "contributions": [ + "code" + ] + }, + { + "login": "mrdahbi", + "name": "mrdahbi", + "avatar_url": "https://avatars.githubusercontent.com/u/59203607?v=4", + "profile": "https://github.com/mrdahbi", + "contributions": [ + "code" + ] + }, + { + "login": "chfsx", + "name": "Fabian Schmid", + "avatar_url": "https://avatars.githubusercontent.com/u/6661332?v=4", + "profile": "http://sr.solutions", + "contributions": [ + "code" + ] + }, + { + "login": "realchrisolin", + "name": "Chris Olin", + "avatar_url": "https://avatars.githubusercontent.com/u/1288116?v=4", + "profile": "https://www.chrisolin.com", + "contributions": [ + "code" + ] + }, + { + "login": "mnemonicly", + "name": "Dan", + "avatar_url": "https://avatars.githubusercontent.com/u/3803132?v=4", + "profile": "https://github.com/mnemonicly", + "contributions": [ + "code" + ] + }, + { + "login": "NebelKreis", + "name": "Nebel", + "avatar_url": "https://avatars.githubusercontent.com/u/43917728?v=4", + "profile": "https://github.com/NebelKreis", + "contributions": [ + "code" + ] + }, + { + "login": "test1337ahp", + "name": "test1337ahp", + "avatar_url": "https://avatars.githubusercontent.com/u/132433803?v=4", + "profile": "https://github.com/test1337ahp", + "contributions": [ + "code" + ] + }, + { + "login": "JonathonReinhart", + "name": "Jonathon Reinhart", + "avatar_url": "https://avatars.githubusercontent.com/u/1916566?v=4", + "profile": "https://github.com/JonathonReinhart", + "contributions": [ + "code" + ] + }, + { + "login": "aranar-pro", + "name": "aranar-pro", + "avatar_url": "https://avatars.githubusercontent.com/u/484742?v=4", + "profile": "https://github.com/aranar-pro", + "contributions": [ + "code" + ] + }, + { + "login": "phil-flip", + "name": "Phil", + "avatar_url": "https://avatars.githubusercontent.com/u/27019397?v=4", + "profile": "https://github.com/phil-flip", + "contributions": [ + "code" + ] + }, + { + "login": "fe80", + "name": "Steffy Fort", + "avatar_url": "https://avatars.githubusercontent.com/u/6473460?v=4", + "profile": "https://fe80.fr/", + "contributions": [ + "code" + ] + }, + { + "login": "sorvani", + "name": "Jared Busch", + "avatar_url": "https://avatars.githubusercontent.com/u/3302372?v=4", + "profile": "https://github.com/sorvani", + "contributions": [ + "code" + ] + }, + { + "login": "seanborg-codethink", + "name": "seanborg-codethink", + "avatar_url": "https://avatars.githubusercontent.com/u/111956991?v=4", + "profile": "https://github.com/seanborg-codethink", + "contributions": [ + "code" + ] + }, + { + "login": "dkaatz", + "name": "dkaatz", + "avatar_url": "https://avatars.githubusercontent.com/u/160669961?v=4", + "profile": "https://github.com/dkaatz", + "contributions": [ + "code" + ] + }, + { + "login": "DanielRuf", + "name": "Daniel Ruf", + "avatar_url": "https://avatars.githubusercontent.com/u/827205?v=4", + "profile": "https://threema.id/74SF7MW6?text=", + "contributions": [ + "code" + ] + }, + { + "login": "ahpaleus", + "name": "ahpaleus", + "avatar_url": "https://avatars.githubusercontent.com/u/38883201?v=4", + "profile": "https://github.com/ahpaleus", + "contributions": [ + "code" + ] + }, + { + "login": "mink-adao-duy", + "name": "Anh DAO-DUY", + "avatar_url": "https://avatars.githubusercontent.com/u/22906055?v=4", + "profile": "https://github.com/mink-adao-duy", + "contributions": [ + "code" + ] + }, + { + "login": "Serdnad", + "name": "Andres Gutierrez", + "avatar_url": "https://avatars.githubusercontent.com/u/4723453?v=4", + "profile": "https://github.com/Serdnad", + "contributions": [ + "code" + ] + }, + { + "login": "wewhite", + "name": "Warren White", + "avatar_url": "https://avatars.githubusercontent.com/u/111083379?v=4", + "profile": "https://github.com/wewhite", + "contributions": [ + "code" + ] + }, + { + "login": "robintemme", + "name": "Robin Temme", + "avatar_url": "https://avatars.githubusercontent.com/u/2809241?v=4", + "profile": "https://robintemme.de/", + "contributions": [ + "code" + ] + }, + { + "login": "herroworrd", + "name": "herroworrd", + "avatar_url": "https://avatars.githubusercontent.com/u/47008367?v=4", + "profile": "https://github.com/herroworrd", + "contributions": [ + "code" + ] + }, + { + "login": "vicleos", + "name": "vicleos", + "avatar_url": "https://avatars.githubusercontent.com/u/28558609?v=4", + "profile": "https://mubiu.com/", + "contributions": [ + "code" + ] + }, + { + "login": "thinkl33t", + "name": "Bob Clough", + "avatar_url": "https://avatars.githubusercontent.com/u/1016780?v=4", + "profile": "http://thinkl33t.co.uk/", + "contributions": [ + "code" + ] + }, + { + "login": "brandon-bailey", + "name": "Brandon Daniel Bailey", + "avatar_url": "https://avatars.githubusercontent.com/u/10648463?v=4", + "profile": "https://github.com/brandon-bailey", + "contributions": [ + "code" + ] + }, + { + "login": "marcquark", + "name": "Marc Bartelt", + "avatar_url": "https://avatars.githubusercontent.com/u/23556080?v=4", + "profile": "https://github.com/marcquark", + "contributions": [ + "code" + ] + }, + { + "login": "manu-crealytics", + "name": "manu-crealytics", + "avatar_url": "https://avatars.githubusercontent.com/u/18286893?v=4", + "profile": "https://github.com/manu-crealytics", + "contributions": [ + "code" + ] + }, + { + "login": "Galaxy102", + "name": "Konstantin Köhring", + "avatar_url": "https://avatars.githubusercontent.com/u/18245993?v=4", + "profile": "https://www.galaxy102.de/", + "contributions": [ + "code" + ] + }, + { + "login": "deloz", + "name": "Deloz", + "avatar_url": "https://avatars.githubusercontent.com/u/685167?v=4", + "profile": "https://deloz.net/", + "contributions": [ + "code" + ] + }, + { + "login": "mbrrg", + "name": "Martin Berg", + "avatar_url": "https://avatars.githubusercontent.com/u/2682426?v=4", + "profile": "https://github.com/mbrrg", + "contributions": [ + "code" + ] + }, + { + "login": "Nothing4You", + "name": "Richard Schwab", + "avatar_url": "https://avatars.githubusercontent.com/u/3694534?v=4", + "profile": "https://github.com/Nothing4You", + "contributions": [ + "code" + ] + }, + { + "login": "rickheil", + "name": "Rick Heil", + "avatar_url": "https://avatars.githubusercontent.com/u/8959676?v=4", + "profile": "https://rickheil.com/", + "contributions": [ + "code" + ] + }, + { + "login": "rosscdh", + "name": "Ross Crawford-d'Heureuse", + "avatar_url": "https://avatars.githubusercontent.com/u/397106?v=4", + "profile": "https://github.com/rosscdh", + "contributions": [ + "code" + ] + }, + { + "login": "McG800", + "name": "Ryan McGuire", + "avatar_url": "https://avatars.githubusercontent.com/u/1621107?v=4", + "profile": "https://github.com/McG800", + "contributions": [ + "code" + ] + }, + { + "login": "SBrown2021", + "name": "SBrown2021", + "avatar_url": "https://avatars.githubusercontent.com/u/77835667?v=4", + "profile": "https://github.com/SBrown2021", + "contributions": [ + "code" + ] + }, + { + "login": "serkanerip", + "name": "Serkan", + "avatar_url": "https://avatars.githubusercontent.com/u/8780913?v=4", + "profile": "https://github.com/serkanerip", + "contributions": [ + "code" + ] + }, + { + "login": "Shankschn", + "name": "Shanks", + "avatar_url": "https://avatars.githubusercontent.com/u/63188620?v=4", + "profile": "https://www.yudelei.com/", + "contributions": [ + "code" + ] + }, + { + "login": "cendai-mis", + "name": "cendai-mis", + "avatar_url": "https://avatars.githubusercontent.com/u/198525698?v=4", + "profile": "https://github.com/cendai-mis", + "contributions": [ + "code" + ] + }, + { + "login": "smcpeck", + "name": "Shaun McPeck", + "avatar_url": "https://avatars.githubusercontent.com/u/8724583?v=4", + "profile": "https://smcpeck.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "snazy2000", + "name": "Stephen", + "avatar_url": "https://avatars.githubusercontent.com/u/1378836?v=4", + "profile": "https://github.com/snazy2000", + "contributions": [ + "code" + ] + }, + { + "login": "Nevets82", + "name": "Steven", + "avatar_url": "https://avatars.githubusercontent.com/u/4462739?v=4", + "profile": "http://nevets82.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "Mateus-Romera", + "name": "Mateus Villar", + "avatar_url": "https://avatars.githubusercontent.com/u/29017267?v=4", + "profile": "https://mateusvillar.com/", + "contributions": [ + "code" + ] + }, + { + "login": "mzack5020", + "name": "Matthew Zackschewski", + "avatar_url": "https://avatars.githubusercontent.com/u/12749393?v=4", + "profile": "https://github.com/mzack5020", + "contributions": [ + "code" + ] + }, + { + "login": "firefrei", + "name": "Matthias Frei", + "avatar_url": "https://avatars.githubusercontent.com/u/12660103?v=4", + "profile": "https://www.frei.media/", + "contributions": [ + "code" + ] + }, + { + "login": "nticaric", + "name": "Nenad Ticaric", + "avatar_url": "https://avatars.githubusercontent.com/u/824840?v=4", + "profile": "https://github.com/nticaric", + "contributions": [ + "code" + ] + }, + { + "login": "Scorcher", + "name": "Nikolay Didenko", + "avatar_url": "https://avatars.githubusercontent.com/u/706439?v=4", + "profile": "https://github.com/Scorcher", + "contributions": [ + "code" + ] + }, + { + "login": "nunomaduro", + "name": "Nuno Maduro", + "avatar_url": "https://avatars.githubusercontent.com/u/5457236?v=4", + "profile": "https://nunomaduro.com/sponsorships", + "contributions": [ + "code" + ] + }, + { + "login": "owalerys", + "name": "Oliver Walerys", + "avatar_url": "https://avatars.githubusercontent.com/u/8883074?v=4", + "profile": "https://tektikhq.com/", + "contributions": [ + "code" + ] + }, + { + "login": "rcmcdonald91", + "name": "R. Christian McDonald", + "avatar_url": "https://avatars.githubusercontent.com/u/3102039?v=4", + "profile": "https://keybase.io/rcmcdonald91", + "contributions": [ + "code" + ] + }, + { + "login": "nixn", + "name": "nix", + "avatar_url": "https://avatars.githubusercontent.com/u/1525581?v=4", + "profile": "https://nnix.net/", + "contributions": [ + "code" + ] + }, + { + "login": "octobunny", + "name": "octobunny", + "avatar_url": "https://avatars.githubusercontent.com/u/55462380?v=4", + "profile": "https://github.com/octobunny", + "contributions": [ + "code" + ] + }, + { + "login": "sreyemnayr", + "name": "Ryan", + "avatar_url": "https://avatars.githubusercontent.com/u/8558670?v=4", + "profile": "https://github.com/sreyemnayr", + "contributions": [ + "code" + ] + }, + { + "login": "p3nj", + "name": "p3nj", + "avatar_url": "https://avatars.githubusercontent.com/u/1501022?v=4", + "profile": "https://benji.ltd/", + "contributions": [ + "code" + ] + }, + { + "login": "timwsuqld", + "name": "Tim White", + "avatar_url": "https://avatars.githubusercontent.com/u/6201617?v=4", + "profile": "https://github.com/timwsuqld", + "contributions": [ + "code" + ] + }, + { + "login": "yannikp", + "name": "yannikp", + "avatar_url": "https://avatars.githubusercontent.com/u/22473767?v=4", + "profile": "https://github.com/yannikp", + "contributions": [ + "code" + ] + }, + { + "login": "viclou", + "name": "victoria", + "avatar_url": "https://avatars.githubusercontent.com/u/20525448?v=4", + "profile": "https://github.com/viclou", + "contributions": [ + "code" + ] + }, + { + "login": "valentyntu", + "name": "Valentyn Tulub", + "avatar_url": "https://avatars.githubusercontent.com/u/40685314?v=4", + "profile": "https://github.com/valentyntu", + "contributions": [ + "code" + ] + }, + { + "login": "Wouter0100", + "name": "Wouter van Os", + "avatar_url": "https://avatars.githubusercontent.com/u/864520?v=4", + "profile": "http://wouter0100.nl/", + "contributions": [ + "code" + ] + }, + { + "login": "xWyatt", + "name": "Wyatt Teeter", + "avatar_url": "https://avatars.githubusercontent.com/u/3946540?v=4", + "profile": "https://www.linkedin.com/in/wyatt-teeter", + "contributions": [ + "code" + ] + }, + { + "login": "terwey", + "name": "Yorick Terweijden", + "avatar_url": "https://avatars.githubusercontent.com/u/1596124?v=4", + "profile": "https://github.com/terwey", + "contributions": [ + "code" + ] + }, + { + "login": "bmkalle", + "name": "bmkalle", + "avatar_url": "https://avatars.githubusercontent.com/u/69298836?v=4", + "profile": "https://github.com/bmkalle", + "contributions": [ + "code" + ] + }, + { + "login": "bricelabelle", + "name": "bricelabelle", + "avatar_url": "https://avatars.githubusercontent.com/u/28403467?v=4", + "profile": "https://github.com/bricelabelle", + "contributions": [ + "code" + ] + }, + { + "login": "corydlamb", + "name": "corydlamb", + "avatar_url": "https://avatars.githubusercontent.com/u/97770090?v=4", + "profile": "https://github.com/corydlamb", + "contributions": [ + "code" + ] + }, + { + "login": "splashx", + "name": "Diogenes S. Jesus", + "avatar_url": "https://avatars.githubusercontent.com/u/1154133?v=4", + "profile": "http://twitter.com/splash", + "contributions": [ + "code" + ] + }, + { + "login": "dkmansion", + "name": "D M", + "avatar_url": "https://avatars.githubusercontent.com/u/5826629?v=4", + "profile": "https://github.com/dkmansion", + "contributions": [ + "code" + ] + }, + { + "login": "Jarli01", + "name": "Dustin B", + "avatar_url": "https://avatars.githubusercontent.com/u/14837699?v=4", + "profile": "https://github.com/Jarli01", + "contributions": [ + "code" + ] + }, + { + "login": "fabiang", + "name": "Fabian Grutschus", + "avatar_url": "https://avatars.githubusercontent.com/u/348344?v=4", + "profile": "https://github.com/fabiang", + "contributions": [ + "code" + ] + }, + { + "login": "MelonSmasher", + "name": "MelonSmasher", + "avatar_url": "https://avatars.githubusercontent.com/u/1491053?v=4", + "profile": "https://github.com/MelonSmasher", + "contributions": [ + "code" + ] + }, + { + "login": "AlexanderWPapyrus", + "name": "AlexanderWPapyrus", + "avatar_url": "https://avatars.githubusercontent.com/u/80526133?v=4", + "profile": "https://github.com/AlexanderWPapyrus", + "contributions": [ + "code" + ] + }, + { + "login": "disc", + "name": "Alexandr Hacicheant", + "avatar_url": "https://avatars.githubusercontent.com/u/306231?v=4", + "profile": "https://github.com/disc", + "contributions": [ + "code" + ] + }, + { + "login": "hex128", + "name": "Hex", + "avatar_url": "https://avatars.githubusercontent.com/u/3032891?v=4", + "profile": "https://hex128.io/", + "contributions": [ + "code" + ] + }, + { + "login": "arukompas", + "name": "Arunas Skirius", + "avatar_url": "https://avatars.githubusercontent.com/u/8697942?v=4", + "profile": "https://github.com/arukompas", + "contributions": [ + "code" + ] + }, + { + "login": "benperiton", + "name": "Ben Periton", + "avatar_url": "https://avatars.githubusercontent.com/u/104396?v=4", + "profile": "https://github.com/benperiton", + "contributions": [ + "code" + ] + }, + { + "login": "byronwolfman", + "name": "Byron Wolfman", + "avatar_url": "https://avatars.githubusercontent.com/u/11906832?v=4", + "profile": "https://wolfman.dev/", + "contributions": [ + "code" + ] + }, + { + "login": "CalvinSchwartz", + "name": "Calvin", + "avatar_url": "https://avatars.githubusercontent.com/u/56485508?v=4", + "profile": "https://github.com/CalvinSchwartz", + "contributions": [ + "code" + ] + }, + { + "login": "juanfont", + "name": "Juan Font", + "avatar_url": "https://avatars.githubusercontent.com/u/181059?v=4", + "profile": "https://github.com/juanfont", + "contributions": [ + "code" + ] + }, + { + "login": "juhotaipale", + "name": "Juho Taipale", + "avatar_url": "https://avatars.githubusercontent.com/u/13137708?v=4", + "profile": "https://github.com/juhotaipale", + "contributions": [ + "code" + ] + }, + { + "login": "KorvinSzanto", + "name": "Korvin Szanto", + "avatar_url": "https://avatars.githubusercontent.com/u/1007419?v=4", + "profile": "https://github.com/KorvinSzanto", + "contributions": [ + "code" + ] + }, + { + "login": "sniff122", + "name": "Lewis Foster", + "avatar_url": "https://avatars.githubusercontent.com/u/8513053?v=4", + "profile": "https://lewisfoster.foo/", + "contributions": [ + "code" + ] + }, + { + "login": "loganswartz", + "name": "Logan Swartzendruber", + "avatar_url": "https://avatars.githubusercontent.com/u/33877541?v=4", + "profile": "https://github.com/loganswartz", + "contributions": [ + "code" + ] + }, + { + "login": "lopezio", + "name": "Lorenzo P.", + "avatar_url": "https://avatars.githubusercontent.com/u/1156208?v=4", + "profile": "https://github.com/lopezio", + "contributions": [ + "code" + ] + }, + { + "login": "m4us1ne", + "name": "Lukas Jung", + "avatar_url": "https://avatars.githubusercontent.com/u/33946590?v=4", + "profile": "https://github.com/m4us1ne", + "contributions": [ + "code" + ] + }, + { + "login": "LeafedFox", + "name": "Ellie", + "avatar_url": "https://avatars.githubusercontent.com/u/10965027?v=4", + "profile": "https://leafedfox.xyz/", + "contributions": [ + "code" + ] + }, + { + "login": "gastamper", + "name": "GA Stamper", + "avatar_url": "https://avatars.githubusercontent.com/u/20960555?v=4", + "profile": "https://github.com/gastamper", + "contributions": [ + "code" + ] + }, + { + "login": "gl-pup", + "name": "Guillaume Lefranc", + "avatar_url": "https://avatars.githubusercontent.com/u/206553556?v=4", + "profile": "https://github.com/gl-pup", + "contributions": [ + "code" + ] + }, + { + "login": "dasjoe", + "name": "Hajo Möller", + "avatar_url": "https://avatars.githubusercontent.com/u/733892?v=4", + "profile": "https://github.com/dasjoe", + "contributions": [ + "code" + ] + }, + { + "login": "pottom", + "name": "Istvan Basa", + "avatar_url": "https://avatars.githubusercontent.com/u/3420063?v=4", + "profile": "https://github.com/pottom", + "contributions": [ + "code" + ] + }, + { + "login": "jjasghar", + "name": "JJ Asghar", + "avatar_url": "https://avatars.githubusercontent.com/u/810824?v=4", + "profile": "https://jjasghar.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "JemCdo", + "name": "James E. Msenga", + "avatar_url": "https://avatars.githubusercontent.com/u/40404495?v=4", + "profile": "https://github.com/JemCdo", + "contributions": [ + "code" + ] + }, + { + "login": "jfwiebe", + "name": "Jan Felix Wiebe", + "avatar_url": "https://avatars.githubusercontent.com/u/6865786?v=4", + "profile": "https://github.com/jfwiebe", + "contributions": [ + "code" + ] + }, + { + "login": "drexljo", + "name": "Jo Drexl", + "avatar_url": "https://avatars.githubusercontent.com/u/43412008?v=4", + "profile": "https://www.nfon.com/", + "contributions": [ + "code" + ] + }, + { + "login": "austinsasko", + "name": "Austin Sasko", + "avatar_url": "https://avatars.githubusercontent.com/u/4807843?v=4", + "profile": "https://github.com/austinsasko", + "contributions": [ + "code" + ] + }, + { + "login": "JassonCordones", + "name": "Jasson", + "avatar_url": "https://avatars.githubusercontent.com/u/4875039?v=4", + "profile": "http://jassoncordones.github.io", + "contributions": [ + "code" + ] + }, + { + "login": "Tinyblargon", + "name": "Okean", + "avatar_url": "https://avatars.githubusercontent.com/u/76069640?v=4", + "profile": "https://github.com/Tinyblargon", + "contributions": [ + "code" + ] + }, + { + "login": "amedranogil", + "name": "Alejandro Medrano", + "avatar_url": "https://avatars.githubusercontent.com/u/6515064?v=4", + "profile": "https://www.lst.tfo.upm.es/alejandro-medrano/", + "contributions": [ + "code" + ] } ] } diff --git a/.env.dev.docker b/.env.dev.docker index 7b9e2000cf..f47e69ce4b 100644 --- a/.env.dev.docker +++ b/.env.dev.docker @@ -1,6 +1,8 @@ # -------------------------------------------- # REQUIRED: DB SETUP # -------------------------------------------- +# https://mariadb.com/kb/en/mariadb-server-docker-official-image-environment-variables/ + MYSQL_DATABASE=snipeit MYSQL_USER=snipeit MYSQL_PASSWORD=changeme1234 @@ -9,12 +11,12 @@ MYSQL_ROOT_PASSWORD=changeme1234 # REQUIRED: BASIC APP SETTINGS # -------------------------------------------- APP_ENV=develop -APP_DEBUG=false +APP_DEBUG=true # please regenerate the APP_KEY value by calling `docker-compose run --rm snipeit bash` and then `php artisan key:generate --show` and then copy paste the value here APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ= APP_URL=http://localhost:8000 APP_TIMEZONE='UTC' -APP_LOCALE=en +APP_LOCALE=en-US MAX_RESULTS=500 # -------------------------------------------- @@ -33,6 +35,7 @@ DB_USERNAME=snipeit DB_PASSWORD=changeme1234 DB_PREFIX=null DB_DUMP_PATH='/usr/bin' +DB_DUMP_SKIP_SSL=true DB_CHARSET=utf8mb4 DB_COLLATION=utf8mb4_unicode_ci @@ -76,6 +79,13 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null MAIL_BACKUP_NOTIFICATION_ADDRESS=null BACKUP_ENV=true +# -------------------------------------------- +# OPTIONAL: CHANGE PHP UPLOAD LIMITS (UNCOMMENT WHEN NEEDING TO BE CHANGED) +# -------------------------------------------- +#PHP_UPLOAD_LIMIT=10 +#PHP_POST_MAX_SIZE=10 +#PHP_UPLOAD_MAX_FILESIZE=10 +#PHP_MEMORY_LIMIT=10 # -------------------------------------------- # OPTIONAL: SESSION SETTINGS @@ -156,7 +166,7 @@ RESET_PASSWORD_LINK_EXPIRES=900 # -------------------------------------------- # OPTIONAL: MISC # -------------------------------------------- -LOG_CHANNEL=stderr +LOG_CHANNEL=single LOG_MAX_DAYS=10 APP_LOCKED=false APP_CIPHER=AES-256-CBC diff --git a/.env.docker b/.env.docker index 35c10bfde1..88b65b8f79 100644 --- a/.env.docker +++ b/.env.docker @@ -1,7 +1,7 @@ # -------------------------------------------- # REQUIRED: DOCKER SPECIFIC SETTINGS # -------------------------------------------- -APP_VERSION=v6.4.1 +APP_VERSION= APP_PORT=8000 # -------------------------------------------- @@ -9,12 +9,12 @@ APP_PORT=8000 # -------------------------------------------- APP_ENV=production APP_DEBUG=false -# Please regenerate the APP_KEY value by calling `docker compose run --rm snipeit php artisan key:generate --show`. Copy paste the value here +# Please regenerate the APP_KEY value by calling `docker compose run --rm app php artisan key:generate --show`. Copy paste the value here APP_KEY=base64:3ilviXqB9u6DX1NRcyWGJ+sjySF+H18CPDGb3+IVwMQ= APP_URL=http://localhost:8000 # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - TZ identifier APP_TIMEZONE='UTC' -APP_LOCALE=en +APP_LOCALE=en-US MAX_RESULTS=500 # -------------------------------------------- @@ -35,6 +35,7 @@ DB_PASSWORD=changeme1234 MYSQL_ROOT_PASSWORD=changeme1234 DB_PREFIX=null DB_DUMP_PATH='/usr/bin' +DB_DUMP_SKIP_SSL=true DB_CHARSET=utf8mb4 DB_COLLATION=utf8mb4_unicode_ci @@ -83,6 +84,15 @@ MAIL_BACKUP_NOTIFICATION_DRIVER=null MAIL_BACKUP_NOTIFICATION_ADDRESS=null BACKUP_ENV=true +# -------------------------------------------- +# OPTIONAL: CHANGE PHP UPLOAD LIMITS (UNCOMMENT WHEN NEEDING TO BE CHANGED) +# -------------------------------------------- +#PHP_UPLOAD_LIMIT=10 +#PHP_POST_MAX_SIZE=10 +#PHP_UPLOAD_MAX_FILESIZE=10 +#PHP_MEMORY_LIMIT=10 + + # -------------------------------------------- # OPTIONAL: SESSION SETTINGS # -------------------------------------------- @@ -97,7 +107,7 @@ API_TOKEN_EXPIRATION_YEARS=40 # -------------------------------------------- # OPTIONAL: SECURITY HEADER SETTINGS # -------------------------------------------- -APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1,172.0.0.0/8 +APP_TRUSTED_PROXIES=192.168.1.1,10.0.0.1,172.16.0.0/12 ALLOW_IFRAMING=false REFERRER_POLICY=same-origin ENABLE_CSP=false diff --git a/.env.dusk.example b/.env.dusk.example index 2f16f44c7a..e306a06cf8 100644 --- a/.env.dusk.example +++ b/.env.dusk.example @@ -6,7 +6,7 @@ APP_DEBUG=false APP_KEY=base64:hTUIUh9CP6dQx+6EjSlfWTgbaMaaRvlpEwk45vp+xmk= APP_URL=http://127.0.0.1:8000 APP_TIMEZONE='US/Eastern' -APP_LOCALE=en +APP_LOCALE=en-US APP_LOCKED=false MAX_RESULTS=200 diff --git a/.env.example b/.env.example index c92dc22b71..81de24ead1 100644 --- a/.env.example +++ b/.env.example @@ -30,8 +30,11 @@ DB_USERNAME=null DB_PASSWORD=null DB_PREFIX=null DB_DUMP_PATH='/usr/bin' +DB_DUMP_SKIP_SSL=false DB_CHARSET=utf8mb4 DB_COLLATION=utf8mb4_unicode_ci +DB_SANITIZE_BY_DEFAULT=false + # -------------------------------------------- # OPTIONAL: SSL DATABASE SETTINGS @@ -78,6 +81,12 @@ MAIL_BACKUP_NOTIFICATION_ADDRESS=null BACKUP_ENV=true ALLOW_BACKUP_DELETE=false ALLOW_DATA_PURGE=false +ALL_BACKUP_KEEP_DAYS=7 +DAILY_BACKUP_KEEP_DAYS=16 +WEEKLY_BACKUP_KEEP_WEEKS=8 +MONTHLY_BACKUP_KEEP_MONTHS=4 +YEARLY_BACKUP_KEEP_YEARS=2 +BACKUP_PURGE_OLDEST_AT_MEGS=5000 # -------------------------------------------- # OPTIONAL: SESSION SETTINGS @@ -87,10 +96,11 @@ SESSION_LIFETIME=12000 EXPIRE_ON_CLOSE=false ENCRYPT=false COOKIE_NAME=snipeit_session +PASSPORT_COOKIE_NAME='snipeit_passport_token' COOKIE_DOMAIN=null SECURE_COOKIES=false API_TOKEN_EXPIRATION_YEARS=15 -BS_TABLE_STORAGE=cookieStorage +BS_TABLE_STORAGE=localStorage BS_TABLE_DEEPLINK=true # -------------------------------------------- @@ -169,6 +179,7 @@ PASSWORD_RESET_MAX_ATTEMPTS_PER_MIN=50 # OPTIONAL: MISC # -------------------------------------------- LOG_CHANNEL=single +LOG_DEPRECATIONS=false LOG_MAX_DAYS=10 APP_LOCKED=false APP_CIPHER=AES-256-CBC @@ -183,6 +194,7 @@ REPORT_TIME_LIMIT=12000 REQUIRE_SAML=false API_THROTTLE_PER_MINUTE=120 CSV_ESCAPE_FORMULAS=true +LIVEWIRE_URL_PREFIX=null # -------------------------------------------- # OPTIONAL: HASHING diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index b5f067cc68..0000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,38 +0,0 @@ -#### Expected Behavior (or desired behavior if a feature request) - -(what you expect to happen goes here) - ------ - -#### Actual Behavior - -(what actually happens goes here) - ------ - -#### Please confirm you have done the following before posting your bug report: - -- [ ] I have enabled debug mode -- [ ] I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues) - ------ -#### Provide answers to these questions: - -- Is this a fresh install or an upgrade? -- Version of Snipe-IT you're running -- Version of PHP you're running -- Version of MySQL/MariaDB you're running -- What OS and web server you're running Snipe-IT on -- What method you used to install Snipe-IT (install.sh, manual installation, docker, etc) -- WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error -- What specific Snipe-IT page you're on, and what specific element you're interacting with to trigger the error -- If a stacktrace is provided in the error, include that too. -- Any errors that appear in your browser's error console. -- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo. -- Include any additional information you can find in `storage/logs` and your webserver's logs. -- Include what you've done so far in the installation, and if you got any error messages along the way. -- Indicate whether or not you've manually edited any data directly in the database - -Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you. - -https://snipe-it.readme.io/docs/getting-help diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 5cc00c2a31..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,129 +0,0 @@ -name: Bug Report -description: Create a report to help us improve -body: - - type: checkboxes - attributes: - label: Debug mode - description: Please confirm you have done the following before posting your bug report - options: - - label: I have enabled debug mode - required: true - - label: I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues) - required: true - - type: textarea - attributes: - label: Describe the bug - description: A clear and concise description of what the bug is. - validations: - required: true - - type: textarea - attributes: - label: Reproduction steps - description: Steps to reproduce the behavior. - value: | - 1. - 2. - 3. - ... - validations: - required: true - - type: textarea - attributes: - label: Expected behavior - description: A clear and concise description of what you expected to happen. - validations: - required: true - - type: textarea - attributes: - label: Screenshots - description: 'If applicable, add screenshots to help explain your problem.' - - type: markdown - attributes: - value: "### Server" - - type: input - attributes: - label: Snipe-IT Version - validations: - required: true - - type: input - id: server_operatingSystem - attributes: - label: Operating System - description: 'e.g. Ubuntu, Windows' - validations: - required: true - - type: input - attributes: - label: Web Server - description: 'e.g. Apache, IIS' - validations: - required: true - - type: input - attributes: - label: PHP Version - validations: - required: true - - type: markdown - attributes: - value: "### Desktop" - - type: input - id: desktop_operatingSystem - attributes: - label: Operating System - description: 'e.g. Ubuntu, Windows' - - type: input - id: desktop_browser - attributes: - label: Browser - description: 'e.g. Google Chrome, Safari' - - type: input - id: desktop_version - attributes: - label: Version - description: 'e.g. 93' - - type: markdown - attributes: - value: "### Mobile" - - type: input - attributes: - label: Device - description: 'e.g. iPhone 6, Pixel 4a' - - type: input - id: mobile_operatingSystem - attributes: - label: Operating System - description: 'e.g. iOS 8.1, Android 9' - - type: input - id: mobile_browser - attributes: - label: Browser - description: 'e.g. Google Chrome, Safari' - - type: input - id: mobile_version - attributes: - label: Version - description: 'e.g. 93' - - type: textarea - attributes: - label: Error messages - description: | - WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error - If a stacktrace is provided in the error, include that too. - Any errors that appear in your browser's error console. - Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo. - Include any additional information you can find in `storage/logs` and your webserver's logs. - Include the output from `php -m` (this should display what modules you have enabled.) - render: shell - - type: textarea - attributes: - label: Additional context - description: | - Is this a fresh install or an upgrade? - What OS and web server you're running Snipe-IT on - What method you used to install Snipe-IT (install.sh, manual installation, docker, etc) - Include what you've done so far in the installation, and if you got any error messages along the way. - Indicate whether or not you've manually edited any data directly in the database - Add any other context about the problem here. - - type: markdown - attributes: - value: Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 3ba13e0cec..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index dec3a8fe08..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Feature Request -description: Suggest an idea for this project -title: "[Feature Request]: " -labels: ["feature request"] -body: - - type: textarea - attributes: - label: Is your feature request related to a problem? Please describe. - description: A clear and concise description of what the problem is. The more information you can provide about your use-case, the more liklely we are to consider your feature. - validations: - required: true - - type: textarea - attributes: - label: Describe the solution you'd like - description: A clear and concise description of what you want to happen. - validations: - required: true - - type: textarea - attributes: - label: Describe alternatives you've considered - description: A clear and concise description of any alternative solutions or features you've considered. - - type: textarea - attributes: - label: Additional context - description: Add any other context or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 7ef56a0d5f..0000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,40 +0,0 @@ -# Description - -Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context, providing screenshots where practical. List any dependencies that are required for this change. - -Fixes # (issue) - -## Type of change - -Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] This change requires a documentation update - -# How Has This Been Tested? - -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration - -- [ ] Test A -- [ ] Test B - -**Test Configuration**: -* PHP version: -* MySQL version -* Webserver version -* OS version - - -# Checklist: - -- [ ] I have read the Contributing documentation available here: https://snipe-it.readme.io/docs/contributing-overview -- [ ] I have formatted this PR according to the project guidelines: https://snipe-it.readme.io/docs/contributing-overview#pull-request-guidelines -- [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] New and existing unit tests pass locally with my changes diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 53acc282e4..0000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,43 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - security - - :woman_technologist: ready for dev - - :moneybag: bounty - - :hand: bug - - "🔐 security" - - "👩‍💻 ready for dev" - - "💰 bounty" - - "✋ bug" - -exemptMilestones: true - -# Label to use when marking an issue as stale -staleLabel: stale - -only: issues - -# Comment to post when removing the stale label. -unmarkComment: > - Okay, it looks like this issue or feature request might still be important. We'll re-open - it for now. Thank you for letting us know! - -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - Is this still relevant? We haven't heard from anyone in a bit. If so, - please comment with any updates or additional detail. - - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Don't - take it personally, we just need to keep a handle on things. Thank you - for your contributions! -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: > - This issue has been automatically closed because it has not had - recent activity. If you believe this is still an issue, please confirm that - this issue is still happening in the most recent version of Snipe-IT and reply - to this thread to re-open it. diff --git a/.github/travis-memory.ini b/.github/travis-memory.ini deleted file mode 100644 index 6bd8019ec4..0000000000 --- a/.github/travis-memory.ini +++ /dev/null @@ -1 +0,0 @@ -memory_limit= 2048M diff --git a/.github/weekly-digest.yml b/.github/weekly-digest.yml deleted file mode 100644 index fe502fbc98..0000000000 --- a/.github/weekly-digest.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Configuration for weekly-digest - https://github.com/apps/weekly-digest -publishDay: sun -canPublishIssues: true -canPublishPullRequests: true -canPublishContributors: true -canPublishStargazers: true -canPublishCommits: true diff --git a/.github/workflows/codacy-analysis.yml b/.github/workflows/codacy-analysis.yml index f8368dff83..a11509d69e 100644 --- a/.github/workflows/codacy-analysis.yml +++ b/.github/workflows/codacy-analysis.yml @@ -10,10 +10,10 @@ name: Codacy Security Scan on: push: - branches: [ master ] + branches: [ develop ] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [ develop ] schedule: - cron: '36 23 * * 3' @@ -22,11 +22,11 @@ permissions: jobs: codacy-security-scan: - # Ensure schedule job never runs on forked repos. It's only executed for 'snipe/snipe-it' + # Ensure schedule job never runs on forked repos. It's only executed for 'grokability/snipe-it' permissions: contents: read # for actions/checkout to fetch code security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - if: (github.repository == 'snipe/snipe-it') || ((github.repository != 'snipe/snipe-it') && (github.event_name != 'schedule')) + if: (github.repository == 'grokability/snipe-it') || ((github.repository != 'grokability/snipe-it') && (github.event_name != 'schedule')) name: Codacy Security Scan runs-on: ubuntu-latest steps: @@ -36,7 +36,7 @@ jobs: # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - name: Run Codacy Analysis CLI - uses: codacy/codacy-analysis-cli-action@v4.4.1 + uses: codacy/codacy-analysis-cli-action@v4.4.5 with: # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository # You can also omit the token and run the tools that support default configurations diff --git a/.github/workflows/docker-alpine.yml b/.github/workflows/docker-alpine.yml index bd46f9567b..86e0c022e2 100644 --- a/.github/workflows/docker-alpine.yml +++ b/.github/workflows/docker-alpine.yml @@ -20,8 +20,8 @@ permissions: jobs: docker: - # Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it' - if: github.repository == 'snipe/snipe-it' + # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it' + if: github.repository == 'grokability/snipe-it' runs-on: ubuntu-latest env: # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) @@ -32,7 +32,7 @@ jobs: type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }},suffix=-alpine type=ref,event=tag,suffix=-alpine - type=semver,pattern=v{{major}}-latest-alpine + type=semver,pattern=v{{major}}-latest-alpine # Define default tag "flavor" for docker/metadata-action per # https://github.com/docker/metadata-action#flavor-input # We turn off 'latest' tag by default. diff --git a/.github/workflows/docker.yml b/.github/workflows/docker-ubuntu.yml similarity index 95% rename from .github/workflows/docker.yml rename to .github/workflows/docker-ubuntu.yml index adb87f3a5d..a78ba7413c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker-ubuntu.yml @@ -1,5 +1,5 @@ # Snipe-IT Docker image build for hub.docker.com -name: Docker images +name: Docker images (Ubuntu) # Run this Build for all pushes to 'master' or develop branch, or tagged releases. # Also run for PRs to ensure PR doesn't break Docker build process @@ -20,8 +20,8 @@ permissions: jobs: docker: - # Ensure this job never runs on forked repos. It's only executed for 'snipe/snipe-it' - if: github.repository == 'snipe/snipe-it' + # Ensure this job never runs on forked repos. It's only executed for 'grokability/snipe-it' + if: github.repository == 'grokability/snipe-it' runs-on: ubuntu-latest env: # Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action) @@ -32,7 +32,7 @@ jobs: type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }} type=ref,event=tag - type=semver,pattern=v{{major}}-latest + type=semver,pattern=v{{major}}-latest # Define default tag "flavor" for docker/metadata-action per # https://github.com/docker/metadata-action#flavor-input # We turn off 'latest' tag by default. diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..5a4042aee4 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,40 @@ +name: 'Close stale issues' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + # contents: write # only for delete-branch option + issues: write + # pull-requests: write + steps: + - uses: actions/stale@v9 + with: + debug-only: true + ascending: true + operations-per-run: 1000 # just while we're debugging + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 60 + days-before-close: 7 + exempt-all-milestones: true + stale-issue-message: > + Is this still relevant? We haven't heard from anyone in a bit. If so, + please comment with any updates or additional detail. + + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Don't + take it personally, we just need to keep a handle on things. Thank you + for your contributions! + close-issue-message: > + This issue has been automatically closed because it has not had + recent activity. If you believe this is still an issue, please confirm that + this issue is still happening in the most recent version of Snipe-IT and reply + to this thread to re-open it. + # There doesn't seem to be a 'reopen issue message'? + # Since there is no 'stale-pr-message' - PR's should not be stale'd + stale-issue-label: stale + exempt-issue-labels: > + pinned,security,:woman_technologist: ready for dev,:moneybag: bounty,:hand: bug,🔐 security,👩‍💻 ready for dev,💰 bounty,✋ bug \ No newline at end of file diff --git a/.github/workflows/tests-mysql.yml b/.github/workflows/tests-mysql.yml index 737a86dca3..16190900b0 100644 --- a/.github/workflows/tests-mysql.yml +++ b/.github/workflows/tests-mysql.yml @@ -25,9 +25,9 @@ jobs: fail-fast: false matrix: php-version: - - "8.1" - "8.2" - "8.3" + - "8.4" name: PHP ${{ matrix.php-version }} @@ -67,7 +67,7 @@ jobs: run: | php artisan key:generate php artisan migrate --force - php artisan passport:install + php artisan passport:install --no-interaction chmod -R 777 storage bootstrap/cache - name: Execute tests (Unit and Feature tests) via PHPUnit @@ -76,4 +76,4 @@ jobs: DB_DATABASE: snipeit DB_PORT: ${{ job.services.mysql.ports[3306] }} DB_USERNAME: root - run: php artisan test --parallel + run: php artisan test diff --git a/.github/workflows/tests-postgres.yml b/.github/workflows/tests-postgres.yml index b12fdebbc4..d111fc87a5 100644 --- a/.github/workflows/tests-postgres.yml +++ b/.github/workflows/tests-postgres.yml @@ -22,6 +22,9 @@ jobs: matrix: php-version: - "8.2" + - "8.3" + - "8.4" + name: PHP ${{ matrix.php-version }} @@ -62,7 +65,7 @@ jobs: run: | php artisan key:generate php artisan migrate --force - php artisan passport:install + php artisan passport:install --no-interaction chmod -R 777 storage bootstrap/cache - name: Execute tests (Unit and Feature tests) via PHPUnit @@ -72,4 +75,4 @@ jobs: DB_PORT: ${{ job.services.postgresql.ports[5432] }} DB_USERNAME: snipeit DB_PASSWORD: password - run: php artisan test --parallel + run: php artisan test diff --git a/.github/workflows/tests-sqlite.yml b/.github/workflows/tests-sqlite.yml index 49c7c92d8e..cd06805e01 100644 --- a/.github/workflows/tests-sqlite.yml +++ b/.github/workflows/tests-sqlite.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: php-version: - - "8.1.1" + - "8.3" name: PHP ${{ matrix.php-version }} @@ -43,6 +43,9 @@ jobs: cp -v .env.testing.example .env cp -v .env.testing.example .env.testing + - name: Create database file + run: touch database/database.sqlite + - name: Install Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist @@ -57,5 +60,5 @@ jobs: - name: Execute tests (Unit and Feature tests) via PHPUnit env: - DB_CONNECTION: sqlite_testing - run: php artisan test --parallel + DB_CONNECTION: sqlite + run: php artisan test diff --git a/.gitignore b/.gitignore index bf8360ba24..17a7d28dab 100755 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ storage/private_uploads/users/* tests/_data/scenarios tests/_output/* tests/_support/_generated/* +tests/coverage/* /npm-debug.log /storage/oauth-private.key /storage/oauth-public.key @@ -67,3 +68,6 @@ _ide_helper_models.php /.phplint-cache storage/ldap_client_tls.cert storage/ldap_client_tls.key +/storage/framework/testing + +/.phpunit.cache \ No newline at end of file diff --git a/.pa11yci.json b/.pa11yci.json new file mode 100644 index 0000000000..73b84a8140 --- /dev/null +++ b/.pa11yci.json @@ -0,0 +1,240 @@ +{ + "standard": "WCAG2AA", + "level": "error", + "defaults": { + "useIncognitoBrowserContext": false, + "timeout": 500000, + "wait": 5000, + "ignore" : [ + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G145.Fail", + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail" + ], + + "viewport": { + "width": 1280, + "height": 1024 + } + }, + "urls": [ + { + "__NOTE" : "this should always be FIRST (if browser context is preserved)", + "url": "https://snipe-it.test/login", + "actions": [ + "navigate to https://snipe-it.test/login", + "screen capture tests/pa11y/login.png", + "set field input[name='username'] to admin", + "set field input[name='password'] to password", + "click element button[type=submit]", + "wait for url to be https://snipe-it.test/", + "screen capture tests/pa11y/dashboard.png" + ] + }, + { + "url" : "https://snipe-it.test/admin", + "actions" : [ + "navigate to https://snipe-it.test/admin", + "screen capture tests/pa11y/admin-settings.png" + ] + }, + { + "url" : "https://snipe-it.test/admin/branding", + "actions" : [ + "navigate to https://snipe-it.test/admin/branding", + "screen capture tests/pa11y/admin-branding.png" + ] + }, + { + "url" : "https://snipe-it.test/admin/general", + "actions" : [ + "navigate to https://snipe-it.test/admin/general", + "screen capture tests/pa11y/admin-general.png" + ] + }, + { + "url" : "https://snipe-it.test/hardware/create", + "actions" : [ + "navigate to https://snipe-it.test/hardware/create", + "screen capture tests/pa11y/asset-create.png" + ] + }, + { + "url" : "https://snipe-it.test/hardware", + "actions" : [ + "navigate to https://snipe-it.test/hardware", + "screen capture tests/pa11y/asset-list.png" + ] + }, + { + "url" : "https://snipe-it.test/hardware/1", + "actions" : [ + "navigate to https://snipe-it.test/hardware/1", + "screen capture tests/pa11y/asset-detail.png" + ] + }, + { + "url" : "https://snipe-it.test/account/view-assets", + "actions" : [ + "navigate to https://snipe-it.test/account/view-assets", + "screen capture tests/pa11y/profile.png" + ] + }, + { + "url" : "https://snipe-it.test/licences", + "actions" : [ + "navigate to https://snipe-it.test/licenses", + "screen capture tests/pa11y/license-list.png" + ] + }, + { + "url" : "https://snipe-it.test/licences/create", + "actions" : [ + "navigate to https://snipe-it.test/licenses/create", + "screen capture tests/pa11y/license-create.png" + ] + }, + { + "url" : "https://snipe-it.test/licences/1", + "actions" : [ + "navigate to https://snipe-it.test/licenses/1", + "screen capture tests/pa11y/license-view.png" + ] + }, + { + "url" : "https://snipe-it.test/consumables", + "actions" : [ + "navigate to https://snipe-it.test/consumables", + "screen capture tests/pa11y/consumable-list.png" + ] + }, + { + "url" : "https://snipe-it.test/consumables/create", + "actions" : [ + "navigate to https://snipe-it.test/consumables/create", + "screen capture tests/pa11y/consumable-create.png" + ] + }, + { + "url" : "https://snipe-it.test/consumables/1", + "actions" : [ + "navigate to https://snipe-it.test/consumables/1", + "screen capture tests/pa11y/consumable-view.png" + ] + }, + { + "url" : "https://snipe-it.test/accessories", + "actions" : [ + "navigate to https://snipe-it.test/accessories", + "screen capture tests/pa11y/accessory-list.png" + ] + }, + { + "url" : "https://snipe-it.test/accessories/create", + "actions" : [ + "navigate to https://snipe-it.test/accessories/create", + "screen capture tests/pa11y/accessory-create.png" + ] + }, + { + "url" : "https://snipe-it.test/accessories/1", + "actions" : [ + "navigate to https://snipe-it.test/accessories/1", + "screen capture tests/pa11y/accessory-view.png" + ] + }, + { + "url" : "https://snipe-it.test/locations", + "actions" : [ + "navigate to https://snipe-it.test/locations", + "screen capture tests/pa11y/location-list.png" + ] + }, + { + "url" : "https://snipe-it.test/locations/create", + "actions" : [ + "navigate to https://snipe-it.test/locations/create", + "screen capture tests/pa11y/location-create.png" + ] + }, + { + "url" : "https://snipe-it.test/locations/1", + "actions" : [ + "navigate to https://snipe-it.test/locations/1", + "screen capture tests/pa11y/location-view.png" + ] + }, + + { + "url" : "https://snipe-it.test/models", + "actions" : [ + "navigate to https://snipe-it.test/models", + "screen capture tests/pa11y/model-list.png" + ] + }, + { + "url" : "https://snipe-it.test/models/create", + "actions" : [ + "navigate to https://snipe-it.test/models/create", + "screen capture tests/pa11y/model-create.png" + ] + }, + { + "url" : "https://snipe-it.test/models/1", + "actions" : [ + "navigate to https://snipe-it.test/models/1", + "screen capture tests/pa11y/model-view.png" + ] + }, + + { + "url" : "https://snipe-it.test/companies", + "actions" : [ + "navigate to https://snipe-it.test/companies", + "screen capture tests/pa11y/company-list.png" + ] + }, + { + "url" : "https://snipe-it.test/companies/create", + "actions" : [ + "navigate to https://snipe-it.test/companies/create", + "screen capture tests/pa11y/company-create.png" + ] + }, + { + "url" : "https://snipe-it.test/companies/1", + "actions" : [ + "navigate to https://snipe-it.test/companies/1", + "screen capture tests/pa11y/company-view.png" + ] + }, + + { + "url" : "https://snipe-it.test/departments", + "actions" : [ + "navigate to https://snipe-it.test/departments", + "screen capture tests/pa11y/department-list.png" + ] + }, + { + "url" : "https://snipe-it.test/departments/create", + "actions" : [ + "navigate to https://snipe-it.test/departments/create", + "screen capture tests/pa11y/department-create.png" + ] + }, + { + "url" : "https://snipe-it.test/departments/1", + "actions" : [ + "navigate to https://snipe-it.test/departments/1", + "screen capture tests/pa11y/department-view.png" + ] + }, + + { + "url" : "https://snipe-it.test/invalid-url", + "actions" : [ + "navigate to https://snipe-it.test/invalid-url", + "screen capture tests/pa11y/404.png" + ] + } + ] +} \ No newline at end of file diff --git a/.upgrade_requirements.json b/.upgrade_requirements.json index 53d7337b62..9c4ece093b 100644 --- a/.upgrade_requirements.json +++ b/.upgrade_requirements.json @@ -3,8 +3,8 @@ "DOC2": "In other words, what you see locally are the requirements for your _current_ install", "DOC3": "Please don't rely on these versions for planning upgrades unless you've fetched the most recent version", "DOC4": "You should really just ignore it and run upgrade.php. Really", - "php_min_version": "8.1.0", - "php_max_major_minor": "8.3", - "php_max_wontwork": "8.4.0", - "current_snipeit_version": "7.0" + "php_min_version": "8.2.0", + "php_max_major_minor": "8.4", + "php_max_wontwork": "8.5.0", + "current_snipeit_version": "8.0" } diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c0ca7951dd..7ab3810780 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,461 +1,73 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)) who have helped Snipe-IT get this far: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
snipe
snipe

💻 🚇 📖 ⚠️ 🐛 🎨 👀
Brady Wetherington
Brady Wetherington

💻 📖 🚇 👀
Daniel Meltzer
Daniel Meltzer

💻 ⚠️ 📖
Michael T
Michael T

💻
madd15
madd15

📖 💬
Vincent Sposato
Vincent Sposato

💻
Andrea Bergamasco
Andrea Bergamasco

💻
Karol
Karol

🌍 💻
morph027
morph027

💻
fvleminckx
fvleminckx

🚇
itsupportcmsukorg
itsupportcmsukorg

💻 🐛
Frank
Frank

💻
Deleted user
Deleted user

🌍 💻
tiagom62
tiagom62

💻 🚇
Ryan Stafford
Ryan Stafford

💻
Eammon Hanlon
Eammon Hanlon

💻
zjean
zjean

💻
Matthias Frei
Matthias Frei

💻
opsydev
opsydev

💻
Daniel Dreier
Daniel Dreier

💻
Nikolai Prokoschenko
Nikolai Prokoschenko

💻
Drew
Drew

💻
Walter
Walter

💻
Petr Baloun
Petr Baloun

💻
reidblomquist
reidblomquist

📖
Mathieu Kooiman
Mathieu Kooiman

💻
csayre
csayre

📖
Adam Dunson
Adam Dunson

💻
Hereward
Hereward

💻
swoopdk
swoopdk

💻
Abdullah Alansari
Abdullah Alansari

💻
Micael Rodrigues
Micael Rodrigues

💻
Patrick Gallagher
Patrick Gallagher

📖
Miliamber
Miliamber

💻
hawk554
hawk554

💻
Justin Kerr
Justin Kerr

💻
Ira W. Snyder
Ira W. Snyder

📖
Aladin Alaily
Aladin Alaily

💻
Chase Hansen
Chase Hansen

💻 💬 🐛
IDM Helpdesk
IDM Helpdesk

💻
Kai
Kai

💻
Michael Daniels
Michael Daniels

💻
Tom Castleman
Tom Castleman

💻
Daniel Nemanic
Daniel Nemanic

💻
SouthWolf
SouthWolf

💻
Ivar Nesje
Ivar Nesje

💻
Jérémy Benoist
Jérémy Benoist

📖
Chris Leathley
Chris Leathley

🚇
splaer
splaer

🐛 💻
Joe Ferguson
Joe Ferguson

💻
diwanicki
diwanicki

💻 📖
Lee Thoong Ching
Lee Thoong Ching

📖 💻
Marek Šuppa
Marek Šuppa

💻
Juan J. Martinez
Juan J. Martinez

🌍
R Ryan Dial
R Ryan Dial

🌍
Andrej Manduch
Andrej Manduch

📖
Jay Richards
Jay Richards

💻
Alexander Innes
Alexander Innes

💻
Danny Garcia
Danny Garcia

💻
archpoint
archpoint

💻
Jake McGraw
Jake McGraw

💻
FleischKarussel
FleischKarussel

📖
Dylan Yi
Dylan Yi

💻
Gil Rutkowski
Gil Rutkowski

💻
Desmond Morris
Desmond Morris

💻
Nick Peelman
Nick Peelman

💻
Abraham Vegh
Abraham Vegh

💻
Mohamed Rashid
Mohamed Rashid

📖
Kasey
Kasey

💻
Brett
Brett

⚠️
Jason Spriggs
Jason Spriggs

💻
Nate Felton
Nate Felton

💻
Manasses Ferreira
Manasses Ferreira

💻
Steve
Steve

⚠️
matc
matc

⚠️
Cole R. Davis
Cole R. Davis

⚠️
gibsonjoshua55
gibsonjoshua55

💻
Robin Temme
Robin Temme

💻
Iman
Iman

💻
Richard Hofman
Richard Hofman

💻
gizzmojr
gizzmojr

💻
Jenny Li
Jenny Li

📖
Geoff Young
Geoff Young

💻
Elliot Blackburn
Elliot Blackburn

📖
Tõnis Ormisson
Tõnis Ormisson

💻
Nicolai Essig
Nicolai Essig

💻
Danielle
Danielle

📖
Lawrence
Lawrence

⚠️ 🐛
uknzaeinozpas
uknzaeinozpas

⚠️ 💻
Ryan
Ryan

📖
vcordes79
vcordes79

💻
fordster78
fordster78

💻
CronKz
CronKz

💻 🌍
Tim Bishop
Tim Bishop

💻
Sean McIlvenna
Sean McIlvenna

💻
cepacs
cepacs

🐛 📖
lea-mink
lea-mink

💻
Hannah Tinkler
Hannah Tinkler

💻
Doeke Zanstra
Doeke Zanstra

💻
Djamon Staal
Djamon Staal

💻
Earl Ramirez
Earl Ramirez

💻
Richard Ray Thomas
Richard Ray Thomas

💻
Ryan Kuba
Ryan Kuba

💻
Brian Monroe
Brian Monroe

💻
plexorama
plexorama

💻
Till Deeke
Till Deeke

💻
5quirrel
5quirrel

💻
Jason
Jason

💻
Antti
Antti

💻
DeusMaximus
DeusMaximus

💻
a-royal
a-royal

🌍
Alberto Aldrigo
Alberto Aldrigo

🌍
Alex Stanev
Alex Stanev

🌍
Andreas Rehm
Andreas Rehm

🌍
Andreas Erhard
Andreas Erhard

🌍
Andrés Vanegas Jiménez
Andrés Vanegas Jiménez

🌍
Antonio Schiavon
Antonio Schiavon

🌍
benunter
benunter

🌍
Borys Żmuda
Borys Żmuda

🌍
chibacityblues
chibacityblues

🌍
Chien Wei Lin
Chien Wei Lin

🌍
Christian Schuster
Christian Schuster

🌍
Christian Stefanus
Christian Stefanus

🌍
wxcafé
wxcafé

🌍
dpyroc
dpyroc

🌍
Daniel Friedlmaier
Daniel Friedlmaier

🌍
Daniel Heene
Daniel Heene

🌍
danielcb
danielcb

🌍
Dominik Senti
Dominik Senti

🌍
Eric Gautheron
Eric Gautheron

🌍
Erlend Pilø
Erlend Pilø

🌍
Fabio Rapposelli
Fabio Rapposelli

🌍
Felipe Barros
Felipe Barros

🌍
Fernando Possebon
Fernando Possebon

🌍
gdraque
gdraque

🌍
Georg Wallisch
Georg Wallisch

🌍
Gerardo Robles
Gerardo Robles

🌍
Gluek
Gluek

🌍
AdnanAbuShahad
AdnanAbuShahad

🌍
Hafidzi My
Hafidzi My

🌍
Harim Park
Harim Park

🌍
Henrik Kentsson
Henrik Kentsson

🌍
Husnul Yaqien
Husnul Yaqien

🌍
Ibrahim
Ibrahim

🌍
igolman
igolman

🌍
itangiang
itangiang

🌍
jarby1211
jarby1211

🌍
Jhonn Willker
Jhonn Willker

🌍
Jose
Jose

🌍
laopangzi
laopangzi

🌍
Lars Strojny
Lars Strojny

🌍
MarcosBL
MarcosBL

🌍
marie joy cajes
marie joy cajes

🌍
Mark S. Johansen
Mark S. Johansen

🌍
Martin Stub
Martin Stub

🌍
Meyer Flavio
Meyer Flavio

🌍
Micael Rodrigues
Micael Rodrigues

🌍
Mikael Rasmussen
Mikael Rasmussen

🌍
IxFail
IxFail

🌍
Mohammed Fota
Mohammed Fota

🌍
Moayad Alserihi
Moayad Alserihi

🌍
saymd
saymd

🌍
Patrik Larsson
Patrik Larsson

🌍
drcryo
drcryo

🌍
pawel1615
pawel1615

🌍
bodrovics
bodrovics

🌍
priatna
priatna

🌍
Fan Jiang
Fan Jiang

🌍
ragnarcx
ragnarcx

🌍
Rein van Haaren
Rein van Haaren

🌍
Teguh Dwicaksana
Teguh Dwicaksana

🌍
fraccie
fraccie

🌍
vinzruzell
vinzruzell

🌍
Kevin Austin
Kevin Austin

🌍
Wira Sandy
Wira Sandy

🌍
Илья
Илья

🌍
GodUseVPN
GodUseVPN

🌍
周周
周周

🌍
Sam
Sam

💻
Azerothian
Azerothian

💻
Wes Hulette
Wes Hulette

💻
patrict
patrict

💻
Dmitriy Minaev
Dmitriy Minaev

💻
liquidhorse
liquidhorse

💻
Jordi Boggiano
Jordi Boggiano

💻
Ivan Nieto
Ivan Nieto

💻
Ben RUBSON
Ben RUBSON

💻
NMathar
NMathar

💻
Steffen
Steffen

💻
Sxderp
Sxderp

💻
fanta8897
fanta8897

💻
Andrey Bolonin
Andrey Bolonin

💻
shinayoshi
shinayoshi

💻
Hubert
Hubert

💻
KeenRivals
KeenRivals

💻
omyno
omyno

💻
Evgeny
Evgeny

💻
Colin Campbell
Colin Campbell

💻
Ľubomír Kučera
Ľubomír Kučera

💻
Martin Meredith
Martin Meredith

💻
Tim Farmer
Tim Farmer

💻
Marián Skrip
Marián Skrip

💻
Godfrey Martinez
Godfrey Martinez

💻
bigtreeEdo
bigtreeEdo

💻
Colin  McNeil
Colin McNeil

💻
JoKneeMo
JoKneeMo

💻
Joshi
Joshi

💻
Anthony Burns
Anthony Burns

💻
johnson-yi
johnson-yi

💻
Sanjay Govind
Sanjay Govind

💻
Peter Upfold
Peter Upfold

💻
Jared Biel
Jared Biel

💻
Dampfklon
Dampfklon

💻
Charles Hamilton
Charles Hamilton

💻
Giuseppe Iannello
Giuseppe Iannello

💻
Peter Dave Hello
Peter Dave Hello

💻
sigmoidal
sigmoidal

💻
Vincent Lainé
Vincent Lainé

💻
Lucas Pleß
Lucas Pleß

💻
Ian Littman
Ian Littman

💻
João Paulo
João Paulo

💻
ThoBur
ThoBur

💻
Alexander Chibrikin
Alexander Chibrikin

💻
Anthony Winstanley
Anthony Winstanley

💻
Folke
Folke

💻
Bennett Blodinger
Bennett Blodinger

💻
NMC
NMC

💻
andres-baller
andres-baller

💻
sean-borg
sean-borg

💻
EDVLeer
EDVLeer

💻
Kurokat
Kurokat

💻
Kevin Köllmann
Kevin Köllmann

💻
sw-mreyes
sw-mreyes

💻
Joel Pittet
Joel Pittet

💻
Eli Young
Eli Young

💻
Raell Dottin
Raell Dottin

💻
Tom Misilo
Tom Misilo

💻
David Davenne
David Davenne

💻
Mark Stenglein
Mark Stenglein

💻
ajsy
ajsy

💻
Jan Kiesewetter
Jan Kiesewetter

💻
Tetrachloromethane250
Tetrachloromethane250

💻
Lars Kajes
Lars Kajes

💻
Joly0
Joly0

💻
theburger
theburger

💻
David Valin Alonso
David Valin Alonso

💻
andreaci
andreaci

💻
Jelle Sebreghts
Jelle Sebreghts

💻
Michael Pietsch
Michael Pietsch

Masudul Haque Shihab
Masudul Haque Shihab

💻
Supapong Areeprasertkul
Supapong Areeprasertkul

💻
Peter Sarossy
Peter Sarossy

💻
Renee Margaret McConahy
Renee Margaret McConahy

💻
JohnnyPicnic
JohnnyPicnic

💻
markbrule
markbrule

💻
Mike Campbell
Mike Campbell

💻
tbrconnect
tbrconnect

💻
kcoyo
kcoyo

💻
Travis Miller
Travis Miller

💻
Evan Taylor
Evan Taylor

💻
Petri Asikainen
Petri Asikainen

💻
derdeagle
derdeagle

💻
Mike Frysinger
Mike Frysinger

💻
ALPHA
ALPHA

💻
FliegenKLATSCH
FliegenKLATSCH

💻
Jeremy Price
Jeremy Price

💻
Toreg87
Toreg87

💻
Matthew Nickson
Matthew Nickson

💻
Jethro Nederhof
Jethro Nederhof

💻
Oskar Stenberg
Oskar Stenberg

💻
Robert-Azelis
Robert-Azelis

💻
Alexander William Smith
Alexander William Smith

💻
LEITWERK AG
LEITWERK AG

💻
Adam
Adam

💻
Ian
Ian

💻
Shao Yu-Lung (Allen)
Shao Yu-Lung (Allen)

💻
Haxatron
Haxatron

💻
PlaneNuts
PlaneNuts

💻
Bradley Coudriet
Bradley Coudriet

💻
Dalton Durst
Dalton Durst

💻
Alex Janes
Alex Janes

💻
Nuraeil
Nuraeil

💻
TenOfTens
TenOfTens

💻
waffle
waffle

💻
Yevhenii Huzii
Yevhenii Huzii

💻
Achmad Fienan Rahardianto
Achmad Fienan Rahardianto

💻
Yevhenii Huzii
Yevhenii Huzii

💻
Christian Weirich
Christian Weirich

💻
denzfarid
denzfarid

ntbutler-nbcs
ntbutler-nbcs

💻
Naveen
Naveen

💻
Mike Roquemore
Mike Roquemore

💻
Daniel Reeder
Daniel Reeder

🌍 🌍 💻
vickyjaura183
vickyjaura183

💻
Peace
Peace

💻
Kyle Gordon
Kyle Gordon

💻
Katharina Drexel
Katharina Drexel

💻
David Sferruzza
David Sferruzza

💻
Rick Nelson
Rick Nelson

💻
BasO12
BasO12

💻
Vautia
Vautia

💻
Chris Hartjes
Chris Hartjes

💻
geo-chen
geo-chen

💻
Phan Nguyen
Phan Nguyen

💻
Iisakki Jaakkola
Iisakki Jaakkola

💻
Ikko Ashimine
Ikko Ashimine

💻
Lukas Fehling
Lukas Fehling

💻
Fernando Almeida
Fernando Almeida

💻
akemidx
akemidx

💻
Oguz Bilgic
Oguz Bilgic

💻
Scooter Crawford
Scooter Crawford

💻
subdriven
subdriven

💻
Andrew Savinykh
Andrew Savinykh

💻
Tadayuki Onishi
Tadayuki Onishi

💻
Florian
Florian

💻
Spencer Long
Spencer Long

💻
Marcus Moore
Marcus Moore

💻
Martin Meredith
Martin Meredith

dboth
dboth

💻
Zachary Fleck
Zachary Fleck

💻
VIKAAS-A
VIKAAS-A

💻
Abdul Kareem
Abdul Kareem

💻
NojoudAlshehri
NojoudAlshehri

💻
Stefan Stidl
Stefan Stidl

💻
Quentin Aymard
Quentin Aymard

💻
Grant Le Roux
Grant Le Roux

💻
Bogdan
Bogdan

💻
mmanjos
mmanjos

💻
Abdelaziz Faki
Abdelaziz Faki

💻
bilias
bilias

💻
coach1988
coach1988

💻
MrM
MrM

💻
koiakoia
koiakoia

💻
Mustafa Online
Mustafa Online

💻
franceslui
franceslui

💻
Q4kK
Q4kK

💻
squintfox
squintfox

💻
Jeff Clay
Jeff Clay

💻
Phil J R
Phil J R

💻
i_virus
i_virus

💻
Paul Grime
Paul Grime

💻
Lee Porte
Lee Porte

💻
BRYAN
BRYAN

💻 ⚠️
U-H-T
U-H-T

💻
Matt Tyree
Matt Tyree

📖
- - - - +| [
snipe](http://www.snipe.net)
[💻](https://github.com/snipe/snipe-it/commits?author=snipe "Code") [🚇](#infra-snipe "Infrastructure (Hosting, Build-Tools, etc)") [📖](https://github.com/snipe/snipe-it/commits?author=snipe "Documentation") [⚠️](https://github.com/snipe/snipe-it/commits?author=snipe "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asnipe "Bug reports") [🎨](#design-snipe "Design") [👀](#review-snipe "Reviewed Pull Requests") | [
Brady Wetherington](http://www.uberbrady.com)
[💻](https://github.com/snipe/snipe-it/commits?author=uberbrady "Code") [📖](https://github.com/snipe/snipe-it/commits?author=uberbrady "Documentation") [🚇](#infra-uberbrady "Infrastructure (Hosting, Build-Tools, etc)") [👀](#review-uberbrady "Reviewed Pull Requests") | [
Daniel Meltzer](https://github.com/dmeltzer)
[💻](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Tests") [📖](https://github.com/snipe/snipe-it/commits?author=dmeltzer "Documentation") | [
Michael T](http://www.tuckertechonline.com)
[💻](https://github.com/snipe/snipe-it/commits?author=mtucker6784 "Code") | [
madd15](https://github.com/madd15)
[📖](https://github.com/snipe/snipe-it/commits?author=madd15 "Documentation") [💬](#question-madd15 "Answering Questions") | [
Vincent Sposato](https://github.com/vsposato)
[💻](https://github.com/snipe/snipe-it/commits?author=vsposato "Code") | [
Andrea Bergamasco](https://github.com/vjandrea)
[💻](https://github.com/snipe/snipe-it/commits?author=vjandrea "Code") | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| [
Karol](https://github.com/kpawelski)
[🌍](#translation-kpawelski "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=kpawelski "Code") | [
morph027](http://blog.morph027.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=morph027 "Code") | [
fvleminckx](https://github.com/fvleminckx)
[🚇](#infra-fvleminckx "Infrastructure (Hosting, Build-Tools, etc)") | [
itsupportcmsukorg](https://github.com/itsupportcmsukorg)
[💻](https://github.com/snipe/snipe-it/commits?author=itsupportcmsukorg "Code") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aitsupportcmsukorg "Bug reports") | [
Frank](https://override.io)
[💻](https://github.com/snipe/snipe-it/commits?author=base-zero "Code") | [
Deleted user](https://github.com/ghost)
[🌍](#translation-ghost "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=ghost "Code") | [
tiagom62](https://github.com/tiagom62)
[💻](https://github.com/snipe/snipe-it/commits?author=tiagom62 "Code") [🚇](#infra-tiagom62 "Infrastructure (Hosting, Build-Tools, etc)") | +| [
Ryan Stafford](https://github.com/rystaf)
[💻](https://github.com/snipe/snipe-it/commits?author=rystaf "Code") | [
Eammon Hanlon](https://github.com/ehanlon)
[💻](https://github.com/snipe/snipe-it/commits?author=ehanlon "Code") | [
zjean](https://github.com/zjean)
[💻](https://github.com/snipe/snipe-it/commits?author=zjean "Code") | [
Matthias Frei](http://www.frei.media)
[💻](https://github.com/snipe/snipe-it/commits?author=FREImedia "Code") | [
opsydev](https://github.com/opsydev)
[💻](https://github.com/snipe/snipe-it/commits?author=opsydev "Code") | [
Daniel Dreier](http://www.ddreier.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ddreier "Code") | [
Nikolai Prokoschenko](http://rassie.org)
[💻](https://github.com/snipe/snipe-it/commits?author=rassie "Code") | +| [
Drew](https://github.com/YetAnotherCodeMonkey)
[💻](https://github.com/snipe/snipe-it/commits?author=YetAnotherCodeMonkey "Code") | [
Walter](https://github.com/merid14)
[💻](https://github.com/snipe/snipe-it/commits?author=merid14 "Code") | [
Petr Baloun](https://github.com/balous)
[💻](https://github.com/snipe/snipe-it/commits?author=balous "Code") | [
reidblomquist](https://github.com/reidblomquist)
[📖](https://github.com/snipe/snipe-it/commits?author=reidblomquist "Documentation") | [
Mathieu Kooiman](https://github.com/mathieuk)
[💻](https://github.com/snipe/snipe-it/commits?author=mathieuk "Code") | [
csayre](https://github.com/csayre)
[📖](https://github.com/snipe/snipe-it/commits?author=csayre "Documentation") | [
Adam Dunson](https://github.com/adamdunson)
[💻](https://github.com/snipe/snipe-it/commits?author=adamdunson "Code") | +| [
Hereward](https://github.com/thehereward)
[💻](https://github.com/snipe/snipe-it/commits?author=thehereward "Code") | [
swoopdk](https://github.com/swoopdk)
[💻](https://github.com/snipe/snipe-it/commits?author=swoopdk "Code") | [
Abdullah Alansari](https://linkedin.com/in/ahimta)
[💻](https://github.com/snipe/snipe-it/commits?author=Ahimta "Code") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[💻](https://github.com/snipe/snipe-it/commits?author=MicaelRodrigues "Code") | [
Patrick Gallagher](http://macadmincorner.com)
[📖](https://github.com/snipe/snipe-it/commits?author=patgmac "Documentation") | [
Miliamber](https://github.com/Miliamber)
[💻](https://github.com/snipe/snipe-it/commits?author=Miliamber "Code") | [
hawk554](https://github.com/hawk554)
[💻](https://github.com/snipe/snipe-it/commits?author=hawk554 "Code") | +| [
Justin Kerr](http://jbirdkerr.net)
[💻](https://github.com/snipe/snipe-it/commits?author=jbirdkerr "Code") | [
Ira W. Snyder](http://www.irasnyder.com/devel/)
[📖](https://github.com/snipe/snipe-it/commits?author=irasnyd "Documentation") | [
Aladin Alaily](https://github.com/aalaily)
[💻](https://github.com/snipe/snipe-it/commits?author=aalaily "Code") | [
Chase Hansen](https://github.com/kobie-chasehansen)
[💻](https://github.com/snipe/snipe-it/commits?author=kobie-chasehansen "Code") [💬](#question-kobie-chasehansen "Answering Questions") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3Akobie-chasehansen "Bug reports") | [
IDM Helpdesk](https://github.com/IDM-Helpdesk)
[💻](https://github.com/snipe/snipe-it/commits?author=IDM-Helpdesk "Code") | [
Kai](http://balticer.de)
[💻](https://github.com/snipe/snipe-it/commits?author=balticer "Code") | [
Michael Daniels](http://www.michaeldaniels.me)
[💻](https://github.com/snipe/snipe-it/commits?author=mdaniels5757 "Code") | +| [
Tom Castleman](http://tomcastleman.me)
[💻](https://github.com/snipe/snipe-it/commits?author=tomcastleman "Code") | [
Daniel Nemanic](https://github.com/DanielNemanic)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielNemanic "Code") | [
SouthWolf](https://github.com/southwolf)
[💻](https://github.com/snipe/snipe-it/commits?author=southwolf "Code") | [
Ivar Nesje](https://github.com/ivarne)
[💻](https://github.com/snipe/snipe-it/commits?author=ivarne "Code") | [
Jérémy Benoist](http://www.j0k3r.net)
[📖](https://github.com/snipe/snipe-it/commits?author=j0k3r "Documentation") | [
Chris Leathley](https://github.com/cleathley)
[🚇](#infra-cleathley "Infrastructure (Hosting, Build-Tools, etc)") | [
splaer](https://github.com/splaer)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Asplaer "Bug reports") [💻](https://github.com/snipe/snipe-it/commits?author=splaer "Code") | +| [
Joe Ferguson](http://www.joeferguson.me)
[💻](https://github.com/snipe/snipe-it/commits?author=svpernova09 "Code") | [
diwanicki](https://github.com/diwanicki)
[💻](https://github.com/snipe/snipe-it/commits?author=diwanicki "Code") [📖](https://github.com/snipe/snipe-it/commits?author=diwanicki "Documentation") | [
Lee Thoong Ching](https://github.com/pakkua80)
[📖](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Documentation") [💻](https://github.com/snipe/snipe-it/commits?author=pakkua80 "Code") | [
Marek Šuppa](http://shu.io)
[💻](https://github.com/snipe/snipe-it/commits?author=mrshu "Code") | [
Juan J. Martinez](https://github.com/mizar1616)
[🌍](#translation-mizar1616 "Translation") | [
R Ryan Dial](https://github.com/rrdial)
[🌍](#translation-rrdial "Translation") | [
Andrej Manduch](https://github.com/burlito)
[📖](https://github.com/snipe/snipe-it/commits?author=burlito "Documentation") | +| [
Jay Richards](http://www.cordeos.com)
[💻](https://github.com/snipe/snipe-it/commits?author=technogenus "Code") | [
Alexander Innes](https://necurity.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=leostat "Code") | [
Danny Garcia](https://buzzedword.codes)
[💻](https://github.com/snipe/snipe-it/commits?author=buzzedword "Code") | [
archpoint](https://github.com/archpoint)
[💻](https://github.com/snipe/snipe-it/commits?author=archpoint "Code") | [
Jake McGraw](http://www.jakemcgraw.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jakemcgraw "Code") | [
FleischKarussel](https://github.com/FleischKarussel)
[📖](https://github.com/snipe/snipe-it/commits?author=FleischKarussel "Documentation") | [
Dylan Yi](https://github.com/feeva)
[💻](https://github.com/snipe/snipe-it/commits?author=feeva "Code") | +| [
Gil Rutkowski](http://FlashingCursor.com)
[💻](https://github.com/snipe/snipe-it/commits?author=flashingcursor "Code") | [
Desmond Morris](http://www.desmondmorris.com)
[💻](https://github.com/snipe/snipe-it/commits?author=desmondmorris "Code") | [
Nick Peelman](http://peelman.us)
[💻](https://github.com/snipe/snipe-it/commits?author=peelman "Code") | [
Abraham Vegh](https://abrahamvegh.com)
[💻](https://github.com/snipe/snipe-it/commits?author=abrahamvegh "Code") | [
Mohamed Rashid](https://github.com/rashivkp)
[📖](https://github.com/snipe/snipe-it/commits?author=rashivkp "Documentation") | [
Kasey](http://hinchk.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=HinchK "Code") | [
Brett](https://github.com/BrettFagerlund)
[⚠️](https://github.com/snipe/snipe-it/commits?author=BrettFagerlund "Tests") | +| [
Jason Spriggs](http://jasonspriggs.com)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonspriggs "Code") | [
Nate Felton](http://n8felton.wordpress.com)
[💻](https://github.com/snipe/snipe-it/commits?author=n8felton "Code") | [
Manasses Ferreira](http://homepages.dcc.ufmg.br/~manassesferreira)
[💻](https://github.com/snipe/snipe-it/commits?author=manassesferreira "Code") | [
Steve](https://github.com/steveelwood)
[⚠️](https://github.com/snipe/snipe-it/commits?author=steveelwood "Tests") | [
matc](http://twitter.com/matc)
[⚠️](https://github.com/snipe/snipe-it/commits?author=matc "Tests") | [
Cole R. Davis](http://www.davisracingteam.com)
[⚠️](https://github.com/snipe/snipe-it/commits?author=VanillaNinjaD "Tests") | [
gibsonjoshua55](https://github.com/gibsonjoshua55)
[💻](https://github.com/snipe/snipe-it/commits?author=gibsonjoshua55 "Code") | +| [
Robin Temme](https://github.com/zwerch)
[💻](https://github.com/snipe/snipe-it/commits?author=zwerch "Code") | [
Iman](https://github.com/imanghafoori1)
[💻](https://github.com/snipe/snipe-it/commits?author=imanghafoori1 "Code") | [
Richard Hofman](https://github.com/richardhofman6)
[💻](https://github.com/snipe/snipe-it/commits?author=richardhofman6 "Code") | [
gizzmojr](https://github.com/gizzmojr)
[💻](https://github.com/snipe/snipe-it/commits?author=gizzmojr "Code") | [
Jenny Li](https://github.com/imjennyli)
[📖](https://github.com/snipe/snipe-it/commits?author=imjennyli "Documentation") | [
Geoff Young](https://github.com/GeoffYoung)
[💻](https://github.com/snipe/snipe-it/commits?author=GeoffYoung "Code") | [
Elliot Blackburn](http://www.elliotblackburn.com)
[📖](https://github.com/snipe/snipe-it/commits?author=BlueHatbRit "Documentation") | +| [
Tõnis Ormisson](http://andmemasin.eu)
[💻](https://github.com/snipe/snipe-it/commits?author=TonisOrmisson "Code") | [
Nicolai Essig](http://www.nicolai-essig.de)
[💻](https://github.com/snipe/snipe-it/commits?author=thakilla "Code") | [
Danielle](https://github.com/techincolor)
[📖](https://github.com/snipe/snipe-it/commits?author=techincolor "Documentation") | [
Lawrence](https://github.com/TheVakman)
[⚠️](https://github.com/snipe/snipe-it/commits?author=TheVakman "Tests") [🐛](https://github.com/snipe/snipe-it/issues?q=author%3ATheVakman "Bug reports") | [
uknzaeinozpas](https://github.com/uknzaeinozpas)
[⚠️](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Tests") [💻](https://github.com/snipe/snipe-it/commits?author=uknzaeinozpas "Code") | [
Ryan](https://github.com/Gelob)
[📖](https://github.com/snipe/snipe-it/commits?author=Gelob "Documentation") | [
vcordes79](https://github.com/vcordes79)
[💻](https://github.com/snipe/snipe-it/commits?author=vcordes79 "Code") | +| [
fordster78](https://github.com/fordster78)
[💻](https://github.com/snipe/snipe-it/commits?author=fordster78 "Code") | [
CronKz](https://github.com/CronKz)
[💻](https://github.com/snipe/snipe-it/commits?author=CronKz "Code") [🌍](#translation-CronKz "Translation") | [
Tim Bishop](https://github.com/tdb)
[💻](https://github.com/snipe/snipe-it/commits?author=tdb "Code") | [
Sean McIlvenna](https://www.seanmcilvenna.com)
[💻](https://github.com/snipe/snipe-it/commits?author=seanmcilvenna "Code") | [
cepacs](https://github.com/cepacs)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Acepacs "Bug reports") [📖](https://github.com/snipe/snipe-it/commits?author=cepacs "Documentation") | [
lea-mink](https://github.com/lea-mink)
[💻](https://github.com/snipe/snipe-it/commits?author=lea-mink "Code") | [
Hannah Tinkler](https://github.com/hannahtinkler)
[💻](https://github.com/snipe/snipe-it/commits?author=hannahtinkler "Code") | +| [
Doeke Zanstra](https://github.com/doekman)
[💻](https://github.com/snipe/snipe-it/commits?author=doekman "Code") | [
Djamon Staal](https://www.sdhd.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=SjamonDaal "Code") | [
Earl Ramirez](https://github.com/EarlRamirez)
[💻](https://github.com/snipe/snipe-it/commits?author=EarlRamirez "Code") | [
Richard Ray Thomas](https://github.com/RichardRay)
[💻](https://github.com/snipe/snipe-it/commits?author=RichardRay "Code") | [
Ryan Kuba](https://www.taisun.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=thelamer "Code") | [
Brian Monroe](https://github.com/ParadoxGuitarist)
[💻](https://github.com/snipe/snipe-it/commits?author=ParadoxGuitarist "Code") | [
plexorama](https://github.com/plexorama)
[💻](https://github.com/snipe/snipe-it/commits?author=plexorama "Code") | +| [
Till Deeke](https://tilldeeke.de)
[💻](https://github.com/snipe/snipe-it/commits?author=tilldeeke "Code") | [
5quirrel](https://github.com/5quirrel)
[💻](https://github.com/snipe/snipe-it/commits?author=5quirrel "Code") | [
Jason](https://github.com/jasonlshelton)
[💻](https://github.com/snipe/snipe-it/commits?author=jasonlshelton "Code") | [
Antti](https://github.com/chemfy)
[💻](https://github.com/snipe/snipe-it/commits?author=chemfy "Code") | [
DeusMaximus](https://github.com/DeusMaximus)
[💻](https://github.com/snipe/snipe-it/commits?author=DeusMaximus "Code") | [
a-royal](https://github.com/A-ROYAL)
[🌍](#translation-A-ROYAL "Translation") | [
Alberto Aldrigo](https://github.com/albertoaldrigo)
[🌍](#translation-albertoaldrigo "Translation") | +| [
Alex Stanev](http://alex.stanev.org/blog)
[🌍](#translation-RealEnder "Translation") | [
Andreas Rehm](http://devel.itsolution2.de)
[🌍](#translation-sirrus "Translation") | [
Andreas Erhard](https://github.com/xelan)
[🌍](#translation-xelan "Translation") | [
Andrés Vanegas Jiménez](https://github.com/angeldeejay)
[🌍](#translation-angeldeejay "Translation") | [
Antonio Schiavon](https://github.com/aschiavon91)
[🌍](#translation-aschiavon91 "Translation") | [
benunter](https://github.com/benunter)
[🌍](#translation-benunter "Translation") | [
Borys Żmuda](http://catweb24.pl)
[🌍](#translation-rudashi "Translation") | +| [
chibacityblues](https://github.com/chibacityblues)
[🌍](#translation-chibacityblues "Translation") | [
Chien Wei Lin](https://github.com/cwlin0416)
[🌍](#translation-cwlin0416 "Translation") | [
Christian Schuster](https://github.com/Againstreality)
[🌍](#translation-Againstreality "Translation") | [
Christian Stefanus](http://chriss.webhostid.com)
[🌍](#translation-kopi-item "Translation") | [
wxcafé](http://wxcafe.net)
[🌍](#translation-wxcafe "Translation") | [
dpyroc](https://github.com/dpyroc)
[🌍](#translation-dpyroc "Translation") | [
Daniel Friedlmaier](http://www.friedlmaier.net)
[🌍](#translation-da-friedl "Translation") | +| [
Daniel Heene](https://github.com/danielheene)
[🌍](#translation-danielheene "Translation") | [
danielcb](https://github.com/danielcb)
[🌍](#translation-danielcb "Translation") | [
Dominik Senti](https://github.com/dominiksenti)
[🌍](#translation-dominiksenti "Translation") | [
Eric Gautheron](http://www.konectik.com)
[🌍](#translation-EpixFr "Translation") | [
Erlend Pilø](https://erlpil.com)
[🌍](#translation-Erlpil "Translation") | [
Fabio Rapposelli](http://fabio.technology)
[🌍](#translation-frapposelli "Translation") | [
Felipe Barros](https://github.com/fgbs)
[🌍](#translation-fgbs "Translation") | +| [
Fernando Possebon](https://github.com/possebon)
[🌍](#translation-possebon "Translation") | [
gdraque](https://github.com/gdraque)
[🌍](#translation-gdraque "Translation") | [
Georg Wallisch](https://github.com/georgwallisch)
[🌍](#translation-georgwallisch "Translation") | [
Gerardo Robles](https://github.com/jgroblesr85)
[🌍](#translation-jgroblesr85 "Translation") | [
Gluek](https://t.me/Gluek)
[🌍](#translation-mrgluek "Translation") | [
AdnanAbuShahad](https://github.com/AdnanAbuShahad)
[🌍](#translation-AdnanAbuShahad "Translation") | [
Hafidzi My](https://hafidzi.my)
[🌍](#translation-hafidzi "Translation") | +| [
Harim Park](https://github.com/fofwisdom)
[🌍](#translation-fofwisdom "Translation") | [
Henrik Kentsson](http://www.kentsson.se)
[🌍](#translation-Kentsson "Translation") | [
Husnul Yaqien](https://github.com/husnulyaqien)
[🌍](#translation-husnulyaqien "Translation") | [
Ibrahim](http://abaalkhail.org)
[🌍](#translation-abaalkh "Translation") | [
igolman](https://github.com/igolman)
[🌍](#translation-igolman "Translation") | [
itangiang](https://github.com/itangiang)
[🌍](#translation-itangiang "Translation") | [
jarby1211](https://github.com/jarby1211)
[🌍](#translation-jarby1211 "Translation") | +| [
Jhonn Willker](http://jwillker.com)
[🌍](#translation-JohnWillker "Translation") | [
Jose](https://github.com/joxelito94)
[🌍](#translation-joxelito94 "Translation") | [
laopangzi](https://github.com/laopangzi)
[🌍](#translation-laopangzi "Translation") | [
Lars Strojny](http://usrportage.de)
[🌍](#translation-lstrojny "Translation") | [
MarcosBL](http://twitter.com/marcosbl)
[🌍](#translation-MarcosBL "Translation") | [
marie joy cajes](https://github.com/mariejoyacajes)
[🌍](#translation-mariejoyacajes "Translation") | [
Mark S. Johansen](http://www.markjohansen.dk)
[🌍](#translation-msjohansen "Translation") | +| [
Martin Stub](http://martinstub.dk)
[🌍](#translation-stubben "Translation") | [
Meyer Flavio](https://github.com/meyerf99)
[🌍](#translation-meyerf99 "Translation") | [
Micael Rodrigues](https://github.com/MicaelRodrigues)
[🌍](#translation-MicaelRodrigues "Translation") | [
Mikael Rasmussen](http://rubixy.com/)
[🌍](#translation-mikaelssen "Translation") | [
IxFail](https://github.com/IxFail)
[🌍](#translation-IxFail "Translation") | [
Mohammed Fota](http://www.mohammedfota.com)
[🌍](#translation-MohammedFota "Translation") | [
Moayad Alserihi](https://github.com/omego)
[🌍](#translation-omego "Translation") | +| [
saymd](https://github.com/saymd)
[🌍](#translation-saymd "Translation") | [
Patrik Larsson](https://nordsken.se)
[🌍](#translation-pooot "Translation") | [
drcryo](https://github.com/drcryo)
[🌍](#translation-drcryo "Translation") | [
pawel1615](https://github.com/pawel1615)
[🌍](#translation-pawel1615 "Translation") | [
bodrovics](https://github.com/bodrovics)
[🌍](#translation-bodrovics "Translation") | [
priatna](https://github.com/priatna)
[🌍](#translation-priatna "Translation") | [
Fan Jiang](https://amayume.net)
[🌍](#translation-ProfFan "Translation") | +| [
ragnarcx](https://github.com/ragnarcx)
[🌍](#translation-ragnarcx "Translation") | [
Rein van Haaren](http://www.reinvanhaaren.nl/)
[🌍](#translation-reinvanhaaren "Translation") | [
Teguh Dwicaksana](http://dheche.songolimo.net)
[🌍](#translation-dheche "Translation") | [
fraccie](https://github.com/FRaccie)
[🌍](#translation-FRaccie "Translation") | [
vinzruzell](https://github.com/vinzruzell)
[🌍](#translation-vinzruzell "Translation") | [
Kevin Austin](http://kevinaustin.com)
[🌍](#translation-vipsystem "Translation") | [
Wira Sandy](http://azuraweb.xyz)
[🌍](#translation-wira-sandy "Translation") | +| [
Илья](https://github.com/GrayHoax)
[🌍](#translation-GrayHoax "Translation") | [
GodUseVPN](https://github.com/godusevpn)
[🌍](#translation-godusevpn "Translation") | [
周周](https://github.com/EngrZhou)
[🌍](#translation-EngrZhou "Translation") | [
Sam](https://github.com/takuy)
[💻](https://github.com/snipe/snipe-it/commits?author=takuy "Code") | [
Azerothian](https://www.illisian.com.au)
[💻](https://github.com/snipe/snipe-it/commits?author=Azerothian "Code") | [
Wes Hulette](http://macfoo.wordpress.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=jwhulette "Code") | [
patrict](https://github.com/patrict)
[💻](https://github.com/snipe/snipe-it/commits?author=patrict "Code") | +| [
Dmitriy Minaev](https://github.com/VELIKII-DIVAN)
[💻](https://github.com/snipe/snipe-it/commits?author=VELIKII-DIVAN "Code") | [
liquidhorse](https://github.com/liquidhorse)
[💻](https://github.com/snipe/snipe-it/commits?author=liquidhorse "Code") | [
Jordi Boggiano](https://seld.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=Seldaek "Code") | [
Ivan Nieto](https://github.com/inietov)
[💻](https://github.com/snipe/snipe-it/commits?author=inietov "Code") | [
Ben RUBSON](https://github.com/benrubson)
[💻](https://github.com/snipe/snipe-it/commits?author=benrubson "Code") | [
NMathar](https://github.com/NMathar)
[💻](https://github.com/snipe/snipe-it/commits?author=NMathar "Code") | [
Steffen](https://github.com/smb)
[💻](https://github.com/snipe/snipe-it/commits?author=smb "Code") | +| [
Sxderp](https://github.com/Sxderp)
[💻](https://github.com/snipe/snipe-it/commits?author=Sxderp "Code") | [
fanta8897](https://github.com/fanta8897)
[💻](https://github.com/snipe/snipe-it/commits?author=fanta8897 "Code") | [
Andrey Bolonin](https://andreybolonin.com/phpconsulting/)
[💻](https://github.com/snipe/snipe-it/commits?author=andreybolonin "Code") | [
shinayoshi](http://www.shinayoshi.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=shinayoshi "Code") | [
Hubert](https://github.com/reuser)
[💻](https://github.com/snipe/snipe-it/commits?author=reuser "Code") | [
KeenRivals](https://brashear.me)
[💻](https://github.com/snipe/snipe-it/commits?author=KeenRivals "Code") | [
omyno](https://github.com/omyno)
[💻](https://github.com/snipe/snipe-it/commits?author=omyno "Code") | +| [
Evgeny](https://github.com/jackka)
[💻](https://github.com/snipe/snipe-it/commits?author=jackka "Code") | [
Colin Campbell](https://digitalist.se)
[💻](https://github.com/snipe/snipe-it/commits?author=colin-campbell "Code") | [
Ľubomír Kučera](https://github.com/lubo)
[💻](https://github.com/snipe/snipe-it/commits?author=lubo "Code") | [
Martin Meredith](https://www.sourceguru.net)
[💻](https://github.com/snipe/snipe-it/commits?author=Mezzle "Code") | [
Tim Farmer](https://github.com/timothyfarmer)
[💻](https://github.com/snipe/snipe-it/commits?author=timothyfarmer "Code") | [
Marián Skrip](https://github.com/mskrip)
[💻](https://github.com/snipe/snipe-it/commits?author=mskrip "Code") | [
Godfrey Martinez](https://github.com/Godmartinz)
[💻](https://github.com/snipe/snipe-it/commits?author=Godmartinz "Code") | +| [
bigtreeEdo](https://github.com/bigtreeEdo)
[💻](https://github.com/snipe/snipe-it/commits?author=bigtreeEdo "Code") | [
Colin McNeil](https://colinmcneil.me/)
[💻](https://github.com/snipe/snipe-it/commits?author=ColinMcNeil "Code") | [
JoKneeMo](https://github.com/JoKneeMo)
[💻](https://github.com/snipe/snipe-it/commits?author=JoKneeMo "Code") | [
Joshi](http://www.redbridge.se)
[💻](https://github.com/snipe/snipe-it/commits?author=joshi-redbridge "Code") | [
Anthony Burns](https://github.com/anthonypburns)
[💻](https://github.com/snipe/snipe-it/commits?author=anthonypburns "Code") | [
johnson-yi](https://github.com/johnson-yi)
[💻](https://github.com/snipe/snipe-it/commits?author=johnson-yi "Code") | [
Sanjay Govind](https://tangentmc.net)
[💻](https://github.com/snipe/snipe-it/commits?author=sanjay900 "Code") | +| [
Peter Upfold](https://peter.upfold.org.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterUpfold "Code") | [
Jared Biel](https://github.com/jbiel)
[💻](https://github.com/snipe/snipe-it/commits?author=jbiel "Code") | [
Dampfklon](https://github.com/dampfklon)
[💻](https://github.com/snipe/snipe-it/commits?author=dampfklon "Code") | [
Charles Hamilton](https://communityclosing.com)
[💻](https://github.com/snipe/snipe-it/commits?author=chamilton-ccn "Code") | [
Giuseppe Iannello](https://github.com/giannello)
[💻](https://github.com/snipe/snipe-it/commits?author=giannello "Code") | [
Peter Dave Hello](https://www.peterdavehello.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=PeterDaveHello "Code") | [
sigmoidal](https://github.com/sigmoidal)
[💻](https://github.com/snipe/snipe-it/commits?author=sigmoidal "Code") | +| [
Vincent Lainé](https://github.com/phenixdotnet)
[💻](https://github.com/snipe/snipe-it/commits?author=phenixdotnet "Code") | [
Lucas Pleß](http://www.lucas-pless.com)
[💻](https://github.com/snipe/snipe-it/commits?author=derlucas "Code") | [
Ian Littman](http://twitter.com/iansltx)
[💻](https://github.com/snipe/snipe-it/commits?author=iansltx "Code") | [
João Paulo](https://github.com/PauloLuna)
[💻](https://github.com/snipe/snipe-it/commits?author=PauloLuna "Code") | [
ThoBur](https://github.com/ThoBur)
[💻](https://github.com/snipe/snipe-it/commits?author=ThoBur "Code") | [
Alexander Chibrikin](http://phpprofi.ru/)
[💻](https://github.com/snipe/snipe-it/commits?author=alek13 "Code") | [
Anthony Winstanley](https://github.com/winstan)
[💻](https://github.com/snipe/snipe-it/commits?author=winstan "Code") | +| [
Folke](https://github.com/fashberg)
[💻](https://github.com/snipe/snipe-it/commits?author=fashberg "Code") | [
Bennett Blodinger](https://github.com/benwa)
[💻](https://github.com/snipe/snipe-it/commits?author=benwa "Code") | [
NMC](https://nmc.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=ncareau "Code") | [
andres-baller](https://github.com/andres-baller)
[💻](https://github.com/snipe/snipe-it/commits?author=andres-baller "Code") | [
sean-borg](https://github.com/sean-borg)
[💻](https://github.com/snipe/snipe-it/commits?author=sean-borg "Code") | [
EDVLeer](https://github.com/EDVLeer)
[💻](https://github.com/snipe/snipe-it/commits?author=EDVLeer "Code") | [
Kurokat](https://github.com/Kurokat)
[💻](https://github.com/snipe/snipe-it/commits?author=Kurokat "Code") | +| [
Kevin Köllmann](https://www.kevinkoellmann.de)
[💻](https://github.com/snipe/snipe-it/commits?author=koelle25 "Code") | [
sw-mreyes](https://github.com/sw-mreyes)
[💻](https://github.com/snipe/snipe-it/commits?author=sw-mreyes "Code") | [
Joel Pittet](https://pittet.ca)
[💻](https://github.com/snipe/snipe-it/commits?author=joelpittet "Code") | [
Eli Young](https://elyscape.com)
[💻](https://github.com/snipe/snipe-it/commits?author=elyscape "Code") | [
Raell Dottin](https://github.com/raelldottin)
[💻](https://github.com/snipe/snipe-it/commits?author=raelldottin "Code") | [
Tom Misilo](https://github.com/misilot)
[💻](https://github.com/snipe/snipe-it/commits?author=misilot "Code") | [
David Davenne](http://david.davenne.be)
[💻](https://github.com/snipe/snipe-it/commits?author=JuustoMestari "Code") | +| [
Mark Stenglein](https://markstenglein.com)
[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [
ajsy](https://github.com/ajsy)
[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [
Jan Kiesewetter](https://github.com/t3easy)
[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [
Tetrachloromethane250](https://github.com/Tetrachloromethane250)
[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [
Lars Kajes](https://www.kajes.se/)
[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [
Joly0](https://github.com/Joly0)
[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [
theburger](https://github.com/limeless)
[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") | +| [
David Valin Alonso](https://github.com/deivishome)
[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [
andreaci](https://github.com/andreaci)
[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [
Jelle Sebreghts](http://www.jellesebreghts.be)
[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [
Michael Pietsch](https://github.com/Skywalker-11)
| [
Masudul Haque Shihab](https://github.com/sh1hab)
[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [
Supapong Areeprasertkul](http://www.freedomdive.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [
Peter Sarossy](https://github.com/psarossy)
[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") | +| [
Renee Margaret McConahy](https://github.com/nepella)
[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [
JohnnyPicnic](https://github.com/JohnnyPicnic)
[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [
markbrule](https://github.com/markbrule)
[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [
Mike Campbell](https://github.com/mikecmpbll)
[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [
tbrconnect](https://github.com/tbrconnect)
[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [
kcoyo](https://github.com/kcoyo)
[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [
Travis Miller](https://travismiller.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") | +| [
Evan Taylor](https://github.com/Delta5)
[💻](https://github.com/snipe/snipe-it/commits?author=Delta5 "Code") | [
Petri Asikainen](https://github.com/PetriAsi)
[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [
derdeagle](https://github.com/derdeagle)
[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [
Mike Frysinger](https://wh0rd.org/)
[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [
ALPHA](https://github.com/AL4AL)
[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [
FliegenKLATSCH](https://www.ifern.de)
[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [
Jeremy Price](https://github.com/jerm)
[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") | +| [
Toreg87](https://github.com/Toreg87)
[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") | [
Matthew Nickson](https://github.com/Computroniks)
[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [
Jethro Nederhof](https://jethron.id.au)
[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [
Oskar Stenberg](https://github.com/01ste02)
[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [
Robert-Azelis](https://github.com/Robert-Azelis)
[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [
Alexander William Smith](https://github.com/alwism)
[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") | [
LEITWERK AG](https://www.leitwerk.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=leitwerk-ag "Code") | +| [
Adam](http://www.aboutcher.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=adamboutcher "Code") | [
Ian](https://snksrv.com)
[💻](https://github.com/snipe/snipe-it/commits?author=sneak-it "Code") | [
Shao Yu-Lung (Allen)](http://blog.bestlong.idv.tw/)
[💻](https://github.com/snipe/snipe-it/commits?author=bestlong "Code") | [
Haxatron](https://github.com/Haxatron)
[💻](https://github.com/snipe/snipe-it/commits?author=Haxatron "Code") | [
PlaneNuts](https://github.com/PlaneNuts)
[💻](https://github.com/snipe/snipe-it/commits?author=PlaneNuts "Code") | [
Bradley Coudriet](http://bjcpgd.cias.rit.edu)
[💻](https://github.com/snipe/snipe-it/commits?author=exula "Code") | [
Dalton Durst](https://daltondur.st)
[💻](https://github.com/snipe/snipe-it/commits?author=UniversalSuperBox "Code") | +| [
Alex Janes](https://adagiohealth.org)
[💻](https://github.com/snipe/snipe-it/commits?author=adagioajanes "Code") | [
Nuraeil](https://github.com/nuraeil)
[💻](https://github.com/snipe/snipe-it/commits?author=nuraeil "Code") | [
TenOfTens](https://github.com/TenOfTens)
[💻](https://github.com/snipe/snipe-it/commits?author=TenOfTens "Code") | [
waffle](https://ditisjens.be/)
[💻](https://github.com/snipe/snipe-it/commits?author=insert-waffle "Code") | [
Yevhenii Huzii](https://github.com/qveensi)
[💻](https://github.com/snipe/snipe-it/commits?author=qveensi "Code") | [
Achmad Fienan Rahardianto](https://github.com/veenone)
[💻](https://github.com/snipe/snipe-it/commits?author=veenone "Code") | [
Christian Weirich](https://github.com/chrisweirich)
[💻](https://github.com/snipe/snipe-it/commits?author=chrisweirich "Code") | +| [
denzfarid](https://github.com/denzfarid)
| [
ntbutler-nbcs](https://github.com/ntbutler-nbcs)
[💻](https://github.com/snipe/snipe-it/commits?author=ntbutler-nbcs "Code") | [
Naveen](https://naveensrinivasan.dev)
[💻](https://github.com/snipe/snipe-it/commits?author=naveensrinivasan "Code") | [
Mike Roquemore](https://github.com/mikeroq)
[💻](https://github.com/snipe/snipe-it/commits?author=mikeroq "Code") | [
Daniel Reeder](https://github.com/reederda)
[🌍](#translation-reederda "Translation") [🌍](#translation-reederda "Translation") [💻](https://github.com/snipe/snipe-it/commits?author=reederda "Code") | [
vickyjaura183](https://github.com/vickyjaura183)
[💻](https://github.com/snipe/snipe-it/commits?author=vickyjaura183 "Code") | [
Peace](https://github.com/julian-piehl)
[💻](https://github.com/snipe/snipe-it/commits?author=julian-piehl "Code") | +| [
Kyle Gordon](https://github.com/kylegordon)
[💻](https://github.com/snipe/snipe-it/commits?author=kylegordon "Code") | [
Katharina Drexel](http://www.bfh.ch)
[💻](https://github.com/snipe/snipe-it/commits?author=sunflowerbofh "Code") | [
David Sferruzza](https://david.sferruzza.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=dsferruzza "Code") | [
Rick Nelson](https://github.com/rnelsonee)
[💻](https://github.com/snipe/snipe-it/commits?author=rnelsonee "Code") | [
BasO12](https://github.com/BasO12)
[💻](https://github.com/snipe/snipe-it/commits?author=BasO12 "Code") | [
Vautia](https://github.com/Vautia)
[💻](https://github.com/snipe/snipe-it/commits?author=Vautia "Code") | [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | +| [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") | [
akemidx](https://github.com/akemidx)
[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | +| [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") | [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | +| [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") | [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | +| [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") | [
Bogdan](http://@singrity)
[💻](https://github.com/snipe/snipe-it/commits?author=Singrity "Code") | [
mmanjos](https://github.com/mmanjos)
[💻](https://github.com/snipe/snipe-it/commits?author=mmanjos "Code") | [
Abdelaziz Faki](https://azooz2014.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Azooz2014 "Code") | [
bilias](https://github.com/bilias)
[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | +| [
coach1988](https://github.com/coach1988)
[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [
MrM](https://github.com/mauro-miatello)
[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [
koiakoia](https://github.com/koiakoia)
[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [
Mustafa Online](https://github.com/mustafa-online)
[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [
franceslui](https://github.com/franceslui)
[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [
Q4kK](https://github.com/Q4kK)
[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | [
squintfox](https://github.com/squintfox)
[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | +| [
Jeff Clay](https://github.com/jeffclay)
[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [
Phil J R](https://github.com/PP-JN-RL)
[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [
i_virus](https://www.corelight.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [
Paul Grime](https://github.com/gitgrimbo)
[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [
Lee Porte](https://leeporte.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [
BRYAN ](https://github.com/bryanlopezinc)
[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | [
U-H-T](https://github.com/U-H-T)
[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | +| [
Matt Tyree](https://github.com/Tyree)
[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [
Florent Bervas](http://spoontux.net)
[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [
Daniel Albertsen](https://ditscheri.com)
[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [
r-xyz](https://github.com/r-xyz)
[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [
Steven Mainor](https://github.com/DrekiDegga)
[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [
arne-kroeger](https://github.com/arne-kroeger)
[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | [
Glukose1](https://github.com/Glukose1)
[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | +| [
Scarzy](https://github.com/Scarzy)
[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [
setpill](https://github.com/setpill)
[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [
swift2512](https://github.com/swift2512)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [
Darren Rainey](https://darrenraineys.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [
maciej-poleszczyk](https://github.com/maciej-poleszczyk)
[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [
Sebastian Groß](https://github.com/sgross-emlix)
[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") | [
Anouar Touati](https://github.com/AnouarTouati)
[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | +| [
aHVzY2g](https://github.com/aHVzY2g)
[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [
林博仁 Buo-ren Lin](https://brlin.me)
[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [
Adugna Gizaw](https://orbalia.pythonanywhere.com/)
[🌍](#translation-addex12 "Translation") | [
Jesse Ostrander](https://github.com/jostrander)
[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [
James M](https://github.com/azmcnutt)
[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") | [
Fiala06](https://github.com/Fiala06)
[💻](https://github.com/snipe/snipe-it/commits?author=Fiala06 "Code") | [
Nathan Taylor](https://github.com/ntaylor-86)
[💻](https://github.com/snipe/snipe-it/commits?author=ntaylor-86 "Code") | +| [
fvollmer](https://github.com/fvollmer)
[💻](https://github.com/snipe/snipe-it/commits?author=fvollmer "Code") | [
36864](https://github.com/36864)
[💻](https://github.com/snipe/snipe-it/commits?author=36864 "Code") | [
Daniel O'Connor](http://clockwerx.blogspot.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=CloCkWeRX "Code") | [
BeatSpark](https://github.com/BeatSpark)
[💻](https://github.com/snipe/snipe-it/commits?author=BeatSpark "Code") | [
mrdahbi](https://github.com/mrdahbi)
[💻](https://github.com/snipe/snipe-it/commits?author=mrdahbi "Code") | [
Fabian Schmid](http://sr.solutions)
[💻](https://github.com/snipe/snipe-it/commits?author=chfsx "Code") | [
Chris Olin](https://www.chrisolin.com)
[💻](https://github.com/snipe/snipe-it/commits?author=realchrisolin "Code") | +| [
Dan](https://github.com/mnemonicly)
[💻](https://github.com/snipe/snipe-it/commits?author=mnemonicly "Code") | [
Nebel](https://github.com/NebelKreis)
[💻](https://github.com/snipe/snipe-it/commits?author=NebelKreis "Code") | [
test1337ahp](https://github.com/test1337ahp)
[💻](https://github.com/snipe/snipe-it/commits?author=test1337ahp "Code") | [
Jonathon Reinhart](https://github.com/JonathonReinhart)
[💻](https://github.com/snipe/snipe-it/commits?author=JonathonReinhart "Code") | [
aranar-pro](https://github.com/aranar-pro)
[💻](https://github.com/snipe/snipe-it/commits?author=aranar-pro "Code") | [
Phil](https://github.com/phil-flip)
[💻](https://github.com/snipe/snipe-it/commits?author=phil-flip "Code") | [
Steffy Fort](https://fe80.fr/)
[💻](https://github.com/snipe/snipe-it/commits?author=fe80 "Code") | +| [
Jared Busch](https://github.com/sorvani)
[💻](https://github.com/snipe/snipe-it/commits?author=sorvani "Code") | [
seanborg-codethink](https://github.com/seanborg-codethink)
[💻](https://github.com/snipe/snipe-it/commits?author=seanborg-codethink "Code") | [
dkaatz](https://github.com/dkaatz)
[💻](https://github.com/snipe/snipe-it/commits?author=dkaatz "Code") | [
Daniel Ruf](https://threema.id/74SF7MW6?text=)
[💻](https://github.com/snipe/snipe-it/commits?author=DanielRuf "Code") | [
ahpaleus](https://github.com/ahpaleus)
[💻](https://github.com/snipe/snipe-it/commits?author=ahpaleus "Code") | [
Anh DAO-DUY](https://github.com/mink-adao-duy)
[💻](https://github.com/snipe/snipe-it/commits?author=mink-adao-duy "Code") | [
Andres Gutierrez](https://github.com/Serdnad)
[💻](https://github.com/snipe/snipe-it/commits?author=Serdnad "Code") | +| [
Warren White](https://github.com/wewhite)
[💻](https://github.com/snipe/snipe-it/commits?author=wewhite "Code") | [
Robin Temme](https://robintemme.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=robintemme "Code") | [
herroworrd](https://github.com/herroworrd)
[💻](https://github.com/snipe/snipe-it/commits?author=herroworrd "Code") | [
vicleos](https://mubiu.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=vicleos "Code") | [
Bob Clough](http://thinkl33t.co.uk/)
[💻](https://github.com/snipe/snipe-it/commits?author=thinkl33t "Code") | [
Brandon Daniel Bailey](https://github.com/brandon-bailey)
[💻](https://github.com/snipe/snipe-it/commits?author=brandon-bailey "Code") | [
Marc Bartelt](https://github.com/marcquark)
[💻](https://github.com/snipe/snipe-it/commits?author=marcquark "Code") | +| [
manu-crealytics](https://github.com/manu-crealytics)
[💻](https://github.com/snipe/snipe-it/commits?author=manu-crealytics "Code") | [
Konstantin Köhring](https://www.galaxy102.de/)
[💻](https://github.com/snipe/snipe-it/commits?author=Galaxy102 "Code") | [
Deloz](https://deloz.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=deloz "Code") | [
Martin Berg](https://github.com/mbrrg)
[💻](https://github.com/snipe/snipe-it/commits?author=mbrrg "Code") | [
Richard Schwab](https://github.com/Nothing4You)
[💻](https://github.com/snipe/snipe-it/commits?author=Nothing4You "Code") | [
Rick Heil](https://rickheil.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=rickheil "Code") | [
Ross Crawford-d'Heureuse](https://github.com/rosscdh)
[💻](https://github.com/snipe/snipe-it/commits?author=rosscdh "Code") | +| [
Ryan McGuire](https://github.com/McG800)
[💻](https://github.com/snipe/snipe-it/commits?author=McG800 "Code") | [
SBrown2021](https://github.com/SBrown2021)
[💻](https://github.com/snipe/snipe-it/commits?author=SBrown2021 "Code") | [
Serkan](https://github.com/serkanerip)
[💻](https://github.com/snipe/snipe-it/commits?author=serkanerip "Code") | [
Shanks](https://www.yudelei.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=Shankschn "Code") | [
cendai-mis](https://github.com/cendai-mis)
[💻](https://github.com/snipe/snipe-it/commits?author=cendai-mis "Code") | [
Shaun McPeck](https://smcpeck.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=smcpeck "Code") | [
Stephen](https://github.com/snazy2000)
[💻](https://github.com/snipe/snipe-it/commits?author=snazy2000 "Code") | +| [
Steven](http://nevets82.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=Nevets82 "Code") | [
Mateus Villar](https://mateusvillar.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=Mateus-Romera "Code") | [
Matthew Zackschewski](https://github.com/mzack5020)
[💻](https://github.com/snipe/snipe-it/commits?author=mzack5020 "Code") | [
Matthias Frei](https://www.frei.media/)
[💻](https://github.com/snipe/snipe-it/commits?author=firefrei "Code") | [
Nenad Ticaric](https://github.com/nticaric)
[💻](https://github.com/snipe/snipe-it/commits?author=nticaric "Code") | [
Nikolay Didenko](https://github.com/Scorcher)
[💻](https://github.com/snipe/snipe-it/commits?author=Scorcher "Code") | [
Nuno Maduro](https://nunomaduro.com/sponsorships)
[💻](https://github.com/snipe/snipe-it/commits?author=nunomaduro "Code") | +| [
Oliver Walerys](https://tektikhq.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=owalerys "Code") | [
R. Christian McDonald](https://keybase.io/rcmcdonald91)
[💻](https://github.com/snipe/snipe-it/commits?author=rcmcdonald91 "Code") | [
nix](https://nnix.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=nixn "Code") | [
octobunny](https://github.com/octobunny)
[💻](https://github.com/snipe/snipe-it/commits?author=octobunny "Code") | [
Ryan](https://github.com/sreyemnayr)
[💻](https://github.com/snipe/snipe-it/commits?author=sreyemnayr "Code") | [
p3nj](https://benji.ltd/)
[💻](https://github.com/snipe/snipe-it/commits?author=p3nj "Code") | [
Tim White](https://github.com/timwsuqld)
[💻](https://github.com/snipe/snipe-it/commits?author=timwsuqld "Code") | +| [
yannikp](https://github.com/yannikp)
[💻](https://github.com/snipe/snipe-it/commits?author=yannikp "Code") | [
victoria](https://github.com/viclou)
[💻](https://github.com/snipe/snipe-it/commits?author=viclou "Code") | [
Valentyn Tulub](https://github.com/valentyntu)
[💻](https://github.com/snipe/snipe-it/commits?author=valentyntu "Code") | [
Wouter van Os](http://wouter0100.nl/)
[💻](https://github.com/snipe/snipe-it/commits?author=Wouter0100 "Code") | [
Wyatt Teeter](https://www.linkedin.com/in/wyatt-teeter)
[💻](https://github.com/snipe/snipe-it/commits?author=xWyatt "Code") | [
Yorick Terweijden](https://github.com/terwey)
[💻](https://github.com/snipe/snipe-it/commits?author=terwey "Code") | [
bmkalle](https://github.com/bmkalle)
[💻](https://github.com/snipe/snipe-it/commits?author=bmkalle "Code") | +| [
bricelabelle](https://github.com/bricelabelle)
[💻](https://github.com/snipe/snipe-it/commits?author=bricelabelle "Code") | [
corydlamb](https://github.com/corydlamb)
[💻](https://github.com/snipe/snipe-it/commits?author=corydlamb "Code") | [
Diogenes S. Jesus](http://twitter.com/splash)
[💻](https://github.com/snipe/snipe-it/commits?author=splashx "Code") | [
D M](https://github.com/dkmansion)
[💻](https://github.com/snipe/snipe-it/commits?author=dkmansion "Code") | [
Dustin B](https://github.com/Jarli01)
[💻](https://github.com/snipe/snipe-it/commits?author=Jarli01 "Code") | [
Fabian Grutschus](https://github.com/fabiang)
[💻](https://github.com/snipe/snipe-it/commits?author=fabiang "Code") | [
MelonSmasher](https://github.com/MelonSmasher)
[💻](https://github.com/snipe/snipe-it/commits?author=MelonSmasher "Code") | +| [
AlexanderWPapyrus](https://github.com/AlexanderWPapyrus)
[💻](https://github.com/snipe/snipe-it/commits?author=AlexanderWPapyrus "Code") | [
Alexandr Hacicheant](https://github.com/disc)
[💻](https://github.com/snipe/snipe-it/commits?author=disc "Code") | [
Hex](https://hex128.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=hex128 "Code") | [
Arunas Skirius](https://github.com/arukompas)
[💻](https://github.com/snipe/snipe-it/commits?author=arukompas "Code") | [
Ben Periton](https://github.com/benperiton)
[💻](https://github.com/snipe/snipe-it/commits?author=benperiton "Code") | [
Byron Wolfman](https://wolfman.dev/)
[💻](https://github.com/snipe/snipe-it/commits?author=byronwolfman "Code") | [
Calvin](https://github.com/CalvinSchwartz)
[💻](https://github.com/snipe/snipe-it/commits?author=CalvinSchwartz "Code") | +| [
Juan Font](https://github.com/juanfont)
[💻](https://github.com/snipe/snipe-it/commits?author=juanfont "Code") | [
Juho Taipale](https://github.com/juhotaipale)
[💻](https://github.com/snipe/snipe-it/commits?author=juhotaipale "Code") | [
Korvin Szanto](https://github.com/KorvinSzanto)
[💻](https://github.com/snipe/snipe-it/commits?author=KorvinSzanto "Code") | [
Lewis Foster](https://lewisfoster.foo/)
[💻](https://github.com/snipe/snipe-it/commits?author=sniff122 "Code") | [
Logan Swartzendruber](https://github.com/loganswartz)
[💻](https://github.com/snipe/snipe-it/commits?author=loganswartz "Code") | [
Lorenzo P.](https://github.com/lopezio)
[💻](https://github.com/snipe/snipe-it/commits?author=lopezio "Code") | [
Lukas Jung](https://github.com/m4us1ne)
[💻](https://github.com/snipe/snipe-it/commits?author=m4us1ne "Code") | +| [
Ellie](https://leafedfox.xyz/)
[💻](https://github.com/snipe/snipe-it/commits?author=LeafedFox "Code") | [
GA Stamper](https://github.com/gastamper)
[💻](https://github.com/snipe/snipe-it/commits?author=gastamper "Code") | [
Guillaume Lefranc](https://github.com/gl-pup)
[💻](https://github.com/snipe/snipe-it/commits?author=gl-pup "Code") | [
Hajo Möller](https://github.com/dasjoe)
[💻](https://github.com/snipe/snipe-it/commits?author=dasjoe "Code") | [
Istvan Basa](https://github.com/pottom)
[💻](https://github.com/snipe/snipe-it/commits?author=pottom "Code") | [
JJ Asghar](https://jjasghar.github.io/)
[💻](https://github.com/snipe/snipe-it/commits?author=jjasghar "Code") | [
James E. Msenga](https://github.com/JemCdo)
[💻](https://github.com/snipe/snipe-it/commits?author=JemCdo "Code") | +| [
Jan Felix Wiebe](https://github.com/jfwiebe)
[💻](https://github.com/snipe/snipe-it/commits?author=jfwiebe "Code") | [
Jo Drexl](https://www.nfon.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=drexljo "Code") | [
Austin Sasko](https://github.com/austinsasko)
[💻](https://github.com/snipe/snipe-it/commits?author=austinsasko "Code") | [
Jasson](http://jassoncordones.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=JassonCordones "Code") | [
Okean](https://github.com/Tinyblargon)
[💻](https://github.com/snipe/snipe-it/commits?author=Tinyblargon "Code") | [
Alejandro Medrano](https://www.lst.tfo.upm.es/alejandro-medrano/)
[💻](https://github.com/snipe/snipe-it/commits?author=amedranogil "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! diff --git a/Dockerfile b/Dockerfile index bd363ccd18..b4c4e8a357 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM ubuntu:22.04 +FROM ubuntu:24.04 LABEL maintainer="Brady Wetherington " # No need to add `apt-get clean` here, reference: -# - https://github.com/snipe/snipe-it/pull/9201 +# - https://github.com/grokability/snipe-it/pull/9201 # - https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get RUN export DEBIAN_FRONTEND=noninteractive; \ @@ -14,16 +14,16 @@ RUN export DEBIAN_FRONTEND=noninteractive; \ apt-utils \ apache2 \ apache2-bin \ -libapache2-mod-php8.1 \ -php8.1-curl \ -php8.1-ldap \ -php8.1-mysql \ -php8.1-gd \ -php8.1-xml \ -php8.1-mbstring \ -php8.1-zip \ -php8.1-bcmath \ -php8.1-redis \ +libapache2-mod-php8.3 \ +php8.3-curl \ +php8.3-ldap \ +php8.3-mysql \ +php8.3-gd \ +php8.3-xml \ +php8.3-mbstring \ +php8.3-zip \ +php8.3-bcmath \ +php8.3-redis \ php-memcached \ patch \ curl \ @@ -40,8 +40,7 @@ autoconf \ libc-dev \ libldap-common \ pkg-config \ -libmcrypt-dev \ -php8.1-dev \ +php8.3-dev \ ca-certificates \ unzip \ dnsutils \ @@ -51,18 +50,13 @@ dnsutils \ RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar RUN php go-pear.phar -RUN pecl install mcrypt - -RUN bash -c "echo extension=/usr/lib/php/20210902/mcrypt.so > /etc/php/8.1/mods-available/mcrypt.ini" - -RUN phpenmod mcrypt RUN phpenmod gd RUN phpenmod bcmath -RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/apache2/php.ini -RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.1/cli/php.ini +RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.3/apache2/php.ini +RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/8.3/cli/php.ini -RUN useradd -m --uid 1000 --gid 50 docker +RUN useradd -m --uid 10000 --gid 50 docker RUN echo export APACHE_RUN_USER=docker >> /etc/apache2/envvars RUN echo export APACHE_RUN_GROUP=staff >> /etc/apache2/envvars @@ -116,7 +110,7 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Get dependencies USER docker -RUN composer install --no-dev --working-dir=/var/www/html +RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html && rm -rf /var/www/html/vendor/*/*/.git USER root ############### APPLICATION INSTALL/INIT ################# diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 6d68e7ad9e..5df4b6d06a 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,35 +1,35 @@ -FROM alpine:3.18.6 +FROM alpine:3.19 # Apache + PHP RUN apk add --no-cache \ apache2 \ - php81 \ - php81-common \ - php81-apache2 \ - php81-curl \ - php81-ldap \ - php81-mysqli \ - php81-gd \ - php81-xml \ - php81-mbstring \ - php81-zip \ - php81-ctype \ - php81-tokenizer \ - php81-pdo_mysql \ - php81-openssl \ - php81-bcmath \ - php81-phar \ - php81-json \ - php81-iconv \ - php81-fileinfo \ - php81-simplexml \ - php81-session \ - php81-dom \ - php81-xmlwriter \ - php81-xmlreader \ - php81-sodium \ - php81-redis \ - php81-pecl-memcached \ - php81-exif \ + php82 \ + php82-common \ + php82-apache2 \ + php82-curl \ + php82-ldap \ + php82-mysqli \ + php82-gd \ + php82-xml \ + php82-mbstring \ + php82-zip \ + php82-ctype \ + php82-tokenizer \ + php82-pdo_mysql \ + php82-openssl \ + php82-bcmath \ + php82-phar \ + php82-json \ + php82-iconv \ + php82-fileinfo \ + php82-simplexml \ + php82-session \ + php82-dom \ + php82-xmlwriter \ + php82-xmlreader \ + php82-sodium \ + php82-redis \ + php82-pecl-memcached \ + php82-exif \ curl \ wget \ vim \ @@ -42,7 +42,7 @@ COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf # Where apache's PID lives RUN mkdir -p /run/apache2 && chown apache:apache /run/apache2 -RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php81/php.ini +RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php82/php.ini COPY docker/000-default-2.4.conf /etc/apache2/conf.d/default.conf # Enable mod_rewrite @@ -73,18 +73,18 @@ RUN mkdir -p /var/www/.composer && chown apache /var/www/.composer # Install dependencies USER apache -RUN COMPOSER_CACHE_DIR=/dev/null composer install --no-dev --working-dir=/var/www/html +RUN COMPOSER_CACHE_DIR=/dev/null composer install --working-dir=/var/www/html USER root VOLUME ["/var/lib/snipeit"] -# Entrypoints -COPY docker/entrypoint_alpine.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +# Startup script +COPY docker/startup_alpine.sh /startup.sh +RUN chmod +x /startup.sh ENTRYPOINT ["/sbin/tini", "--"] -CMD ["/entrypoint.sh"] +CMD ["/startup.sh"] EXPOSE 80 diff --git a/Dockerfile.fpm-alpine b/Dockerfile.fpm-alpine index 77302524d4..0b89672267 100644 --- a/Dockerfile.fpm-alpine +++ b/Dockerfile.fpm-alpine @@ -70,7 +70,7 @@ COPY --from=composer /usr/bin/composer /usr/local/bin ARG COMPOSER_ALLOW_SUPERUSER=1 RUN set -eux; \ # Download and extract snipeit tarball - curl -o snipeit.tar.gz -fL "https://github.com/snipe/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \ + curl -o snipeit.tar.gz -fL "https://github.com/grokability/snipe-it/archive/v$SNIPEIT_RELEASE.tar.gz"; \ tar -xzf snipeit.tar.gz --strip-components=1 -C /var/www/html/; \ rm snipeit.tar.gz; \ # Install composer php dependencies @@ -97,7 +97,7 @@ RUN set -eux; \ VOLUME [ "/var/lib/snipeit" ] COPY --chown=www-data:www-data docker/docker-secrets.env /var/www/html/.env -COPY --chmod=655 docker/docker-entrypoint.sh /usr/local/bin/docker-snipeit-entrypoint +COPY --chmod=655 docker/startup_alpine_fpm.sh /startup.sh COPY docker/column-statistics.cnf /etc/mysql/conf.d/column-statistics.cnf -ENTRYPOINT [ "/usr/local/bin/docker-snipeit-entrypoint" ] -CMD [ "/usr/local/bin/docker-php-entrypoint", "php-fpm" ] +ENTRYPOINT [ "/startup.sh" ] +CMD [ "/startup.sh", "php-fpm" ] diff --git a/README.md b/README.md index 6d2c45ac71..062be60453 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,34 @@ -![snipe-it-by-grok](https://github.com/snipe/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602) +![snipe-it-by-grok](https://github.com/grokability/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml) +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/804dd1beb14a41f38810ab77d64fc4fc)](https://app.codacy.com/gh/grokability/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests.yml) [![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) ## Snipe-IT - Open Source Asset Management System This is a FOSS project for asset management in IT Operations. Knowing who has which laptop, when it was purchased in order to depreciate it correctly, handling software licenses, etc. -It is built on [Laravel 10](http://laravel.com). +It is built on [Laravel 11](http://laravel.com). -Snipe-IT is actively developed and we [release quite frequently](https://github.com/snipe/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).) +Snipe-IT is actively developed and we [release quite frequently](https://github.com/grokability/snipe-it/releases). ([Check out the live demo here](https://snipeitapp.com/demo/).) > [!TIP] > __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into. +----- + +### Table of Contents +* [Installation](#installation) +* [User's Manual](#users-manual) +* [Bug Reports & Feature Requests](#bug-reports--feature-requests) +* [Security](#security) +* [Upgrading](#upgrading) +* [Translations!](#translations-) +* [Libraries, Modules & Related Projects](#libraries-modules--related-projects) +* [Join the Community!](#join-the-community) +* [Contributing](#contributing) +* [Announcement List](#announcement-list) + + ----- ### Installation @@ -22,8 +37,6 @@ For instructions on installing and configuring Snipe-IT on your server, check ou If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help. - - ----- ### User's Manual For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.io/docs/overview). @@ -31,24 +44,25 @@ For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.i ----- ### Bug Reports & Feature Requests -Feel free to check out the [GitHub Issues for this project](https://github.com/snipe/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue. +Feel free to check out the [GitHub Issues for this project](https://github.com/grokability/snipe-it/issues) to open a bug report or see what open issues you can help with. Please search through existing issues (open *and* closed) to see if your question has already been answered before opening a new issue. > [!IMPORTANT] > **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.** -> + ----- +### Security + +> [!IMPORTANT] +> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.** +----- + + ### Upgrading Please see the [upgrading documentation](https://snipe-it.readme.io/docs/upgrading) for instructions on upgrading Snipe-IT. ------ -### Announcement List - -To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important. - ------- - ### Translations! Please see the [translations documentation](https://snipe-it.readme.io/docs/translations) for information about available languages and how to add translations to Snipe-IT. @@ -62,38 +76,72 @@ Since the release of the JSON REST API, several third-party developers have been > [!NOTE] > 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. :) -- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer) +#### Libraries & Modules + - [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey) -- [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft) - [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it - [jamf2snipe](https://github.com/grokability/jamf2snipe) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance - [jamf-snipe-rename](https://macblog.org/jamf-snipe-rename/) - Python script to rename computers in Jamf from Snipe-IT -- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT - [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira) - [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag. - [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit). -- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it. -- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT +- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-IT. +- [MosyleSnipeSync](https://github.com/RodneyLeeBrands/MosyleSnipeSync) by [@Karpadiem](https://github.com/Karpadiem) - Python script to synchronize information between Mosyle and Snipe-IT. - [WWW::SnipeIT](https://github.com/SEDC/perl-www-snipeit) by [@SEDC](https://github.com/SEDC) - perl module for accessing the API -- [UniFi to Snipe-IT](https://github.com/RodneyLeeBrands/UnifiSnipeSync) by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT. +- [UniFi to Snipe-IT](https://www.edtechirl.com/p/snipe-it-and-azure-asset-management) originally by [@karpadiem](https://github.com/karpadiem) - Python script that synchronizes UniFi devices with Snipe-IT. - [Kandji2Snipe](https://github.com/grokability/kandji2snipe) by [@briangoldstein](https://github.com/briangoldstein) - Python script that synchronizes Kandji with Snipe-IT. -- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by @ReticentRobot - Windows agent for Snipe-IT +- [SnipeAgent](https://github.com/ReticentRobot/SnipeAgent) by [@ReticentRobot](https://github.com/ReticentRobot) - Windows agent for Snipe-IT. +- [Gate Pass Generator](https://github.com/cha7uraAE/snipe-it-gate-pass-system) by [@cha7uraAE](https://github.com/cha7uraAE) - A Streamlit application for generating gate passes based on hardware data from a Snipe-IT API. +- [InQRy (archived)](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft) +- [Marksman (archived)](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT +- [Python Module (archived)](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer) + +We also have a handful of [Google Apps scripts](https://github.com/grokability/google-apps-scripts-for-snipe-it) to help with various tasks. + +#### Mobile Apps + +We're currently working on our own mobile app, but in the meantime, check out these third-party apps that work with Snipe-IT: + +- [SnipeMate](https://snipemate.app/) (iOS, Google Play, Huawei AppGallery) by Mars Technology +- [Snipe-Scan](https://apps.apple.com/do/app/snipe-scan/id6744179400?uo=2) (iOS) by Nicolas Maton +- [Snipe-IT Assets Management](https://play.google.com/store/apps/details?id=com.diegogarciadev.assetsmanager.snipeit&hl=en&pli=1) (Google Play) by DiegoGarciaDEV +- [AssetX](https://apps.apple.com/my/app/assetx-for-snipe-it/id6741996196?uo=2) (iOS) for Snipe-IT by Rishi Gupta + +----- + +### Join the Community! + +- **[Join our Discord](https://discord.gg/yZFtShAcKk)!** It’s full of great people. We even wrote about it [here](https://grokstar.dev/culture/2024/06/the-unlikely-rise-of-discord-as-a-support-channel/)! +- **Follow us on Bluesky** at [@snipeitapp.com](https://bsky.app/profile/snipeitapp.com) +- **Follow us on Mastodon** at [hachyderm.io/@grokability](https://hachyderm.io/@grokability) +- **Follow our blog** at [Grokstar.Dev](https://grokstar.dev) +- **Subscribe here** on Github for notifications about new releases. (We recommend selecting "Releases" only for most users - this repo can get noisy.) ----- ### Contributing -Please see the documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview). +**Please refrain from submitting issues or pull requests generated by fully-automated tools. Maintainers reserve the right, at their sole discretion, to close such submissions and to block any account responsible for them.** -Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. +Contributions should follow from a human-to-human discussion in the form of an issue for the best chances of being merged into the core project. (Sometimes we might already be working on that feature, sometimes we've decided against ) + +Please see the complete documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview). + +This project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. The ERD is available [online here](https://drawsql.app/templates/snipe-it). -[Here is a list](CONTRIBUTORS.md) of the wonderful people that have contributed to the Snipe-IT. +Be sure to check out all of the [amazing people](CONTRIBUTORS.md) that have contributed to Snipe-IT over the years! ----- -### Security +### Star History -> [!IMPORTANT] -> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.** +[![Star History Chart](https://api.star-history.com/svg?repos=grokability/snipe-it&type=Date)](https://www.star-history.com/#grokability/snipe-it&Date) + +------ +### Announcement List + +To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important. + +We also usually make smaller announcements on our social accounts, our Discord, and our blog, so be sure to subscribe to those if you're looking for more granular announcements. diff --git a/SECURITY.md b/SECURITY.md index dcbfd3e6f0..e052527bd1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,10 +10,13 @@ however there are times when library dependencies and/or PHP/MySQL dependencies make it impossible to backport security fixes on older versions. | Version | Supported | -| ------- | ------------------ | -| 5.1.x | :white_check_mark: | +|---------| ------------------ | +| 8.x | :white_check_mark: | +| 7.x | :white_check_mark: | +| 6.x | :x: | +| 5.1.x | :x: | | 5.0.x | :x: | -| 4.0.x | :white_check_mark: | +| 4.0.x | :x: | | < 4.0 | :x: | ## Reporting a Vulnerability diff --git a/TESTING.md b/TESTING.md index 92f3184eec..002cf27d3a 100644 --- a/TESTING.md +++ b/TESTING.md @@ -20,7 +20,7 @@ APP_DEBUG=true APP_KEY=base64:glJpcM7BYwWiBggp3SQ/+NlRkqsBQMaGEOjemXqJzOU= APP_URL=http://localhost:8000 APP_TIMEZONE='UTC' -APP_LOCALE=en +APP_LOCALE=en-US # -------------------------------------------- # REQUIRED: DATABASE SETTINGS diff --git a/Vagrantfile b/Vagrantfile index 27efef7b1a..7c12ed642b 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,7 +1,7 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -SNIPEIT_SH_URL= "https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh" +SNIPEIT_SH_URL= "https://raw.githubusercontent.com/grokability/snipe-it/master/snipeit.sh" NETWORK_BRIDGE= "en0: Wi-Fi (AirPort)" Vagrant.configure("2") do |config| diff --git a/app.json b/app.json index 0bf523f5e4..24d874114a 100644 --- a/app.json +++ b/app.json @@ -6,7 +6,7 @@ "it asset" ], "website": "https://snipeitapp.com/", - "repository": "https://github.com/snipe/snipe-it", + "repository": "https://github.com/grokability/snipe-it", "logo": "https://pbs.twimg.com/profile_images/976748875733020672/K-HnZCCK_400x400.jpg", "success_url": "/setup", "env": { diff --git a/app/Actions/CheckoutRequests/CancelCheckoutRequestAction.php b/app/Actions/CheckoutRequests/CancelCheckoutRequestAction.php new file mode 100644 index 0000000000..2d6dd68a08 --- /dev/null +++ b/app/Actions/CheckoutRequests/CancelCheckoutRequestAction.php @@ -0,0 +1,48 @@ +cancelRequest(); + + $asset->decrement('requests_counter', 1); + + $data['item'] = $asset; + $data['target'] = $user; + $data['item_quantity'] = 1; + $settings = Setting::getSettings(); + + $logaction = new Actionlog(); + $logaction->item_id = $data['asset_id'] = $asset->id; + $logaction->item_type = $data['item_type'] = Asset::class; + $logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s'); + $logaction->target_id = $data['user_id'] = auth()->id(); + $logaction->target_type = User::class; + $logaction->location_id = $user->location_id ?? null; + $logaction->logaction('request canceled'); + + try { + $settings->notify(new RequestAssetCancelation($data)); + } catch (\Exception $e) { + \Log::warning($e); + } + + return true; + } + +} \ No newline at end of file diff --git a/app/Actions/CheckoutRequests/CreateCheckoutRequestAction.php b/app/Actions/CheckoutRequests/CreateCheckoutRequestAction.php new file mode 100644 index 0000000000..6870cfba2d --- /dev/null +++ b/app/Actions/CheckoutRequests/CreateCheckoutRequestAction.php @@ -0,0 +1,54 @@ +find($asset->id))) { + throw new AssetNotRequestable($asset); + } + if (!Company::isCurrentUserHasAccess($asset)) { + throw new AuthorizationException(); + } + + $data['item'] = $asset; + $data['target'] = $user; + $data['item_quantity'] = 1; + $settings = Setting::getSettings(); + + $logaction = new Actionlog(); + $logaction->item_id = $data['asset_id'] = $asset->id; + $logaction->item_type = $data['item_type'] = Asset::class; + $logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s'); + $logaction->target_id = $data['user_id'] = auth()->id(); + $logaction->target_type = User::class; + $logaction->location_id = $user->location_id ?? null; + $logaction->logaction('requested'); + + $asset->request(); + $asset->increment('requests_counter', 1); + try { + $settings->notify(new RequestAssetNotification($data)); + } catch (\Exception $e) { + Log::warning($e); + } + + return true; + } +} \ No newline at end of file diff --git a/app/Console/Commands/DisableSAML.php b/app/Console/Commands/DisableSAML.php new file mode 100644 index 0000000000..795e65f1c7 --- /dev/null +++ b/app/Console/Commands/DisableSAML.php @@ -0,0 +1,53 @@ +confirm("\n****************************************************\nThis will disable SAML support. You will not be able \nto login with an account that does not exist \nlocally in the Snipe-IT local database. \n****************************************************\n\nDo you wish to continue? [y|N]")) { + $setting = Setting::getSettings(); + $setting->saml_enabled = 0; + if ($setting->save()) { + $this->info('SAML has been set to disabled.'); + } else { + $this->info('Unable to disable SAML.'); + } + } else { + $this->info('Canceled. No actions taken.'); + } + } +} diff --git a/app/Console/Commands/FixBulkAccessoryCheckinActionLogEntries.php b/app/Console/Commands/FixBulkAccessoryCheckinActionLogEntries.php new file mode 100644 index 0000000000..694d8d3b30 --- /dev/null +++ b/app/Console/Commands/FixBulkAccessoryCheckinActionLogEntries.php @@ -0,0 +1,151 @@ +skipBackup = $this->option('skip-backup'); + $this->dryrun = $this->option('dry-run'); + + if ($this->dryrun) { + $this->info('This is a DRY RUN - no changes will be saved.'); + $this->newLine(); + } + + $logs = Actionlog::query() + // only look for accessory checkin logs + ->where('item_type', Accessory::class) + // that were part of a bulk checkin + ->where('note', 'Bulk checkin items') + // logs that were improperly timestamped should have created_at in the 1970s + ->whereYear('created_at', '1970') + ->get(); + + if ($logs->isEmpty()) { + $this->info('No logs found with incorrect timestamps.'); + return 0; + } + + $this->info('Found ' . $logs->count() . ' logs with incorrect timestamps:'); + + $this->table( + ['ID', 'Created By', 'Created At', 'Updated At'], + $logs->map(function ($log) { + return [ + $log->id, + $log->created_by, + $log->created_at, + $log->updated_at, + ]; + }) + ); + + if (!$this->dryrun && !$this->confirm('Update these logs?')) { + return 0; + } + + if (!$this->dryrun && !$this->skipBackup) { + $this->info('Backing up the database before making changes...'); + $this->call('snipeit:backup'); + } + + if ($this->dryrun) { + $this->newLine(); + $this->info('DRY RUN. NOT ACTUALLY UPDATING LOGS.'); + } + + foreach ($logs as $log) { + $this->newLine(); + $this->info('Processing log id:' . $log->id); + + // created_by was not being set for accessory bulk checkins + // so let's see if there was another bulk checkin log + // with the same timestamp and a created_by value we can use. + if (is_null($log->created_by)) { + $createdByFromSimilarLog = $this->getCreatedByAttributeFromSimilarLog($log); + + if ($createdByFromSimilarLog) { + $this->line(vsprintf('Updating log id:%s created_by to %s', [$log->id, $createdByFromSimilarLog])); + $log->created_by = $createdByFromSimilarLog; + } else { + $this->warn(vsprintf('No created_by found for log id:%s', [$log->id])); + $this->warn('Skipping updating this log since no similar log was found to update created_by from.'); + + // If we can't find a similar log then let's skip updating it + continue; + } + } + + $this->line(vsprintf('Updating log id:%s from %s to %s', [$log->id, $log->created_at, $log->updated_at])); + $log->created_at = $log->updated_at; + + if (!$this->dryrun) { + Model::withoutTimestamps(function () use ($log) { + $log->saveQuietly(); + }); + } + } + + $this->newLine(); + + if ($this->dryrun) { + $this->info('DRY RUN. NO CHANGES WERE ACTUALLY MADE.'); + } + + return 0; + } + + /** + * Hopefully the bulk checkin included other items like assets or licenses + * so we can use one of those logs to get the correct created_by value. + * + * This method attempts to find a bulk check in log that was + * created at the same time as the log passed in. + */ + private function getCreatedByAttributeFromSimilarLog(Actionlog $log): null|int + { + $similarLog = Actionlog::query() + ->whereNotNull('created_by') + ->where([ + 'action_type' => 'checkin from', + 'note' => 'Bulk checkin items', + 'target_id' => $log->target_id, + 'target_type' => $log->target_type, + 'created_at' => $log->updated_at, + ]) + ->first(); + + if ($similarLog) { + return $similarLog->created_by; + } + + return null; + } +} diff --git a/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php b/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php new file mode 100644 index 0000000000..f368929f16 --- /dev/null +++ b/app/Console/Commands/FixUpAssignedTypeWithoutAssignedTo.php @@ -0,0 +1,32 @@ +whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]); + $this->info("Assets with an assigned_type but no assigned_to are fixed"); + } +} diff --git a/app/Console/Commands/FixupAssignedToWithoutAssignedType.php b/app/Console/Commands/FixupAssignedToWithoutAssignedType.php new file mode 100644 index 0000000000..ea3a55439a --- /dev/null +++ b/app/Console/Commands/FixupAssignedToWithoutAssignedType.php @@ -0,0 +1,66 @@ +whereNotNull("assigned_to")->withTrashed(); + $this->withProgressBar($assets->get(), function (Asset $asset) { + //now check each action log, from the most recent backwards, to find the last checkin or checkout + foreach($asset->log()->orderBy("id","desc")->get() as $action_log) { + if($this->option("debug")) { + $this->info("Asset id: " . $asset->id . " action log, action type is: " . $action_log->action_type); + } + switch($action_log->action_type) { + case 'checkin from': + if($this->option("debug")) { + $this->info("Doing a checkin for ".$asset->id); + } + $asset->assigned_to = null; + // if you have a required custom field, we still want to save, and we *don't* want an action_log + $asset->saveQuietly(); + return; + + case 'checkout': + if($this->option("debug")) { + $this->info("Doing a checkout for " . $asset->id . " picking target type: " . $action_log->target_type); + } + if($asset->assigned_to != $action_log->target_id) { + $this->error("Asset's assigned_to does *NOT* match Action Log's target_id. \$asset->assigned_to=".$asset->assigned_to." vs. \$action_log->target_id=".$action_log->target_id); + //FIXME - do we abort here? Do we try to keep looking? I don't know, this means your data is *really* messed up... + } + $asset->assigned_type = $action_log->target_type; + $asset->saveQuietly(); // see above + return; + } + } + $asset->assigned_to = null; //asset was never checked in or out in its lifetime - it stays 'checked in' + $asset->saveQuietly(); //see above + }); + $this->newLine(); + $this->info("Assets assigned_type are fixed"); + } +} diff --git a/app/Console/Commands/GeneratePersonalAccessToken.php b/app/Console/Commands/GeneratePersonalAccessToken.php index c5f531213c..63b246978e 100644 --- a/app/Console/Commands/GeneratePersonalAccessToken.php +++ b/app/Console/Commands/GeneratePersonalAccessToken.php @@ -2,11 +2,9 @@ namespace App\Console\Commands; -use App\Helpers\Helper; use Illuminate\Console\Command; use App\Models\User; use Laravel\Passport\TokenRepository; -use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Support\Facades\DB; class GeneratePersonalAccessToken extends Command @@ -43,9 +41,8 @@ class GeneratePersonalAccessToken extends Command * * @return void */ - public function __construct(TokenRepository $tokenRepository, ValidationFactory $validation) + public function __construct(TokenRepository $tokenRepository) { - $this->validation = $validation; $this->tokenRepository = $tokenRepository; parent::__construct(); } @@ -76,7 +73,7 @@ class GeneratePersonalAccessToken extends Command } else { - $this->warn('Your API Token has been created. Be sure to copy this token now, as it will not be accessible again.'); + $this->warn('Your API Token has been created. Be sure to copy this token now, as it WILL NOT be accessible again.'); if ($token = DB::table('oauth_access_tokens')->where('user_id', '=', $user->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first()) { $this->info('API Token ID: '.$token->id); diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php old mode 100755 new mode 100644 index 1856bf10fc..f90aeafd8d --- a/app/Console/Commands/LdapSync.php +++ b/app/Console/Commands/LdapSync.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Models\Asset; use App\Models\Department; use App\Models\Group; use Illuminate\Console\Command; @@ -53,18 +54,22 @@ class LdapSync extends Command ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes ini_set('memory_limit', env('LDAP_MEM_LIM', '500M')); - $ldap_result_username = Setting::getSettings()->ldap_username_field; - $ldap_result_last_name = Setting::getSettings()->ldap_lname_field; - $ldap_result_first_name = Setting::getSettings()->ldap_fname_field; - $ldap_result_active_flag = Setting::getSettings()->ldap_active_flag; - $ldap_result_emp_num = Setting::getSettings()->ldap_emp_num; - $ldap_result_email = Setting::getSettings()->ldap_email; - $ldap_result_phone = Setting::getSettings()->ldap_phone_field; - $ldap_result_jobtitle = Setting::getSettings()->ldap_jobtitle; - $ldap_result_country = Setting::getSettings()->ldap_country; - $ldap_result_location = Setting::getSettings()->ldap_location; - $ldap_result_dept = Setting::getSettings()->ldap_dept; - $ldap_result_manager = Setting::getSettings()->ldap_manager; + + $ldap_map = [ + "username" => Setting::getSettings()->ldap_username_field, + "last_name" => Setting::getSettings()->ldap_lname_field, + "first_name" => Setting::getSettings()->ldap_fname_field, + "active_flag" => Setting::getSettings()->ldap_active_flag, + "emp_num" => Setting::getSettings()->ldap_emp_num, + "email" => Setting::getSettings()->ldap_email, + "phone" => Setting::getSettings()->ldap_phone_field, + "jobtitle" => Setting::getSettings()->ldap_jobtitle, + "country" => Setting::getSettings()->ldap_country, + "location" => Setting::getSettings()->ldap_location, + "dept" => Setting::getSettings()->ldap_dept, + "manager" => Setting::getSettings()->ldap_manager, + ]; + $ldap_default_group = Setting::getSettings()->ldap_default_group; $search_base = Setting::getSettings()->ldap_base_dn; @@ -107,14 +112,25 @@ class LdapSync extends Command } /** - * If a filter has been specified, use that + * If a filter has been specified, use that, otherwise default to null */ if ($this->option('filter') != '') { - $results = Ldap::findLdapUsers($search_base, -1, $this->option('filter')); + $filter = $this->option('filter'); } else { - $results = Ldap::findLdapUsers($search_base); + $filter = null; } - + + /** + * We only need to request the LDAP attributes that we process + */ + $attributes = array_values(array_filter($ldap_map)); + + if (Setting::getSettings()->is_ad === 1 && is_null($ldap_map['active_flag'])) { + $attributes[] = 'useraccountcontrol'; + } + + $results = Ldap::findLdapUsers($search_base, -1, $filter, $attributes); + } catch (\Exception $e) { if ($this->option('json_summary')) { $json_summary = ['error' => true, 'error_message' => $e->getMessage(), 'summary' => []]; @@ -126,23 +142,24 @@ class LdapSync extends Command } /* Determine which location to assign users to by default. */ - $location = null; // TODO - this would be better called "$default_location", which is more explicit about its purpose + $default_location = null; if ($this->option('location') != '') { - if ($location = Location::where('name', '=', $this->option('location'))->first()) { + if ($default_location = Location::where('name', '=', $this->option('location'))->first()) { Log::debug('Location name ' . $this->option('location') . ' passed'); - Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')'); + Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')'); } } elseif ($this->option('location_id')) { + //TODO - figure out how or why this is an array? foreach($this->option('location_id') as $location_id) { - if ($location = Location::where('id', '=', $location_id)->first()) { + if ($default_location = Location::where('id', '=', $location_id)->first()) { Log::debug('Location ID ' . $location_id . ' passed'); - Log::debug('Importing to ' . $location->name . ' (' . $location->id . ')'); + Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')'); } } } - if (! isset($location)) { + if (!isset($default_location)) { Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.'); } @@ -183,17 +200,17 @@ class LdapSync extends Command } $usernames = []; for ($i = 0; $i < $location_users['count']; $i++) { - if (array_key_exists($ldap_result_username, $location_users[$i])) { + if (array_key_exists($ldap_map["username"], $location_users[$i])) { $location_users[$i]['ldap_location_override'] = true; $location_users[$i]['location_id'] = $ldap_loc['id']; - $usernames[] = $location_users[$i][$ldap_result_username][0]; + $usernames[] = $location_users[$i][$ldap_map["username"]][0]; } } // Delete located users from the general group. foreach ($results as $key => $generic_entry) { - if ((is_array($generic_entry)) && (array_key_exists($ldap_result_username, $generic_entry))) { - if (in_array($generic_entry[$ldap_result_username][0], $usernames)) { + if ((is_array($generic_entry)) && (array_key_exists($ldap_map["username"], $generic_entry))) { + if (in_array($generic_entry[$ldap_map["username"]][0], $usernames)) { unset($results[$key]); } } @@ -218,76 +235,78 @@ class LdapSync extends Command for ($i = 0; $i < $results['count']; $i++) { - $item = []; - $item['username'] = $results[$i][$ldap_result_username][0] ?? ''; - $item['employee_number'] = $results[$i][$ldap_result_emp_num][0] ?? ''; - $item['lastname'] = $results[$i][$ldap_result_last_name][0] ?? ''; - $item['firstname'] = $results[$i][$ldap_result_first_name][0] ?? ''; - $item['email'] = $results[$i][$ldap_result_email][0] ?? ''; - $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? ''; - $item['location_id'] = $results[$i]['location_id'] ?? ''; - $item['telephone'] = $results[$i][$ldap_result_phone][0] ?? ''; - $item['jobtitle'] = $results[$i][$ldap_result_jobtitle][0] ?? ''; - $item['country'] = $results[$i][$ldap_result_country][0] ?? ''; - $item['department'] = $results[$i][$ldap_result_dept][0] ?? ''; - $item['manager'] = $results[$i][$ldap_result_manager][0] ?? ''; - $item['location'] = $results[$i][$ldap_result_location][0] ?? ''; + $item = []; + $item['username'] = $results[$i][$ldap_map["username"]][0] ?? ''; + $item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? ''; + $item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? ''; + $item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? ''; + $item['email'] = $results[$i][$ldap_map["email"]][0] ?? ''; + $item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? ''; + $item['location_id'] = $results[$i]['location_id'] ?? ''; + $item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? ''; + $item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? ''; + $item['country'] = $results[$i][$ldap_map["country"]][0] ?? ''; + $item['department'] = $results[$i][$ldap_map["dept"]][0] ?? ''; + $item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? ''; + $item['location'] = $results[$i][$ldap_map["location"]][0] ?? ''; + $location = $default_location; //initially, set '$location' to the default_location (which may just be `null`) - // ONLY if you are using the "ldap_location" option *AND* you have an actual result - if ($ldap_result_location && $item['location']) { - $location = Location::firstOrCreate([ - 'name' => $item['location'], - ]); - } - $department = Department::firstOrCreate([ - 'name' => $item['department'], + // ONLY if you are using the "ldap_location" option *AND* you have an actual result + if ($ldap_map["location"] && $item['location']) { + $location = Location::firstOrCreate([ + 'name' => $item['location'], ]); + } + $department = Department::firstOrCreate([ + 'name' => $item['department'], + ]); - $user = User::where('username', $item['username'])->first(); - if ($user) { - // Updating an existing user. - $item['createorupdate'] = 'updated'; - } else { - // Creating a new user. - $user = new User; - $user->password = $user->noPassword(); - $user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below) - $item['createorupdate'] = 'created'; - } + $user = User::where('username', $item['username'])->first(); + if ($user) { + // Updating an existing user. + $item['createorupdate'] = 'updated'; + } else { + // Creating a new user. + $user = new User; + $user->password = $user->noPassword(); + $user->locale = app()->getLocale(); + $user->activated = 1; // newly created users can log in by default, unless AD's UAC is in use, or an active flag is set (below) + $item['createorupdate'] = 'created'; + } //If a sync option is not filled in on the LDAP settings don't populate the user field - if($ldap_result_username != null){ + if($ldap_map["username"] != null){ $user->username = $item['username']; } - if($ldap_result_last_name != null){ + if($ldap_map["last_name"] != null){ $user->last_name = $item['lastname']; } - if($ldap_result_first_name != null){ + if($ldap_map["first_name"] != null){ $user->first_name = $item['firstname']; } - if($ldap_result_emp_num != null){ + if($ldap_map["emp_num"] != null){ $user->employee_num = e($item['employee_number']); } - if($ldap_result_email != null){ + if($ldap_map["email"] != null){ $user->email = $item['email']; } - if($ldap_result_phone != null){ + if($ldap_map["phone"] != null){ $user->phone = $item['telephone']; } - if($ldap_result_jobtitle != null){ + if($ldap_map["jobtitle"] != null){ $user->jobtitle = $item['jobtitle']; } - if($ldap_result_country != null){ + if($ldap_map["country"] != null){ $user->country = $item['country']; } - if($ldap_result_dept != null){ + if($ldap_map["dept"] != null){ $user->department_id = $department->id; } - if($ldap_result_location != null){ - $user->location_id = $location ? $location->id : null; + if($ldap_map["location"] != null){ + $user->location_id = $location?->id; } - if($ldap_result_manager != null){ + if($ldap_map["manager"] != null){ if($item['manager'] != null) { // Check Cache first if (isset($manager_cache[$item['manager']])) { @@ -304,63 +323,76 @@ class LdapSync extends Command $ldap_manager = [ "count" => 1, 0 => [ - $ldap_result_username => [$item['manager']] + $ldap_map["username"] => [$item['manager']] ] ]; } - + + $add_manager_to_cache = true; if ($ldap_manager["count"] > 0) { + try { + // Get the Manager's username + // PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array. + $ldapManagerUsername = $ldap_manager[0][$ldap_map["username"]][0]; - // Get the Manager's username - // PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array. - $ldapManagerUsername = $ldap_manager[0][$ldap_result_username][0]; + // Get User from Manager username. + $ldap_manager = User::where('username', $ldapManagerUsername)->first(); - // Get User from Manager username. - $ldap_manager = User::where('username', $ldapManagerUsername)->first(); - - if ($ldap_manager && isset($ldap_manager->id)) { - // Link user to manager id. - $user->manager_id = $ldap_manager->id; + if ($ldap_manager && isset($ldap_manager->id)) { + // Link user to manager id. + $user->manager_id = $ldap_manager->id; + } + } catch (\Exception $e) { + $add_manager_to_cache = false; + \Log::warning('Handling ldap manager ' . $item['manager'] . ' caused an exception: ' . $e->getMessage() . '. Continuing synchronization.'); } } - $manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed' + if ($add_manager_to_cache) { + $manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed' + } } } } - // Sync activated state for Active Directory. - if ( !empty($ldap_result_active_flag)) { // IF we have an 'active' flag set.... - // ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them. - // (Specifically, we don't handle a value of '0.0' correctly) - $raw_value = @$results[$i][$ldap_result_active_flag][0]; - $filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); - $boolean_cast = (bool)$raw_value; - + // Sync activated state for Active Directory. + if (!empty($ldap_map["active_flag"])) { // IF we have an 'active' flag set.... + // ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them. + // (Specifically, we don't handle a value of '0.0' correctly) + $raw_value = @$results[$i][$ldap_map["active_flag"]][0]; + $filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + + $boolean_cast = (bool) $raw_value; + + if (Setting::getSettings()->ldap_invert_active_flag === 1) { + // Because ldap_active_flag is set, if filter_var is true or boolean_cast is true, then user is suspended + $user->activated = !($filter_var ?? $boolean_cast); + }else{ $user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast + } - } elseif (array_key_exists('useraccountcontrol', $results[$i]) ) { - // ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists, - // ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in + } elseif (array_key_exists('useraccountcontrol', $results[$i])) { + // ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists, + // ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in - /* The following is _probably_ the correct logic, but we can't use it because - some users may have been dependent upon the previous behavior, and this - could cause additional access to be available to users they don't want - to allow to log in. + /* The following is _probably_ the correct logic, but we can't use it because + some users may have been dependent upon the previous behavior, and this + could cause additional access to be available to users they don't want + to allow to log in. - $useraccountcontrol = $results[$i]['useraccountcontrol'][0]; - if( - // based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties - ($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT - !($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE - !($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT - ) { - $user->activated = 1; - } else { - $user->activated = 0; - } */ - $enabled_accounts = [ + $useraccountcontrol = $results[$i]['useraccountcontrol'][0]; + if( + // based on MS docs at: https://support.microsoft.com/en-us/help/305144/how-to-use-useraccountcontrol-to-manipulate-user-account-properties + ($useraccountcontrol & 0x200) && // is a NORMAL_ACCOUNT + !($useraccountcontrol & 0x02) && // *and* _not_ ACCOUNTDISABLE + !($useraccountcontrol & 0x10) // *and* _not_ LOCKOUT + ) { + $user->activated = 1; + } else { + $user->activated = 0; + } */ + $enabled_accounts = [ '512', // 0x200 NORMAL_ACCOUNT '544', // 0x220 NORMAL_ACCOUNT, PASSWD_NOTREQD '66048', // 0x10200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD @@ -373,44 +405,59 @@ class LdapSync extends Command '4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH '1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED '1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED, - ]; - $user->activated = (in_array($results[$i]['useraccountcontrol'][0], $enabled_accounts)) ? 1 : 0; + ]; + $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 - } /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active. - already-existing accounts will be however the administrator has set them */ + } /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active. + already-existing accounts will be however the administrator has set them */ - if ($item['ldap_location_override'] == true) { - $user->location_id = $item['location_id']; - } elseif ((isset($location)) && (! empty($location))) { - if ((is_array($location)) && (array_key_exists('id', $location))) { - $user->location_id = $location['id']; - } elseif (is_object($location)) { - $user->location_id = $location->id; + if ($item['ldap_location_override'] == true) { + $user->location_id = $item['location_id']; + } elseif ((isset($location)) && (!empty($location))) { + if ((is_array($location)) && (array_key_exists('id', $location))) { + $user->location_id = $location['id']; + } elseif (is_object($location)) { + $user->location_id = $location->id; //THIS is the magic line, this should do it. + } + } + // TODO - should we be NULLING locations if $location is really `null`, and that's what we came up with? + // will that conflict with any overriding setting that the user set? Like, if they moved someone from + // the 'null' location to somewhere, we wouldn't want to try to override that, right? + $location = null; + $user->ldap_import = 1; + + $errors = ''; + + if ($user->save()) { + $item['note'] = $item['createorupdate']; + $item['status'] = 'success'; + if ($item['createorupdate'] === 'created' && $ldap_default_group) { + // Check if the relationship already exists + if (!$user->groups()->where('group_id', $ldap_default_group)->exists()) { + $user->groups()->attach($ldap_default_group); } } - $location = null; - $user->ldap_import = 1; - - $errors = ''; - - if ($user->save()) { - $item['note'] = $item['createorupdate']; - $item['status'] = 'success'; - if ( $item['createorupdate'] === 'created' && $ldap_default_group) { - $user->groups()->attach($ldap_default_group); + + //updates assets location based on user's location + if ($user->wasChanged('location_id')) { + foreach ($user->assets as $asset) { + $asset->location_id = $user->location_id; + // TODO: somehow add note? "Asset Location Changed because of thing" + $asset->save(); } - - } else { - foreach ($user->getErrors()->getMessages() as $key => $err) { - $errors .= $err[0]; - } - $item['note'] = $errors; - $item['status'] = 'error'; } - array_push($summary, $item); + } else { + foreach ($user->getErrors()->getMessages() as $key => $err) { + $errors .= $err[0]; + } + $item['note'] = $errors; + $item['status'] = 'error'; + } + + array_push($summary, $item); } if ($this->option('summary')) { diff --git a/app/Console/Commands/MergeUsersByUsername.php b/app/Console/Commands/MergeUsersByUsername.php index 390942708d..0c5e966ab9 100644 --- a/app/Console/Commands/MergeUsersByUsername.php +++ b/app/Console/Commands/MergeUsersByUsername.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Events\UserMerged; use App\Models\User; use Carbon\Carbon; use Illuminate\Console\Command; @@ -51,7 +52,7 @@ class MergeUsersByUsername extends Command $bad_users = User::where('username', '=', trim($parts[0])) ->whereNull('deleted_at') - ->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations') + ->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations','uploads', 'acceptances') ->get(); @@ -105,10 +106,26 @@ class MergeUsersByUsername extends Command $managedLocation->save(); } + foreach ($bad_user->uploads as $upload) { + $this->info('Updating upload log record '.$upload->id.' to user '.$user->id); + $upload->item_id = $user->id; + $upload->save(); + } + + foreach ($bad_user->acceptances as $acceptance) { + $this->info('Updating acceptance log record '.$acceptance->id.' to user '.$user->id); + $acceptance->item_id = $user->id; + $acceptance->save(); + } + // Mark the user as deleted $this->info('Marking the user as deleted'); $bad_user->deleted_at = Carbon::now()->timestamp; $bad_user->save(); + + event(new UserMerged($bad_user, $user, null)); + + } } } diff --git a/app/Console/Commands/ObjectImportCommand.php b/app/Console/Commands/ObjectImportCommand.php index 8370e7c050..b79949521a 100644 --- a/app/Console/Commands/ObjectImportCommand.php +++ b/app/Console/Commands/ObjectImportCommand.php @@ -6,6 +6,7 @@ use Illuminate\Console\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Illuminate\Support\Facades\Log; +use Symfony\Component\Console\Helper\ProgressIndicator; ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M')); @@ -29,6 +30,11 @@ class ObjectImportCommand extends Command */ protected $description = 'Import Items from CSV'; + /** + * The progress indicator instance. + */ + protected ProgressIndicator $progressIndicator; + /** * Create a new command instance. * @@ -39,8 +45,6 @@ class ObjectImportCommand extends Command parent::__construct(); } - private $bar; - /** * Execute the console command. * @@ -48,12 +52,14 @@ class ObjectImportCommand extends Command */ public function handle() { + $this->progressIndicator = new ProgressIndicator($this->output); + $filename = $this->argument('filename'); $class = title_case($this->option('item-type')); $classString = "App\\Importer\\{$class}Importer"; $importer = new $classString($filename); $importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback']) - ->setUserId($this->option('user_id')) + ->setCreatedBy($this->option('user_id')) ->setUpdating($this->option('update')) ->setShouldNotify($this->option('send-welcome')) ->setUsernameFormat($this->option('username_format')); @@ -61,46 +67,25 @@ class ObjectImportCommand extends Command // This $logFile/useFiles() bit is currently broken, so commenting it out for now // $logFile = $this->option('logfile'); // Log::useFiles($logFile); - $this->comment('======= Importing Items from '.$filename.' ========='); + $this->progressIndicator->start('======= Importing Items from '.$filename.' ========='); + $importer->import(); - $this->bar = null; - - if (! empty($this->errors)) { - $this->comment('The following Errors were encountered.'); - foreach ($this->errors as $asset => $error) { - $this->comment('Error: Item: '.$asset.' failed validation: '.json_encode($error)); - } - } else { - $this->comment('All Items imported successfully!'); - } - $this->comment(''); + $this->progressIndicator->finish('Import finished.'); } - public function errorCallback($item, $field, $errorString) + public function errorCallback($item, $field, $error) { - $this->errors[$item->name][$field] = $errorString; + $this->output->write("\x0D\x1B[2K"); + + $this->warn('Error: Item: '.$item->name.' failed validation: '.json_encode($error)); } - public function progress($count) + public function progress($importedItemsCount) { - if (! $this->bar) { - $this->bar = $this->output->createProgressBar($count); - } - static $index = 0; - $index++; - if ($index < $count) { - $this->bar->advance(); - } else { - $this->bar->finish(); - } + $this->progressIndicator->advance(); } - // Tracks the current item for error messages - private $updating; - // An array of errors encountered while parsing - private $errors; - /** * Log a message to file, configurable by the --log-file parameter. * If a warning message is passed, we'll spit it to the console as well. diff --git a/app/Console/Commands/PaveIt.php b/app/Console/Commands/PaveIt.php index 09c846ea68..ef69d25a5b 100644 --- a/app/Console/Commands/PaveIt.php +++ b/app/Console/Commands/PaveIt.php @@ -51,8 +51,7 @@ class PaveIt extends Command } // List all the tables in the database so we don't have to worry about missing some as the app grows - $tables = DB::connection()->getDoctrineSchemaManager()->listTableNames(); - + $tables = Schema::getTables(); $except_tables = [ 'oauth_access_tokens', 'oauth_clients', @@ -74,7 +73,8 @@ class PaveIt extends Command } } - foreach ($tables as $table) { + foreach ($tables as $table_obj) { + $table = $table_obj['name']; if (in_array($table, $except_tables)) { $this->info($table. ' is SKIPPED.'); } else { diff --git a/app/Console/Commands/Purge.php b/app/Console/Commands/Purge.php index 7abd83c85c..1dd2aaa51d 100644 --- a/app/Console/Commands/Purge.php +++ b/app/Console/Commands/Purge.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\Models\Accessory; +use App\Models\Actionlog; use App\Models\Asset; use App\Models\AssetModel; use App\Models\Category; @@ -15,6 +16,8 @@ use App\Models\Statuslabel; use App\Models\Supplier; use App\Models\User; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; class Purge extends Command { @@ -141,6 +144,20 @@ class Purge extends Command $this->info($users->count().' users purged.'); $user_assoc = 0; foreach ($users as $user) { + + $rel_path = 'private_uploads/users'; + $filenames = Actionlog::where('action_type', 'uploaded') + ->where('item_id', $user->id) + ->pluck('filename'); + foreach($filenames as $filename) { + try { + if (Storage::exists($rel_path . '/' . $filename)) { + Storage::delete($rel_path . '/' . $filename); + } + } catch (\Exception $e) { + Log::info('An error occurred while deleting files: ' . $e->getMessage()); + } + } $this->info('- User "'.$user->username.'" deleted.'); $user_assoc += $user->userlog()->count(); $user->userlog()->forceDelete(); diff --git a/app/Console/Commands/RecryptFromMcrypt.php b/app/Console/Commands/RecryptFromMcrypt.php deleted file mode 100644 index 33c8ae65c9..0000000000 --- a/app/Console/Commands/RecryptFromMcrypt.php +++ /dev/null @@ -1,157 +0,0 @@ -error('ERROR: You do not have a LEGACY_APP_KEY set in your .env file. Please locate your old APP_KEY and ADD a line to your .env file like: LEGACY_APP_KEY=YOUR_OLD_APP_KEY'); - - return false; - } - - // Do some basic legacy app key length checks - if (strlen($legacy_key) == 32) { - $legacy_length_check = true; - } elseif (array_key_exists('1', $key_parts) && (strlen($key_parts[1]) == 44)) { - $legacy_key = base64_decode($key_parts[1], true); - $legacy_length_check = true; - } else { - $legacy_length_check = false; - } - - // Check that the app key is 32 characters - if ($legacy_length_check === true) { - $this->comment('INFO: Your LEGACY_APP_KEY looks correct. Okay to continue.'); - } else { - $this->error('ERROR: Your LEGACY_APP_KEY is not the correct length (32 characters or base64 followed by 44 characters for later versions). Please locate your old APP_KEY and use that as your LEGACY_APP_KEY in your .env file to continue.'); - - return false; - } - - $this->error('================================!!!! WARNING !!!!================================'); - $this->error('================================!!!! WARNING !!!!================================'); - $this->comment("This tool will attempt to decrypt your old Snipe-IT (mcrypt, now deprecated) encrypted data and re-encrypt it using OpenSSL. \n\nYou should only continue if you have backed up any and all old APP_KEYs and have backed up your data."); - - $force = ($this->option('force')) ? true : false; - - if ($force || ($this->confirm('Are you SURE you wish to continue?'))) { - $backup_file = 'backups/env-backups/'.'app_key-'.date('Y-m-d-gis'); - - try { - Storage::disk('local')->put($backup_file, 'APP_KEY: '.config('app.key')); - Storage::disk('local')->append($backup_file, 'LEGACY_APP_KEY: '.$legacy_key); - } catch (\Exception $e) { - $this->info('WARNING: Could not backup app keys'); - } - - if ($legacy_cipher) { - $mcrypter = new McryptEncrypter($legacy_key, $legacy_cipher); - } else { - $mcrypter = new McryptEncrypter($legacy_key); - } - $settings = Setting::getSettings(); - - if ($settings->ldap_pword == '') { - $this->comment('INFO: No LDAP password found. Skipping... '); - } else { - $decrypted_ldap_pword = $mcrypter->decrypt($settings->ldap_pword); - $settings->ldap_pword = Crypt::encrypt($decrypted_ldap_pword); - $settings->save(); - } - /** @var CustomField[] $custom_fields */ - $custom_fields = CustomField::where('field_encrypted', '=', 1)->get(); - $this->comment('INFO: Retrieving encrypted custom fields...'); - - $query = Asset::withTrashed(); - - foreach ($custom_fields as $custom_field) { - $this->comment('FIELD TO RECRYPT: '.$custom_field->name.' ('.$custom_field->db_column.')'); - $query->orWhereNotNull($custom_field->db_column); - } - - // Get all assets with a value in any of the fields that were encrypted - /** @var Asset[] $assets */ - $assets = $query->get(); - - $bar = $this->output->createProgressBar(count($assets)); - - foreach ($assets as $asset) { - foreach ($custom_fields as $encrypted_field) { - $columnName = $encrypted_field->db_column; - - // Make sure the value isn't null - if ($asset->{$columnName} != '') { - // Try to decrypt the payload using the legacy app key - try { - $decrypted_field = $mcrypter->decrypt($asset->{$columnName}); - $asset->{$columnName} = Crypt::encrypt($decrypted_field); - $this->comment($decrypted_field); - } catch (\Exception $e) { - $errors[] = ' - ERROR: Could not decrypt field ['.$encrypted_field->name.']: '.$e->getMessage(); - } - } - } - $asset->save(); - $bar->advance(); - } - - $bar->finish(); - - if (count($errors) > 0) { - $this->comment("\n\n"); - $this->error("The decrypter encountered some errors: \n"); - foreach ($errors as $error) { - $this->error($error); - } - } - } - } -} diff --git a/app/Console/Commands/RemoveExplicitEols.php b/app/Console/Commands/RemoveExplicitEols.php new file mode 100644 index 0000000000..f5164c2ee0 --- /dev/null +++ b/app/Console/Commands/RemoveExplicitEols.php @@ -0,0 +1,60 @@ +option('model_name') == 'all') { + $assets = Asset::all(); + $this->updateAssets($assets); + } else { + $assetModel = AssetModel::where('name', '=', $this->option('model_name'))->first(); + + if ($assetModel) { + $assets = Asset::where('model_id', '=', $assetModel->id)->get(); + $this->updateAssets($assets); + } else { + $this->error('Asset model not found'); + } + } + $endTime = microtime(true); + $executionTime = ($endTime - $startTime); + $this->info('Command executed in ' . round($executionTime, 2) . ' seconds.'); + } + + private function updateAssets($assets) + { + foreach ($assets as $asset) { + $asset->eol_explicit = 0; + $asset->asset_eol_date = null; + $asset->save(); + } + + $this->info($assets->count() . ' Assets updated successfully'); + } +} diff --git a/app/Console/Commands/ResetDemoSettings.php b/app/Console/Commands/ResetDemoSettings.php index 9ddade9efb..8eda65240b 100644 --- a/app/Console/Commands/ResetDemoSettings.php +++ b/app/Console/Commands/ResetDemoSettings.php @@ -50,12 +50,12 @@ class ResetDemoSettings extends Command $settings->alert_email = 'service@snipe-it.io'; $settings->login_note = 'Use `admin` / `password` to login to the demo.'; $settings->header_color = null; - $settings->barcode_type = 'QRCODE'; + $settings->label2_2d_type = 'QRCODE'; $settings->default_currency = 'USD'; $settings->brand = 2; $settings->ldap_enabled = 0; $settings->full_multiple_companies_support = 0; - $settings->alt_barcode = 'C128'; + $settings->label2_1d_type = 'C128'; $settings->skin = ''; $settings->email_domain = 'snipeitapp.com'; $settings->email_format = 'filastname'; @@ -65,7 +65,7 @@ class ResetDemoSettings extends Command $settings->thumbnail_max_h = '30'; $settings->locale = 'en-US'; $settings->version_footer = 'on'; - $settings->support_footer = null; + $settings->support_footer = 'on'; $settings->saml_enabled = '0'; $settings->saml_sp_x509cert = null; $settings->saml_idp_metadata = null; @@ -73,6 +73,7 @@ class ResetDemoSettings extends Command $settings->saml_forcelogin = '0'; $settings->saml_slo = null; $settings->saml_custom_settings = null; + $settings->default_avatar = 'default.png'; $settings->save(); diff --git a/app/Console/Commands/RestoreFromBackup.php b/app/Console/Commands/RestoreFromBackup.php index 1cc9b280e1..35e67a82e7 100644 --- a/app/Console/Commands/RestoreFromBackup.php +++ b/app/Console/Commands/RestoreFromBackup.php @@ -30,8 +30,11 @@ class SQLStreamer { public function parse_sql(string $line): string { // take into account the 'start of line or not' setting as an instance variable? // 'continuation' lines for a permitted statement are PERMITTED. + // remove *only* line-feeds & carriage-returns; helpful for regexes against lines from + // Windows dumps + $line = trim($line, "\r\n"); if($this->statement_is_permitted && $line[0] === ' ') { - return $line; + return $line . "\n"; //re-add the newline } $table_regex = '`?([a-zA-Z0-9_]+)`?'; @@ -42,8 +45,14 @@ class SQLStreamer { "/^(INSERT INTO )$table_regex(.*)$/" => false, "/^UNLOCK TABLES/" => false, // "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here? - "/^\\)[a-zA-Z0-9_= ]*;$/" => false - // ^^^^^^ that bit should *exit* the 'perimitted' black + "/^\\)[a-zA-Z0-9_= ]*;$/" => false, + // ^^^^^^ that bit should *exit* the 'permitted' block + "/^\\(.*\\)[,;]$/" => false, //older MySQL dump style with one set of values per line + /* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with + a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to + assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */ + "<^/\*!40101 SET NAMES '?[a-zA-Z0-9_-]+'? \*/;$>" => false, //using weird delimiters (<,>) for readability. allow quoted or unquoted charsets + "<^/\*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' \*/;$>" => false, //same, now handle zero-values ]; foreach($allowed_statements as $statement => $statechange) { @@ -67,7 +76,7 @@ class SQLStreamer { } //how do we *replace* the tablename? // print "RETURNING LINE: $line"; - return $line; + return $line . "\n"; //re-add newline } } // all that is not allowed is denied. @@ -85,7 +94,7 @@ class SQLStreamer { $parser->line_aware_piping(); // <----- THIS is doing the heavy lifting! $check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics? - //can't use 'users' because the 'accessories_users' table? + //can't use 'users' because the 'accessories_checkout' table? // can't use 'assets' because 'ver1_components_assets' foreach($check_tables as $check_table => $_ignore) { foreach ($parser->tablenames as $tablename => $_count) { @@ -164,7 +173,8 @@ class RestoreFromBackup extends Command {filename : The zip file to be migrated} {--no-progress : Don\'t show a progress bar} {--sanitize-guess-prefix : Guess and output the table-prefix needed to "sanitize" the SQL} - {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix}'; + {--sanitize-with-prefix= : "Sanitize" the SQL, using the passed-in table prefix (can be learned from --sanitize-guess-prefix). Pass as just \'--sanitize-with-prefix=\' to use no prefix} + {--sql-stdout-only : ONLY "Sanitize" the SQL and print it to stdout - useful for debugging - probably requires --sanitize-with-prefix= }'; /** * The console command description. @@ -239,6 +249,7 @@ class RestoreFromBackup extends Command 'storage/private_uploads/consumables', 'storage/private_uploads/eula-pdfs', 'storage/private_uploads/imports', + 'storage/private_uploads/locations', 'storage/private_uploads/licenses', 'storage/private_uploads/signatures', 'storage/private_uploads/users', @@ -279,6 +290,7 @@ class RestoreFromBackup extends Command $interesting_files = []; $boring_files = []; + $unsafe_files = []; for ($i = 0; $i < $za->numFiles; $i++) { $stat_results = $za->statIndex($i); @@ -318,7 +330,8 @@ class RestoreFromBackup extends Command } } $good_extensions = ['png', 'gif', 'jpg', 'svg', 'jpeg', 'doc', 'docx', 'pdf', 'txt', - 'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico',]; + 'zip', 'rar', 'xls', 'xlsx', 'lic', 'xml', 'rtf', 'webp', 'key', 'ico', 'avif' + ]; foreach (array_merge($private_files, $public_files) as $file) { $has_wildcard = (strpos($file, '*') !== false); if ($has_wildcard) { @@ -328,7 +341,9 @@ class RestoreFromBackup extends Command if ($last_pos !== false) { $extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION)); if (!in_array($extension, $good_extensions)) { - $this->warn('Potentially unsafe file ' . $raw_path . ' is being skipped'); + // gathering potentially unsafe files here to return at exit + $unsafe_files[] = $raw_path; + Log::debug('Potentially unsafe file '.$raw_path.' is being skipped'); $boring_files[] = $raw_path; continue 2; } @@ -362,7 +377,17 @@ class RestoreFromBackup extends Command if ($this->option('sanitize-guess-prefix')) { $prefix = SQLStreamer::guess_prefix($sql_contents); $this->line($prefix); - return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitze your SQL."); + + return $this->info("Re-run this command with '--sanitize-with-prefix=".$prefix."' to see an attempt to sanitize your SQL."); + } + + // If we're doing --sql-stdout-only, handle that now so we don't have to open pipes to mysql and all of that silliness + if ($this->option('sql-stdout-only')) { + $sql_importer = new SQLStreamer($sql_contents, STDOUT, $this->option('sanitize-with-prefix')); + $bytes_read = $sql_importer->line_aware_piping(); + return $this->warn("$bytes_read total bytes read"); + //TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL, + // which would be good for redirecting to a file, and not having to trim the last line off of it } //how to invoke the restore? @@ -466,6 +491,9 @@ class RestoreFromBackup extends Command $ugly_file_name = $za->statIndex($file_details['index'])['name']; $fp = $za->getStream($ugly_file_name); //$this->info("Weird problem, here are file details? ".print_r($file_details,true)); + if (!is_dir($file_details['dest'])) { + mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that + } $migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w'); while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) { fwrite($migrated_file, $buffer); @@ -483,6 +511,11 @@ class RestoreFromBackup extends Command } else { $this->info(count($interesting_files).' files were succesfully transferred'); } + if (count($unsafe_files) > 0) { + foreach ($unsafe_files as $unsafe_file) { + $this->warn('Potentially unsafe file '.$unsafe_file.' was skipped'); + } + } foreach ($boring_files as $boring_file) { $this->warn($boring_file.' was skipped.'); } diff --git a/app/Console/Commands/SendAcceptanceReminder.php b/app/Console/Commands/SendAcceptanceReminder.php new file mode 100644 index 0000000000..c1cf4ffda2 --- /dev/null +++ b/app/Console/Commands/SendAcceptanceReminder.php @@ -0,0 +1,111 @@ +where('checkoutable_type', 'App\Models\Asset') + ->whereHas('checkoutable', function($query) { + $query->where('accepted_at', null) + ->where('declined_at', null); + }) + ->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model', 'checkoutable.adminuser']) + ->get(); + + $count = 0; + $unacceptedAssetGroups = $pending + ->filter(function($acceptance) { + return $acceptance->checkoutable_type == 'App\Models\Asset'; + }) + ->map(function($acceptance) { + return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance]; + }) + ->groupBy(function($item) { + return $item['acceptance']->assignedTo ? $item['acceptance']->assignedTo->id : ''; + }); + $no_email_list= []; + + foreach($unacceptedAssetGroups as $unacceptedAssetGroup) { + // The [0] is weird, but it allows for the item_count to work and grabs the appropriate info for each user. + // Collapsing and flattening the collection doesn't work above. + $acceptance = $unacceptedAssetGroup[0]['acceptance']; + + $locale = $acceptance->assignedTo?->locale; + $email = $acceptance->assignedTo?->email; + + if(!$email){ + $no_email_list[] = [ + 'id' => $acceptance->assignedTo?->id, + 'name' => $acceptance->assignedTo?->present()->fullName(), + ]; + } else { + $count++; + } + $item_count = $unacceptedAssetGroup->count(); + + if ($locale && $email) { + Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count))->locale($locale)); + } elseif ($email) { + Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count))); + } + + } + + $this->info($count.' users notified.'); + $headers = ['ID', 'Name']; + $rows = []; + + foreach ($no_email_list as $user) { + $rows[] = [$user['id'], $user['name']]; + } + + if (!empty($rows)) { + $this->info("The following users do not have an email address:"); + $this->table($headers, $rows); + } + + return 0; + } + +} diff --git a/app/Console/Commands/SendExpirationAlerts.php b/app/Console/Commands/SendExpirationAlerts.php index 025ba2ce23..64aff907fb 100644 --- a/app/Console/Commands/SendExpirationAlerts.php +++ b/app/Console/Commands/SendExpirationAlerts.php @@ -2,13 +2,13 @@ namespace App\Console\Commands; +use App\Mail\ExpiringAssetsMail; +use App\Mail\ExpiringLicenseMail; use App\Models\Asset; use App\Models\License; -use App\Models\Recipients\AlertRecipient; use App\Models\Setting; -use App\Notifications\ExpiringAssetsNotification; -use App\Notifications\ExpiringLicenseNotification; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Mail; class SendExpirationAlerts extends Command { @@ -42,27 +42,28 @@ class SendExpirationAlerts extends Command public function handle() { $settings = Setting::getSettings(); - $threshold = $settings->alert_interval; + $alert_interval = $settings->alert_interval; if (($settings->alert_email != '') && ($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 AlertRecipient($item); - }); - + $recipients = collect(explode(',', $settings->alert_email)) + ->map(fn($item) => trim($item)) // Trim each email + ->filter(fn($item) => !empty($item)) + ->all(); // Expiring Assets - $assets = Asset::getExpiringWarrantee($threshold); + $assets = Asset::getExpiringWarrantee($alert_interval); + if ($assets->count() > 0) { - $this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $threshold])); - \Notification::send($recipients, new ExpiringAssetsNotification($assets, $threshold)); + $this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval])); + Mail::to($recipients)->send(new ExpiringAssetsMail($assets, $alert_interval)); } // Expiring licenses - $licenses = License::getExpiringLicenses($threshold); + $licenses = License::getExpiringLicenses($alert_interval); if ($licenses->count() > 0) { - $this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $threshold])); - \Notification::send($recipients, new ExpiringLicenseNotification($licenses, $threshold)); + $this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval])); + Mail::to($recipients)->send(new ExpiringLicenseMail($licenses, $alert_interval)); } } else { if ($settings->alert_email == '') { diff --git a/app/Console/Commands/SendUpcomingAuditReport.php b/app/Console/Commands/SendUpcomingAuditReport.php index 7c81a37d3a..854488adc6 100644 --- a/app/Console/Commands/SendUpcomingAuditReport.php +++ b/app/Console/Commands/SendUpcomingAuditReport.php @@ -2,13 +2,12 @@ namespace App\Console\Commands; +use App\Mail\SendUpcomingAuditMail; use App\Models\Asset; -use App\Models\Recipients\AlertRecipient; use App\Models\Setting; -use App\Notifications\SendUpcomingAuditNotification; use Carbon\Carbon; -use Illuminate\Support\Facades\DB; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Mail; class SendUpcomingAuditReport extends Command { @@ -48,19 +47,20 @@ class SendUpcomingAuditReport extends Command $today = Carbon::now(); $interval_date = $today->copy()->addDays($interval); - $assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get(); - $this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline'); + $assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get(); + $this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline'); - if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) { + if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) { // Send a rollup to the admin, if settings dictate - $recipients = collect(explode(',', $settings->alert_email))->map(function ($item) { - return new AlertRecipient($item); - }); + $recipients = collect(explode(',', $settings->alert_email)) + ->map(fn($item) => trim($item)) + ->filter(fn($item) => !empty($item)) + ->all(); - $this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email); - \Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days)); + $this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email); + Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days)); } } diff --git a/app/Console/Commands/TestLocationsFMCS.php b/app/Console/Commands/TestLocationsFMCS.php new file mode 100644 index 0000000000..70eface2de --- /dev/null +++ b/app/Console/Commands/TestLocationsFMCS.php @@ -0,0 +1,51 @@ +info('This script checks for company ID inconsistencies if Full Multiple Company Support with scoped locations will be used.'); + $this->info('This could take a few moments if have a very large dataset.'); + $this->newLine(); + + // if parameter location_id is set, only test this location + $location_id = null; + if ($this->option('location_id')) { + $location_id = $this->option('location_id'); + } + + $mismatched = Helper::test_locations_fmcs(true, $location_id); + $this->warn(trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched))); + $this->newLine(); + $this->info('Edit your locations to associate them with the correct company.'); + + $header = ['Type', 'ID', 'Name', 'Checkout Type', 'Company ID', 'Item Company', 'Item Location', 'Location Company', 'Location Company ID']; + sort($mismatched); + + $this->table($header, $mismatched); + + } + +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 8d512f303b..ca85459b67 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -5,6 +5,7 @@ namespace App\Console; use App\Console\Commands\ImportLocations; use App\Console\Commands\ReEncodeCustomFieldNames; use App\Console\Commands\RestoreDeletedUsers; +use App\Models\Setting; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -18,12 +19,14 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command('snipeit:inventory-alerts')->daily(); - $schedule->command('snipeit:expiring-alerts')->daily(); - $schedule->command('snipeit:expected-checkin')->daily(); + if(Setting::getSettings()?->alerts_enabled === 1) { + $schedule->command('snipeit:inventory-alerts')->daily(); + $schedule->command('snipeit:expiring-alerts')->daily(); + $schedule->command('snipeit:expected-checkin')->daily(); + $schedule->command('snipeit:upcoming-audits')->daily(); + } $schedule->command('snipeit:backup')->weekly(); $schedule->command('backup:clean')->daily(); - $schedule->command('snipeit:upcoming-audits')->daily(); $schedule->command('auth:clear-resets')->everyFifteenMinutes(); $schedule->command('saml:clear_expired_nonces')->weekly(); } diff --git a/app/Events/CheckoutableCheckedIn.php b/app/Events/CheckoutableCheckedIn.php index 48aed2a64d..fedbd49fbe 100644 --- a/app/Events/CheckoutableCheckedIn.php +++ b/app/Events/CheckoutableCheckedIn.php @@ -28,7 +28,7 @@ class CheckoutableCheckedIn $this->checkedOutTo = $checkedOutTo; $this->checkedInBy = $checkedInBy; $this->note = $note; - $this->action_date = $action_date ?? date('Y-m-d'); + $this->action_date = $action_date ?? date('Y-m-d H:i:s'); $this->originalValues = $originalValues; } } diff --git a/app/Events/UserMerged.php b/app/Events/UserMerged.php index b045fdef03..3a7f4d6a2c 100644 --- a/app/Events/UserMerged.php +++ b/app/Events/UserMerged.php @@ -15,7 +15,7 @@ class UserMerged * * @return void */ - public function __construct(User $from_user, User $to_user, User $admin) + public function __construct(User $from_user, User $to_user, ?User $admin) { $this->merged_from = $from_user; $this->merged_to = $to_user; diff --git a/app/Exceptions/AssetNotRequestable.php b/app/Exceptions/AssetNotRequestable.php new file mode 100644 index 0000000000..4067dda385 --- /dev/null +++ b/app/Exceptions/AssetNotRequestable.php @@ -0,0 +1,9 @@ +getStatusCode(); + // API throttle requests are handled in the RouteServiceProvider configureRateLimiting() method, so we don't need to handle them here switch ($e->getStatusCode()) { case '404': return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode . ' endpoint not found'), 404); - case '429': - return response()->json(Helper::formatStandardApiResponse('error', null, 'Too many requests'), 429); case '405': return response()->json(Helper::formatStandardApiResponse('error', null, 'Method not allowed'), 405); default: @@ -122,6 +122,37 @@ class Handler extends ExceptionHandler } + // This is traaaaash but it handles models that are not found while using route model binding :( + // The only alternative is to set that at *each* route, which is crazypants + if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) { + + // This gets the MVC model name from the exception and formats in a way that's less fugly + $model_name = strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel()))))); + $route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index'; + + // Sigh. + if ($route == 'assets.index') { + $route = 'hardware.index'; + } elseif ($route == 'reporttemplates.index') { + $route = 'reports/custom'; + } elseif ($route == 'assetmodels.index') { + $route = 'models.index'; + } elseif ($route == 'predefinedkits.index') { + $route = 'kits.index'; + } elseif ($route == 'assetmaintenances.index') { + $route = 'maintenances.index'; + } elseif ($route === 'licenseseats.index') { + $route = 'licenses.index'; + } elseif ($route === 'customfields.index') { + $route = 'fields.index'; + } elseif ($route === 'customfieldsets.index') { + $route = 'fields.index'; + } + + return redirect() + ->route($route) + ->withError(trans('general.generic_model_not_found', ['model' => $model_name])); + } if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404' )) { @@ -174,8 +205,9 @@ class Handler extends ExceptionHandler */ public function register() { + $this->reportable(function (Throwable $e) { // }); } -} \ No newline at end of file +} diff --git a/app/Exceptions/UserDoestExistException.php b/app/Exceptions/UserDoestExistException.php new file mode 100644 index 0000000000..e14069dca4 --- /dev/null +++ b/app/Exceptions/UserDoestExistException.php @@ -0,0 +1,10 @@ + 'mn-MN', // Mongolian 'ms' => 'ms-MY', // Malay 'nl' => 'nl-NL', // Dutch - 'no' => 'no-NO', // Norwegian + 'no' => 'nb-NO', // Norwegian Bokmål 'pl' => 'pl-PL', // Polish + 'pt' => 'pt-PT', // Portuguese 'ro' => 'ro-RO', // Romanian 'ru' => 'ru-RU', // Russian 'sk' => 'sk-SK', // Slovak @@ -707,6 +710,28 @@ class Helper return $randomString; } + /** + * A method to be used to handle deprecations notifications, currently handling MS Teams. more can be added when needed. + * + * + * @author [Godfrey Martinez] + * @since [v7.0.14] + * @return array + */ + public static function deprecationCheck() : array { + // The check and message that the user is still using the deprecated version + $deprecations = [ + 'ms_teams_deprecated' => array( + 'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows') && (Setting::getSettings()->webhook_selected === 'microsoft'), + 'message' => 'The Microsoft Teams webhook URL being used will be deprecated Dec 31st, 2025. Change webhook endpoint'), + ]; + + // if item of concern is being used and its being used with the deprecated values return the notification array. + if(Setting::getSettings()->webhook_selected === 'microsoft' && $deprecations['ms_teams_deprecated']['check']) { + return $deprecations; + } + return []; + } /** * This nasty little method gets the low inventory info for the @@ -720,7 +745,7 @@ class Helper { $alert_threshold = \App\Models\Setting::getSettings()->alert_threshold; $consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get(); - $accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get(); + $accessories = Accessory::withCount('checkouts as checkouts_count')->whereNotNull('min_amt')->get(); $components = Component::whereNotNull('min_amt')->get(); $asset_models = AssetModel::where('min_amt', '>', 0)->get(); $licenses = License::where('min_amt', '>', 0)->get(); @@ -748,7 +773,7 @@ class Helper } foreach ($accessories as $accessory) { - $avail = $accessory->qty - $accessory->users_count; + $avail = $accessory->qty - $accessory->checkouts_count; if ($avail < ($accessory->min_amt) + $alert_threshold) { if ($accessory->qty > 0) { $percent = number_format((($avail / $accessory->qty) * 100), 0); @@ -844,7 +869,7 @@ class Helper $filetype = @finfo_file($finfo, $file); finfo_close($finfo); - if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif')) { + if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif') || ($filetype == 'image/avif') || ($filetype == 'image/webp')) { return $filetype; } @@ -871,6 +896,12 @@ class Helper public static function selectedPermissionsArray($permissions, $selected_arr = []) { $permissions_arr = []; + if (is_array($permissions)) { + $permissions = json_encode($permissions); + } + + // Set default to empty JSON if the value is null + $permissions = json_decode($permissions ?? '{}', JSON_OBJECT_AS_ARRAY); foreach ($permissions as $permission) { for ($x = 0; $x < count($permission); $x++) { @@ -881,13 +912,13 @@ class Helper if (is_array($selected_arr)) { if (array_key_exists($permission_name, $selected_arr)) { - $permissions_arr[$permission_name] = $selected_arr[$permission_name]; + $permissions_arr[$permission_name] = (int) $selected_arr[$permission_name]; } else { - $permissions_arr[$permission_name] = '0'; + $permissions_arr[$permission_name] = 0; } } else { - $permissions_arr[$permission_name] = '0'; + $permissions_arr[$permission_name] = 0; } } } @@ -913,13 +944,22 @@ class Helper $rules = $class::rules(); foreach ($rules as $rule_name => $rule) { if ($rule_name == $field) { - if (strpos($rule, 'required') === false) { - return false; + if (is_array($rule)) { + if (in_array('required', $rule)) { + return true; + } else { + return false; + } } else { - return true; - } + if (strpos($rule, 'required') === false) { + return false; + } else { + return true; + } + } } } + return false; } /** @@ -1113,6 +1153,7 @@ class Helper 'png' => 'far fa-image', 'webp' => 'far fa-image', 'avif' => 'far fa-image', + 'svg' => 'fas fa-vector-square', // word 'doc' => 'far fa-file-word', 'docx' => 'far fa-file-word', @@ -1125,7 +1166,7 @@ class Helper //Text 'txt' => 'far fa-file-alt', 'rtf' => 'far fa-file-alt', - 'xml' => 'far fa-file-alt', + 'xml' => 'fas fa-code', // Misc 'pdf' => 'far fa-file-pdf', 'lic' => 'far fa-save', @@ -1138,41 +1179,7 @@ class Helper return 'far fa-file'; } - public static function show_file_inline($filename) - { - $extension = substr(strrchr($filename, '.'), 1); - if ($extension) { - switch ($extension) { - case 'jpg': - case 'jpeg': - case 'gif': - case 'png': - case 'webp': - case 'avif': - return true; - break; - default: - return false; - } - } - - return false; - } - - /** - * Generate a random encrypted password. - * - * @author Wes Hulette - * - * @since 5.0.0 - * - * @return string - */ - public static function generateEncyrptedPassword(): string - { - return bcrypt(self::generateUnencryptedPassword()); - } /** * Get a random unencrypted password. @@ -1306,25 +1313,24 @@ class Helper switch ($item) { case 'asset': return 'fas fa-barcode'; - break; case 'accessory': return 'fas fa-keyboard'; - break; case 'component': return 'fas fa-hdd'; - break; case 'consumable': return 'fas fa-tint'; - break; case 'license': return 'far fa-save'; - break; case 'location': return 'fas fa-map-marker-alt'; - break; case 'user': return 'fas fa-user'; - break; + case 'supplier': + return 'fa-solid fa-store'; + case 'manufacturer': + return 'fa-solid fa-building'; + case 'category': + return 'fa-solid fa-table-columns'; } } @@ -1440,7 +1446,6 @@ class Helper foreach (self::$language_map as $legacy => $new) { if ($language_code == $legacy) { - Log::debug('Current language is '.$legacy.', using '.$new.' instead'); return $new; } } @@ -1451,6 +1456,7 @@ class Helper public static function mapBackToLegacyLocale($new_locale = null) { + if (strlen($new_locale) <= 4) { return $new_locale; //"new locale" apparently wasn't quite so new } @@ -1458,44 +1464,193 @@ class Helper // This does a *reverse* search against our new language map array - given the value, find the *key* for it $legacy_locale = array_search($new_locale, self::$language_map); - if($legacy_locale !== false) { + if ($legacy_locale !== false) { return $legacy_locale; } return $new_locale; // better that you have some weird locale that doesn't fit into our mappings anywhere than 'void' } + public static function determineLanguageDirection() { + return in_array(app()->getLocale(), + [ + 'ar-SA', + 'fa-IR', + 'he-IL' + ]) ? 'rtl' : 'ltr'; + } - static public function getRedirectOption($request, $id, $table, $asset_id = null) + + static public function getRedirectOption($request, $id, $table, $item_id = null) { $redirect_option = Session::get('redirect_option'); $checkout_to_type = Session::get('checkout_to_type'); + $checkedInFrom = Session::get('checkedInFrom'); + $other_redirect = Session::get('other_redirect'); - //return to index - if ($redirect_option == '0') { + // return to index + if ($redirect_option == 'index') { switch ($table) { case "Assets": - return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.checkout.success')); + return route('hardware.index'); + case "Users": + return route('users.index'); + case "Licenses": + return route('licenses.index'); + case "Accessories": + return route('accessories.index'); + case "Components": + return route('components.index'); + case "Consumables": + return route('consumables.index'); } } - //return to thing being assigned - if ($redirect_option == '1') { + + // return to thing being assigned + if ($redirect_option == 'item') { switch ($table) { case "Assets": - return redirect()->route('hardware.show', $id ? $id : $asset_id)->with('success', trans('admin/hardware/message.checkout.success')); + return route('hardware.show', $id ?? $item_id); + case "Users": + return route('users.show', $id ?? $item_id); + case "Licenses": + return route('licenses.show', $id ?? $item_id); + case "Accessories": + return route('accessories.show', $id ?? $item_id); + case "Components": + return route('components.show', $id ?? $item_id); + case "Consumables": + return route('consumables.show', $id ?? $item_id); } } - //return to thing being assigned to - if ($redirect_option == '2') { + + // return to assignment target + if ($redirect_option == 'target') { switch ($checkout_to_type) { case 'user': - return redirect()->route('users.show', $request->assigned_user)->with('success', trans('admin/hardware/message.checkout.success')); + return route('users.show', $request->assigned_user ?? $checkedInFrom); case 'location': - return redirect()->route('locations.show', $request->assigned_location)->with('success', trans('admin/hardware/message.checkout.success')); + return route('locations.show', $request->assigned_location ?? $checkedInFrom); case 'asset': - return redirect()->route('hardware.show', $request->assigned_asset)->with('success', trans('admin/hardware/message.checkout.success')); + return route('hardware.show', $request->assigned_asset ?? $checkedInFrom); } } + + // return to somewhere else + if ($redirect_option == 'other_redirect') { + switch ($other_redirect) { + case 'audit': + return route('assets.audit.due'); + } + + } + return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error')); } + + /** + * Check for inconsistencies before activating scoped locations with FullMultipleCompanySupport + * If there are locations with different companies than related objects unforseen problems could arise + * + * @author T. Regnery + * @since 7.0 + * + * @param $artisan when false, bail out on first inconsistent entry + * @param $location_id when set, only test this specific location + * @param $new_company_id in case of updating a location, this is the newly requested company_id + * @return string [] + */ + static public function test_locations_fmcs($artisan, $location_id = null, $new_company_id = null) { + $mismatched = []; + + if ($location_id) { + $location = Location::find($location_id); + if ($location) { + $locations = collect([])->push(Location::find($location_id)); + } + } else { + $locations = Location::all(); + } + + // Bail out early if there are no locations + if ($locations->count() == 0) { + return []; + } + + foreach($locations as $location) { + // in case of an update of a single location, use the newly requested company_id + if ($new_company_id) { + $location_company = $new_company_id; + } else { + $location_company = $location->company_id; + } + + // Depending on the relationship, we must use different operations to retrieve the objects + $keywords_relation = [ + 'many' => [ + 'accessories', + 'assets', + 'assignedAccessories', + 'assignedAssets', + 'components', + 'consumables', + 'rtd_assets', + 'users', + ], + 'one' => [ + 'manager', + 'parent', + ]]; + + // In case of a single location, the children must be checked as well, because we don't walk every location + if ($location_id) { + $keywords_relation['many'][] = 'children'; + } + + foreach ($keywords_relation as $relation => $keywords) { + foreach($keywords as $keyword) { + if ($relation == 'many') { + $items = $location->{$keyword}->all(); + } else { + $items = collect([])->push($location->$keyword); + } + + $count = 0; + foreach ($items as $item) { + + + if ($item && $item->company_id != $location_company) { + + $mismatched[] = [ + class_basename(get_class($item)), + $item->id, + $item->name ?? $item->asset_tag ?? $item->serial ?? $item->username, + $item->assigned_type ? str_replace('App\\Models\\', '', $item->assigned_type) : null, + $item->company_id ?? null, + $item->company->name ?? null, +// $item->defaultLoc->id ?? null, +// $item->defaultLoc->name ?? null, +// $item->defaultLoc->company->id ?? null, +// $item->defaultLoc->company->name ?? null, + $item->location->name ?? null, + $item->location->company->name ?? null, + $location_company ?? null, + ]; + + $count++; + + // Bail early if this is not being run via artisan + if ((!$artisan) && ($count > 0)) { + return $mismatched; + } + + + + } + } + } + } + } + return $mismatched; + } } diff --git a/app/Helpers/IconHelper.php b/app/Helpers/IconHelper.php new file mode 100644 index 0000000000..7c8e2a7456 --- /dev/null +++ b/app/Helpers/IconHelper.php @@ -0,0 +1,196 @@ +download($filename); } } + + + /** + * This determines the file types that should be allowed inline and checks their fileinfo extension + * to determine that they are safe to display inline. + * + * @author [ + * @since v7.0.14 + * @param $file_with_path + * @return bool + */ + public static function allowSafeInline($file_with_path) { + + $allowed_inline = [ + 'pdf', + 'svg', + 'jpg', + 'gif', + 'svg', + 'avif', + 'webp', + 'png', + ]; + + + // The file exists and is allowed to be displayed inline + if (Storage::exists($file_with_path) && (in_array(pathinfo($file_with_path, PATHINFO_EXTENSION), $allowed_inline))) { + return true; + } + return false; + + } + + /** + * Decide whether to show the file inline or download it. + */ + public static function showOrDownloadFile($file, $filename) { + + $headers = []; + + if (request('inline') == 'true') { + + $headers = [ + 'Content-Disposition' => 'inline', + ]; + + // This is NOT allowed as inline - force it to be displayed as text in the browser + if (self::allowSafeInline($file) != true) { + $headers = array_merge($headers, ['Content-Type' => 'text/plain']); + } + } + + // Everything else seems okay, but the file doesn't exist on the server. + if (Storage::missing($file)) { + throw new FileNotFoundException(); + } + + return Storage::download($file, $filename, $headers); + + } } diff --git a/app/Http/Controllers/Accessories/AccessoriesController.php b/app/Http/Controllers/Accessories/AccessoriesController.php index 81d5d692c5..66a914e9a9 100755 --- a/app/Http/Controllers/Accessories/AccessoriesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesController.php @@ -7,10 +7,10 @@ use App\Http\Controllers\Controller; use App\Http\Requests\ImageUploadRequest; use App\Models\Accessory; use App\Models\Company; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; -use Redirect; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Log; /** This controller handles all actions related to Accessories for @@ -27,24 +27,19 @@ class AccessoriesController extends Controller * @author [A. Gianotto] [] * @see AccessoriesController::getDatatable() method that generates the JSON response * @since [v1.0] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View { $this->authorize('index', Accessory::class); - - return view('accessories/index'); + return view('accessories.index'); } /** * Returns a view with a form to create a new Accessory. * * @author [A. Gianotto] [] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { $this->authorize('create', Accessory::class); $category_type = 'accessory'; @@ -58,10 +53,8 @@ class AccessoriesController extends Controller * * @author [A. Gianotto] [] * @param ImageUploadRequest $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : RedirectResponse { $this->authorize(Accessory::class); @@ -80,16 +73,17 @@ class AccessoriesController extends Controller $accessory->purchase_date = request('purchase_date'); $accessory->purchase_cost = request('purchase_cost'); $accessory->qty = request('qty'); - $accessory->user_id = Auth::user()->id; + $accessory->created_by = auth()->id(); $accessory->supplier_id = request('supplier_id'); $accessory->notes = request('notes'); $accessory = $request->handleImages($accessory); + session()->put(['redirect_option' => $request->get('redirect_option')]); // Was the accessory created? if ($accessory->save()) { // Redirect to the new accessory page - return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.create.success')); + return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.create.success')); } return redirect()->back()->withInput()->withErrors($accessory->getErrors()); @@ -100,20 +94,11 @@ class AccessoriesController extends Controller * * @author [A. Gianotto] [] * @param int $accessoryId - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($accessoryId = null) + public function edit(Accessory $accessory) : View | RedirectResponse { - - if ($item = Accessory::find($accessoryId)) { - $this->authorize($item); - - return view('accessories/edit', compact('item'))->with('category_type', 'accessory'); - } - - return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); - + $this->authorize('update', Accessory::class); + return view('accessories.edit')->with('item', $accessory)->with('category_type', 'accessory'); } /** @@ -122,26 +107,18 @@ class AccessoriesController extends Controller * @author [J. Vinsmoke] * @param int $accessoryId * @since [v6.0] - * @return View */ - public function getClone($accessoryId = null) + public function getClone(Accessory $accessory) : View | RedirectResponse { $this->authorize('create', Accessory::class); - - // Check if the asset exists - if (is_null($accessory_to_clone = Accessory::find($accessoryId))) { - // Redirect to the asset management page - return redirect()->route('accessories.index') - ->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryId])); - } - - $accessory = clone $accessory_to_clone; - $accessory->id = null; - $accessory->location_id = null; + $cloned = clone $accessory; + $cloned->id = null; + $cloned->deleted_at = ''; + $cloned->location_id = null; return view('accessories/edit') - ->with('item', $accessory); + ->with('item', $cloned); } @@ -151,17 +128,15 @@ class AccessoriesController extends Controller * @author [A. Gianotto] [] * @param ImageUploadRequest $request * @param int $accessoryId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(ImageUploadRequest $request, $accessoryId = null) + public function update(ImageUploadRequest $request, Accessory $accessory) : RedirectResponse { - if ($accessory = Accessory::withCount('users as users_count')->find($accessoryId)) { + if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id)) { $this->authorize($accessory); $validator = Validator::make($request->all(), [ - "qty" => "required|numeric|min:$accessory->users_count" + "qty" => "required|numeric|min:$accessory->checkouts_count" ]); if ($validator->fails()) { @@ -189,9 +164,10 @@ class AccessoriesController extends Controller $accessory = $request->handleImages($accessory); - // Was the accessory updated? + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($accessory->save()) { - return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.update.success')); + return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.update.success')); } } else { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); @@ -205,20 +181,18 @@ class AccessoriesController extends Controller * * @author [A. Gianotto] [] * @param int $accessoryId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($accessoryId) + public function destroy($accessoryId) : RedirectResponse { - if (is_null($accessory = Accessory::find($accessoryId))) { + if (is_null($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId))) { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found')); } $this->authorize($accessory); - if ($accessory->hasUsers() > 0) { - return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()])); + if ($accessory->checkouts_count > 0) { + return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled')); } if ($accessory->image) { @@ -243,17 +217,14 @@ class AccessoriesController extends Controller * @param int $accessoryID * @see AccessoriesController::getDataView() method that generates the JSON response * @since [v1.0] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($accessoryID = null) + public function show(Accessory $accessory) : View | RedirectResponse { - $accessory = Accessory::withCount('users as users_count')->find($accessoryID); - $this->authorize('view', $accessory); - if (isset($accessory->id)) { - return view('accessories/view', compact('accessory')); - } + $accessory->loadCount('checkouts as checkouts_count'); - return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist', ['id' => $accessoryID])); + $accessory->load(['adminuser' => fn($query) => $query->withTrashed()]); + + $this->authorize('view', $accessory); + return view('accessories.view', compact('accessory')); } } diff --git a/app/Http/Controllers/Accessories/AccessoriesFilesController.php b/app/Http/Controllers/Accessories/AccessoriesFilesController.php index 75fa0b2f12..9dbb16d83a 100644 --- a/app/Http/Controllers/Accessories/AccessoriesFilesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesFilesController.php @@ -7,10 +7,13 @@ use App\Http\Controllers\Controller; use App\Http\Requests\UploadFileRequest; use App\Models\Actionlog; use App\Models\Accessory; -use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Storage; -use Symfony\Accessory\HttpFoundation\JsonResponse; use Illuminate\Support\Facades\Log; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Response; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; class AccessoriesFilesController extends Controller { @@ -19,20 +22,17 @@ class AccessoriesFilesController extends Controller * * @param UploadFileRequest $request * @param int $accessoryId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - *@author [A. Gianotto] [] + * @author [A. Gianotto] [] * @since [v1.0] * @todo Switch to using the AssetFileRequest form request validator. */ - public function store(UploadFileRequest $request, $accessoryId = null) + public function store(UploadFileRequest $request, $accessoryId = null) : RedirectResponse { if (config('app.lock_passwords')) { return redirect()->route('accessories.show', ['accessory'=>$accessoryId])->with('error', trans('general.feature_disabled')); } - $accessory = Accessory::find($accessoryId); if (isset($accessory->id)) { @@ -51,15 +51,15 @@ class AccessoriesFilesController extends Controller } - return redirect()->route('accessories.show', $accessory->id)->with('success', trans('general.file_upload_success')); + return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('success', trans('general.file_upload_success')); } - return redirect()->route('accessories.show', $accessory->id)->with('error', trans('general.no_files_uploaded')); + return redirect()->route('accessories.show', $accessory->id)->withFragment('files')->with('error', trans('general.no_files_uploaded')); } // Prepare the error message - return redirect()->route('accessories.index') - ->with('error', trans('general.file_does_not_exist')); + return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); + } /** @@ -69,35 +69,30 @@ class AccessoriesFilesController extends Controller * @since [v1.0] * @param int $accessoryId * @param int $fileId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($accessoryId = null, $fileId = null) + public function destroy($accessoryId = null, $fileId = null) : RedirectResponse { - $accessory = Accessory::find($accessoryId); - - // the asset is valid - if (isset($accessory->id)) { + if ($accessory = Accessory::find($accessoryId)) { $this->authorize('update', $accessory); - $log = Actionlog::find($fileId); - // Remove the file if one exists - if (Storage::exists('accessories/'.$log->filename)) { - try { - Storage::delete('accessories/'.$log->filename); - } catch (\Exception $e) { - Log::debug($e); + if ($log = Actionlog::find($fileId)) { + + if (Storage::exists('private_uploads/accessories/'.$log->filename)) { + try { + Storage::delete('private_uploads/accessories/' . $log->filename); + $log->delete(); + return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); + } catch (\Exception $e) { + Log::debug($e); + return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist')); + } } + } - - $log->delete(); - - return redirect()->back() - ->with('success', trans('admin/hardware/message.deletefile.success')); + return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found')); } - // Redirect to the licence management page - return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist')); + return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); } /** @@ -107,53 +102,31 @@ class AccessoriesFilesController extends Controller * @since [v1.4] * @param int $accessoryId * @param int $fileId - * @return \Symfony\Accessory\HttpFoundation\Response - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($accessoryId = null, $fileId = null, $download = true) + public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse { - Log::debug('Private filesystem is: '.config('filesystems.default')); - $accessory = Accessory::find($accessoryId); - - // the accessory is valid - if (isset($accessory->id)) { + if ($accessory = Accessory::find($accessoryId)) { $this->authorize('view', $accessory); $this->authorize('accessories.files', $accessory); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) { - return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found')); - } + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) { + $file = 'private_uploads/accessories/'.$log->filename; - $file = 'private_uploads/accessories/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); - } - - - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found')); } } + + return redirect()->route('accessories.show', ['accessory' => $accessory])->withFragment('files')->with('error', trans('general.log_record_not_found')); + } - return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); + return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); + } } diff --git a/app/Http/Controllers/Accessories/AccessoryCheckinController.php b/app/Http/Controllers/Accessories/AccessoryCheckinController.php index 29c6065057..ab24200d78 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckinController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckinController.php @@ -3,12 +3,14 @@ namespace App\Http\Controllers\Accessories; use App\Events\CheckoutableCheckedIn; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Accessory; -use App\Models\User; +use App\Models\AccessoryCheckout; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; class AccessoryCheckinController extends Controller { @@ -19,22 +21,25 @@ class AccessoryCheckinController extends Controller * @param Request $request * @param int $accessoryUserId * @param string $backto - * @return View - * @internal param int $accessoryId - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create($accessoryUserId = null, $backto = null) + public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse { - // Check if the accessory exists - if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { - // Redirect to the accessory management page with error + if (is_null($accessory_user = DB::table('accessories_checkout')->find($accessoryUserId))) { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found')); } $accessory = Accessory::find($accessory_user->accessory_id); + + //based on what the accessory is checked out to the target redirect option will be displayed accordingly. + $target_option = match ($accessory_user->assigned_type) { + 'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset')]), + 'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]), + default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]), + }; $this->authorize('checkin', $accessory); - return view('accessories/checkin', compact('accessory'))->with('backto', $backto); + return view('accessories/checkin', compact('accessory', 'target_option'))->with('backto', $backto); + } /** @@ -42,24 +47,25 @@ class AccessoryCheckinController extends Controller * * @uses Accessory::checkin_email() to determine if an email can and should be sent * @author [A. Gianotto] [] - * @param null $accessoryUserId + * @param null $accessoryCheckoutId * @param string $backto - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException - * @internal param int $accessoryId */ - public function store(Request $request, $accessoryUserId = null, $backto = null) + public function store(Request $request, $accessoryCheckoutId = null, $backto = null) : RedirectResponse { - // Check if the accessory exists - if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { - // Redirect to the accessory management page with error + if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryCheckoutId))) { return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); } - $accessory = Accessory::find($accessory_user->accessory_id); + $accessory = Accessory::find($accessory_checkout->accessory_id); + + session()->put('checkedInFrom', $accessory_checkout->assigned_to); + session()->put('checkout_to_type', match ($accessory_checkout->assigned_type) { + 'App\Models\User' => 'user', + 'App\Models\Location' => 'location', + 'App\Models\Asset' => 'asset', + }); $this->authorize('checkin', $accessory); - $checkin_hours = date('H:i:s'); $checkin_at = date('Y-m-d H:i:s'); if ($request->filled('checkin_at')) { @@ -67,12 +73,12 @@ class AccessoryCheckinController extends Controller } // Was the accessory updated? - if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) { - $return_to = e($accessory_user->assigned_to); + if ($accessory_checkout->delete()) { + event(new CheckoutableCheckedIn($accessory, $accessory_checkout->assignedTo, auth()->user(), $request->input('note'), $checkin_at)); - event(new CheckoutableCheckedIn($accessory, User::find($return_to), Auth::user(), $request->input('note'), $checkin_at)); + session()->put(['redirect_option' => $request->get('redirect_option')]); - return redirect()->route('accessories.show', $accessory->id)->with('success', trans('admin/accessories/message.checkin.success')); + return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories'))->with('success', trans('admin/accessories/message.checkin.success')); } // Redirect to the accessory management page with error return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error')); diff --git a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php index 679b8d4251..58ce787245 100644 --- a/app/Http/Controllers/Accessories/AccessoryCheckoutController.php +++ b/app/Http/Controllers/Accessories/AccessoryCheckoutController.php @@ -3,29 +3,34 @@ namespace App\Http\Controllers\Accessories; use App\Events\CheckoutableCheckedOut; +use App\Helpers\Helper; +use App\Http\Controllers\CheckInOutRequest; use App\Http\Controllers\Controller; +use App\Http\Requests\AccessoryCheckoutRequest; use App\Models\Accessory; +use App\Models\AccessoryCheckout; use App\Models\User; use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Input; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; class AccessoryCheckoutController extends Controller { + + use CheckInOutRequest; + /** * Return the form to checkout an Accessory to a user. * * @author [A. Gianotto] [] * @param int $id - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create($id) + public function create($id) : View | RedirectResponse { - if ($accessory = Accessory::withCount('users as users_count')->find($id)) { + if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) { $this->authorize('checkout', $accessory); @@ -58,46 +63,41 @@ class AccessoryCheckoutController extends Controller * * @author [A. Gianotto] [] * @param Request $request - * @param int $accessoryId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException + * @param Accessory $accessory */ - public function store(Request $request, $accessoryId) + public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse { - // Check if the accessory exists - if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) { - // Redirect to the accessory management page with error - return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.user_not_found')); - } - + $this->authorize('checkout', $accessory); - if (!$user = User::find($request->input('assigned_to'))) { - return redirect()->route('accessories.checkout.show', $accessory->id)->with('error', trans('admin/accessories/message.checkout.user_does_not_exist')); + $target = $this->determineCheckoutTarget(); + + $accessory->checkout_qty = $request->input('checkout_qty', 1); + + for ($i = 0; $i < $accessory->checkout_qty; $i++) { + + $accessory_checkout = new AccessoryCheckout([ + 'accessory_id' => $accessory->id, + 'created_at' => Carbon::now(), + 'assigned_to' => $target->id, + 'assigned_type' => $target::class, + 'note' => $request->input('note'), + ]); + + $accessory_checkout->created_by = auth()->id(); + $accessory_checkout->save(); } - // Make sure there is at least one available to checkout - if ($accessory->numRemaining() <= 0){ - return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable')); - } + event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note'))); + $request->request->add(['checkout_to_type' => request('checkout_to_type')]); + $request->request->add(['assigned_to' => $target->id]); - // Update the accessory data - $accessory->assigned_to = e($request->input('assigned_to')); + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); - $accessory->users()->attach($accessory->id, [ - 'accessory_id' => $accessory->id, - 'created_at' => Carbon::now(), - 'user_id' => Auth::id(), - 'assigned_to' => $request->get('assigned_to'), - 'note' => $request->input('note'), - ]); - - DB::table('accessories_users')->where('assigned_to', '=', $accessory->assigned_to)->where('accessory_id', '=', $accessory->id)->first(); - - event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note'))); // Redirect to the new accessory page - return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.checkout.success')); + return redirect()->to(Helper::getRedirectOption($request, $accessory->id, 'Accessories')) + ->with('success', trans('admin/accessories/message.checkout.success')); } } diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index fafb6aeda5..b20f7d97e8 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -28,20 +28,18 @@ use Illuminate\Support\Str; use App\Http\Controllers\SettingsController; use Barryvdh\DomPDF\Facade\Pdf; use Carbon\Carbon; -use phpDocumentor\Reflection\Types\Compound; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Log; class AcceptanceController extends Controller { /** * Show a listing of pending checkout acceptances for the current user - * - * @return View */ - public function index() + public function index() : View { - $acceptances = CheckoutAcceptance::forUser(Auth::user())->pending()->get(); - + $acceptances = CheckoutAcceptance::forUser(auth()->user())->pending()->get(); return view('account/accept.index', compact('acceptances')); } @@ -49,9 +47,8 @@ class AcceptanceController extends Controller * Shows a form to either accept or decline the checkout acceptance * * @param int $id - * @return mixed */ - public function create($id) + public function create($id) : View | RedirectResponse { $acceptance = CheckoutAcceptance::find($id); @@ -64,7 +61,7 @@ class AcceptanceController extends Controller return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted')); } - if (! $acceptance->isCheckedOutTo(Auth::user())) { + if (! $acceptance->isCheckedOutTo(auth()->user())) { return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted')); } @@ -80,9 +77,8 @@ class AcceptanceController extends Controller * * @param Request $request * @param int $id - * @return \Illuminate\Http\RedirectResponse */ - public function store(Request $request, $id) + public function store(Request $request, $id) : RedirectResponse { $acceptance = CheckoutAcceptance::find($id); @@ -94,7 +90,7 @@ class AcceptanceController extends Controller return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted')); } - if (! $acceptance->isCheckedOutTo(Auth::user())) { + if (! $acceptance->isCheckedOutTo(auth()->user())) { return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.incorrect_user_accepted')); } @@ -212,9 +208,12 @@ class AcceptanceController extends Controller */ $branding_settings = SettingsController::getPDFBranding(); - if (is_null($branding_settings->logo)){ - $path_logo = ""; - } else { + $path_logo = ""; + + // Check for the PDF logo path and use that, otherwise use the regular logo path + if (!is_null($branding_settings->acceptance_pdf_logo)) { + $path_logo = public_path() . '/uploads/' . $branding_settings->acceptance_pdf_logo; + } elseif (!is_null($branding_settings->logo)) { $path_logo = public_path() . '/uploads/' . $branding_settings->logo; } @@ -222,6 +221,7 @@ class AcceptanceController extends Controller 'item_tag' => $item->asset_tag, 'item_model' => $display_model, 'item_serial' => $item->serial, + 'item_status' => $item->assetstatus?->name, 'eula' => $item->getEula(), 'note' => $request->input('note'), 'check_out_date' => Carbon::parse($acceptance->created_at)->format('Y-m-d'), @@ -240,7 +240,11 @@ class AcceptanceController extends Controller } $acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note')); - $acceptance->notify(new AcceptanceAssetAcceptedNotification($data)); + try { + $acceptance->notify(new AcceptanceAssetAcceptedNotification($data)); + } catch (\Exception $e) { + Log::warning($e); + } event(new CheckoutAccepted($acceptance)); $return_msg = trans('admin/users/message.accepted'); @@ -312,6 +316,7 @@ class AcceptanceController extends Controller 'item_tag' => $item->asset_tag, 'item_model' => $display_model, 'item_serial' => $item->serial, + 'item_status' => $item->assetstatus?->name, 'note' => $request->input('note'), 'declined_date' => Carbon::parse($acceptance->declined_at)->format('Y-m-d'), 'signature' => ($sig_filename) ? storage_path() . '/private_uploads/signatures/' . $sig_filename : null, @@ -336,4 +341,5 @@ class AcceptanceController extends Controller return redirect()->to('account/accept')->with('success', $return_msg); } + } diff --git a/app/Http/Controllers/ActionlogController.php b/app/Http/Controllers/ActionlogController.php index 2d194b7ca6..057ca465bc 100644 --- a/app/Http/Controllers/ActionlogController.php +++ b/app/Http/Controllers/ActionlogController.php @@ -3,13 +3,14 @@ namespace App\Http\Controllers; use App\Helpers\Helper; -use App\Models\Actionlog; -use Illuminate\Support\Facades\Response; +use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; +use \Illuminate\Http\Response; +use Symfony\Component\HttpFoundation\BinaryFileResponse; class ActionlogController extends Controller { - public function displaySig($filename) + public function displaySig($filename) : RedirectResponse | Response | bool { // PHP doesn't let you handle file not found errors well with // file_get_contents, so we set the error reporting for just this class @@ -17,6 +18,7 @@ class ActionlogController extends Controller $disk = config('filesystems.default'); switch (config("filesystems.disks.$disk.driver")) { + case 's3': $file = 'private_uploads/signatures/'.$filename; return redirect()->away(Storage::disk($disk)->temporaryUrl($file, now()->addMinutes(5))); @@ -30,15 +32,24 @@ class ActionlogController extends Controller Log::warning('File '.$file.' not found'); return false; } else { - return Response::make($contents)->header('Content-Type', $filetype); + return response()->make($contents)->header('Content-Type', $filetype); } } } - public function getStoredEula($filename){ + public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse + { $this->authorize('view', \App\Models\Asset::class); - $file = config('app.private_uploads').'/eula-pdfs/'.$filename; - return Response::download($file); + if (config('filesystems.default') == 's3_private') { + return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5))); + } + + if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) { + return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename); + } + + return redirect()->back()->with('error', trans('general.file_does_not_exist')); + } } diff --git a/app/Http/Controllers/Api/AccessoriesController.php b/app/Http/Controllers/Api/AccessoriesController.php index eb74a9e0f0..90486b40f2 100644 --- a/app/Http/Controllers/Api/AccessoriesController.php +++ b/app/Http/Controllers/Api/AccessoriesController.php @@ -4,20 +4,27 @@ namespace App\Http\Controllers\Api; use App\Events\CheckoutableCheckedOut; use App\Helpers\Helper; +use App\Http\Controllers\CheckInOutRequest; use App\Http\Controllers\Controller; +use App\Http\Requests\AccessoryCheckoutRequest; +use App\Http\Requests\StoreAccessoryRequest; use App\Http\Transformers\AccessoriesTransformer; use App\Http\Transformers\SelectlistTransformer; use App\Models\Accessory; use App\Models\Company; use App\Models\User; +use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth; use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; +use App\Models\AccessoryCheckout; class AccessoriesController extends Controller { + use CheckInOutRequest; + /** * Display a listing of the resource. * @@ -45,20 +52,21 @@ class AccessoriesController extends Controller 'min_amt', 'company_id', 'notes', - 'users_count', + 'checkouts_count', 'qty', ]; - $accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier') - ->withCount('users as users_count'); + $accessories = Accessory::select('accessories.*') + ->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser') + ->withCount('checkouts as checkouts_count'); if ($request->filled('search')) { $accessories = $accessories->TextSearch($request->input('search')); } if ($request->filled('company_id')) { - $accessories->where('company_id', '=', $request->input('company_id')); + $accessories->where('accessories.company_id', '=', $request->input('company_id')); } if ($request->filled('category_id')) { @@ -104,7 +112,10 @@ class AccessoriesController extends Controller break; case 'supplier': $accessories = $accessories->OrderSupplier($order); - break; + break; + case 'created_by': + $accessories = $accessories->OrderByCreatedByName($order); + break; default: $accessories = $accessories->orderBy($column_sort, $order); break; @@ -120,14 +131,13 @@ class AccessoriesController extends Controller /** * Store a newly created resource in storage. * + * @param \App\Http\Requests\ImageUploadRequest $request + * @return \Illuminate\Http\JsonResponse * @author [A. Gianotto] [] * @since [v4.0] - * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(StoreAccessoryRequest $request) { - $this->authorize('create', Accessory::class); $accessory = new Accessory; $accessory->fill($request->all()); $accessory = $request->handleImages($accessory); @@ -143,15 +153,15 @@ class AccessoriesController extends Controller /** * Display the specified resource. * + * @param int $id + * @return array * @author [A. Gianotto] [] * @since [v4.0] - * @param int $id - * @return \Illuminate\Http\Response */ public function show($id) { $this->authorize('view', Accessory::class); - $accessory = Accessory::withCount('users as users_count')->findOrFail($id); + $accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id); return (new AccessoriesTransformer)->transformAccessory($accessory); } @@ -160,10 +170,10 @@ class AccessoriesController extends Controller /** * Display the specified resource. * + * @param int $id + * @return array * @author [A. Gianotto] [] * @since [v4.0] - * @param int $id - * @return \Illuminate\Http\Response */ public function accessory_detail($id) { @@ -175,47 +185,33 @@ class AccessoriesController extends Controller /** - * Display the specified resource. + * Get the list of checkouts for a specific accessory * * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response + * @return | array */ - public function checkedout($id, Request $request) + public function checkedout(Request $request, $id) { $this->authorize('view', Accessory::class); $accessory = Accessory::with('lastCheckout')->findOrFail($id); - if (! Company::isCurrentUserHasAccess($accessory)) { - return ['total' => 0, 'rows' => []]; - } - $offset = request('offset', 0); $limit = request('limit', 50); - $accessory_users = $accessory->users; - $total = $accessory_users->count(); - - if ($total < $offset) { - $offset = 0; - } - - $accessory_users = $accessory->users()->skip($offset)->take($limit)->get(); + // Total count of all checkouts for this asset + $accessory_checkouts = $accessory->checkouts(); + // Check for search text in the request if ($request->filled('search')) { - $accessory_users = $accessory->users() - ->where(function ($query) use ($request) { - $search_str = '%' . $request->input('search') . '%'; - $query->where('first_name', 'like', $search_str) - ->orWhere('last_name', 'like', $search_str) - ->orWhere('note', 'like', $search_str); - }) - ->get(); - $total = $accessory_users->count(); + $accessory_checkouts = $accessory_checkouts->TextSearch($request->input('search')); } - return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total); + $total = $accessory_checkouts->count(); + $accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get(); + + return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory_checkouts, $total); } @@ -226,7 +222,7 @@ class AccessoriesController extends Controller * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function update(ImageUploadRequest $request, $id) { @@ -248,16 +244,16 @@ class AccessoriesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function destroy($id) { $this->authorize('delete', Accessory::class); - $accessory = Accessory::findOrFail($id); + $accessory = Accessory::withCount('checkouts as checkouts_count')->findOrFail($id); $this->authorize($accessory); - if ($accessory->hasUsers() > 0) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()]))); + if ($accessory->checkouts_count > 0) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/general.delete_disabled'))); } $accessory->delete(); @@ -272,43 +268,34 @@ class AccessoriesController extends Controller * If Slack is enabled and/or asset acceptance is enabled, it will also * trigger a Slack message and send an email. * - * @author [A. Gianotto] [] * @param int $accessoryId - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\JsonResponse + * @author [A. Gianotto] [] */ - public function checkout(Request $request, $accessoryId) + public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory) { - // Check if the accessory exists - if (is_null($accessory = Accessory::withCount('users as users_count')->find($accessoryId))) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist'))); - } - $this->authorize('checkout', $accessory); + $target = $this->determineCheckoutTarget(); + $accessory->checkout_qty = $request->input('checkout_qty', 1); + for ($i = 0; $i < $accessory->checkout_qty; $i++) { - if ($accessory->numRemaining() > 0) { - - if (! $user = User::find($request->input('assigned_to'))) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist'))); - } - - // Update the accessory data - $accessory->assigned_to = $request->input('assigned_to'); - - $accessory->users()->attach($accessory->id, [ + $accessory_checkout = new AccessoryCheckout([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), - 'user_id' => Auth::id(), - 'assigned_to' => $request->get('assigned_to'), - 'note' => $request->get('note'), + 'assigned_to' => $target->id, + 'assigned_type' => $target::class, + 'note' => $request->input('note'), ]); - event(new CheckoutableCheckedOut($accessory, $user, Auth::user(), $request->input('note'))); - - return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success'))); + $accessory_checkout->created_by = auth()->id(); + $accessory_checkout->save(); } - return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining')); + // Set this value to be able to pass the qty through to the event + event(new CheckoutableCheckedOut($accessory, $target, auth()->user(), $request->input('note'))); + + return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkout.success'))); } @@ -325,29 +312,21 @@ class AccessoriesController extends Controller */ public function checkin(Request $request, $accessoryUserId = null) { - if (is_null($accessory_user = DB::table('accessories_users')->find($accessoryUserId))) { + if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryUserId))) { return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.does_not_exist'))); } - $accessory = Accessory::find($accessory_user->accessory_id); + $accessory = Accessory::find($accessory_checkout->accessory_id); $this->authorize('checkin', $accessory); - $logaction = $accessory->logCheckin(User::find($accessory_user->assigned_to), $request->input('note')); + $accessory->logCheckin(User::find($accessory_checkout->assigned_to), $request->input('note')); // Was the accessory updated? - if (DB::table('accessories_users')->where('id', '=', $accessory_user->id)->delete()) { - if (! is_null($accessory_user->assigned_to)) { - $user = User::find($accessory_user->assigned_to); + if ($accessory_checkout->delete()) { + if (! is_null($accessory_checkout->assigned_to)) { + $user = User::find($accessory_checkout->assigned_to); } - $data['log_id'] = $logaction->id; - $data['first_name'] = $user->first_name; - $data['last_name'] = $user->last_name; - $data['item_name'] = $accessory->name; - $data['checkin_date'] = $logaction->created_at; - $data['item_tag'] = ''; - $data['note'] = $logaction->note; - return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success'))); } diff --git a/app/Http/Controllers/Api/AssetFilesController.php b/app/Http/Controllers/Api/AssetFilesController.php index d8bc5e3841..fabe9ebbb3 100644 --- a/app/Http/Controllers/Api/AssetFilesController.php +++ b/app/Http/Controllers/Api/AssetFilesController.php @@ -3,29 +3,18 @@ namespace App\Http\Controllers\Api; use App\Helpers\StorageHelper; +use App\Http\Transformers\UploadedFilesTransformer; use Illuminate\Support\Facades\Storage; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Http\JsonResponse; -use Illuminate\Support\Facades\Crypt; -use Illuminate\Support\Facades\Gate; use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Asset; -use App\Models\AssetModel; use App\Models\Actionlog; -use \Illuminate\Support\Facades\Auth; -use Carbon\Carbon; -use DB; -use Illuminate\Http\Request; use App\Http\Requests\UploadFileRequest; +use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Log; -use Input; -use Paginator; -use Slack; -use Str; -use TCPDF; -use Validator; -use Route; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Illuminate\Http\Request; /** @@ -44,12 +33,10 @@ class AssetFilesController extends Controller * * @param \App\Http\Requests\UploadFileRequest $request * @param int $assetId - * @return \Illuminate\Http\JsonResponse - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v6.0] * @author [T. Scarsbrook] [] */ - public function store(UploadFileRequest $request, $assetId = null) + public function store(UploadFileRequest $request, $assetId = null) : JsonResponse { // Start by checking if the asset being acted upon exists if (! $asset = Asset::find($assetId)) { @@ -59,7 +46,7 @@ class AssetFilesController extends Controller // Make sure we are allowed to update this asset $this->authorize('update', $asset); - if ($request->hasFile('file')) { + if ($request->hasFile('file')) { // If the file storage directory doesn't exist; create it if (! Storage::exists('private_uploads/assets')) { Storage::makeDirectory('private_uploads/assets', 775); @@ -84,38 +71,40 @@ class AssetFilesController extends Controller * List the files for an asset. * * @param int $assetId - * @return \Illuminate\Http\JsonResponse - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v6.0] * @author [T. Scarsbrook] [] */ - public function list($assetId = null) + public function list(Asset $asset, Request $request) : JsonResponse | array { - // Start by checking if the asset being acted upon exists - if (! $asset = Asset::find($assetId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404); - } - - // the asset is valid - if (isset($asset->id)) { - $this->authorize('view', $asset); - // Check that there are some uploads on this asset that can be listed - if ($asset->uploads->count() > 0) { - $files = array(); - foreach ($asset->uploads as $upload) { - array_push($files, $upload); - } - // Give the list of files back to the user - return response()->json(Helper::formatStandardApiResponse('success', $files, trans('admin/hardware/message.upload.success'))); - } + $this->authorize('view', $asset); - // There are no files. - return response()->json(Helper::formatStandardApiResponse('success', array(), trans('admin/hardware/message.upload.success'))); + $allowed_columns = + [ + 'id', + 'filename', + 'eol', + 'notes', + 'created_at', + 'updated_at', + ]; + + $files = Actionlog::select('action_logs.*')->where('action_type', '=', 'uploaded')->where('item_type', '=', Asset::class)->where('item_id', '=', $asset->id); + + if ($request->filled('search')) { + $files = $files->TextSearch($request->input('search')); } - // Send back an error message - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.download.error')), 500); + // Make sure the offset and limit are actually integers and do not exceed system limits + $offset = ($request->input('offset') > $files->count()) ? $files->count() : abs($request->input('offset')); + $limit = app('api_limit_value'); + $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; + $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; + $files = $files->orderBy($sort, $order); + + $files = $files->skip($offset)->take($limit)->get(); + return (new UploadedFilesTransformer())->transformFiles($files, $files->count()); + } /** @@ -128,12 +117,8 @@ class AssetFilesController extends Controller * @since [v6.0] * @author [T. Scarsbrook] [] */ - public function show($assetId = null, $fileId = null) + public function show(Asset $asset, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse { - // Start by checking if the asset being acted upon exists - if (! $asset = Asset::find($assetId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404); - } // the asset is valid if (isset($asset->id)) { @@ -146,7 +131,7 @@ class AssetFilesController extends Controller // Form the full filename with path $file = 'private_uploads/assets/'.$log->filename; - \Log::debug('Checking for '.$file); + Log::debug('Checking for '.$file); if ($log->action_type == 'audit') { $file = 'private_uploads/audits/'.$log->filename; @@ -178,17 +163,11 @@ class AssetFilesController extends Controller * * @param int $assetId * @param int $fileId - * @return \Illuminate\Http\JsonResponse - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v6.0] * @author [T. Scarsbrook] [] */ - public function destroy($assetId = null, $fileId = null) + public function destroy(Asset $asset, $fileId = null) : JsonResponse { - // Start by checking if the asset being acted upon exists - if (! $asset = Asset::find($assetId)) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 404); - } $rel_path = 'private_uploads/assets'; @@ -198,12 +177,14 @@ class AssetFilesController extends Controller // Check for the file $log = Actionlog::find($fileId); - if ($log) { - // Check the file actually exists, and delete it - if (Storage::exists($rel_path.'/'.$log->filename)) { - Storage::delete($rel_path.'/'.$log->filename); - } - // Delete the record of the file + + if ($log) { + // Check the file actually exists, and delete it + if (Storage::exists($rel_path.'/'.$log->filename)) { + Storage::delete($rel_path.'/'.$log->filename); + } + + // Delete the record of the file $log->delete(); // All deleting done - notify the user of success diff --git a/app/Http/Controllers/Api/AssetMaintenancesController.php b/app/Http/Controllers/Api/AssetMaintenancesController.php index 1ecb3be257..b4e9b44196 100644 --- a/app/Http/Controllers/Api/AssetMaintenancesController.php +++ b/app/Http/Controllers/Api/AssetMaintenancesController.php @@ -9,9 +9,8 @@ use App\Models\Asset; use App\Models\AssetMaintenance; use App\Models\Company; use Illuminate\Support\Facades\Auth; -use Carbon\Carbon; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Input; +use Illuminate\Http\JsonResponse; /** * This controller handles all actions related to Asset Maintenance for @@ -22,7 +21,6 @@ use Illuminate\Support\Facades\Input; class AssetMaintenancesController extends Controller { - /** * Generates the JSON response for asset maintenances listing view. * @@ -30,14 +28,13 @@ class AssetMaintenancesController extends Controller * @author Vincent Sposato * @version v1.0 * @since [v1.8] - * @return string JSON */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', Asset::class); $maintenances = AssetMaintenance::select('asset_maintenances.*') - ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'admin'); + ->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser'); if ($request->filled('search')) { $maintenances = $maintenances->TextSearch($request->input('search')); @@ -51,6 +48,10 @@ class AssetMaintenancesController extends Controller $maintenances->where('asset_maintenances.supplier_id', '=', $request->input('supplier_id')); } + if ($request->filled('created_by')) { + $maintenances->where('asset_maintenances.created_by', '=', $request->input('created_by')); + } + if ($request->filled('asset_maintenance_type')) { $maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type')); } @@ -72,7 +73,7 @@ class AssetMaintenancesController extends Controller 'asset_tag', 'asset_name', 'serial', - 'user_id', + 'created_by', 'supplier', 'is_warranty', 'status_label', @@ -82,8 +83,8 @@ class AssetMaintenancesController extends Controller $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; switch ($sort) { - case 'user_id': - $maintenances = $maintenances->OrderAdmin($order); + case 'created_by': + $maintenances = $maintenances->OrderByCreatedBy($order); break; case 'supplier': $maintenances = $maintenances->OrderBySupplier($order); @@ -120,15 +121,14 @@ class AssetMaintenancesController extends Controller * @author Vincent Sposato * @version v1.0 * @since [v1.8] - * @return string JSON */ - public function store(Request $request) + public function store(Request $request) : JsonResponse | array { $this->authorize('update', Asset::class); // create a new model instance $maintenance = new AssetMaintenance(); $maintenance->fill($request->all()); - $maintenance->user_id = Auth::id(); + $maintenance->created_by = auth()->id(); // Was the asset maintenance created? if ($maintenance->save()) { @@ -148,9 +148,8 @@ class AssetMaintenancesController extends Controller * @param int $request * @version v1.0 * @since [v4.0] - * @return string JSON */ - public function update(Request $request, $id) + public function update(Request $request, $id) : JsonResponse | array { $this->authorize('update', Asset::class); @@ -186,17 +185,13 @@ class AssetMaintenancesController extends Controller * @param int $assetMaintenanceId * @version v1.0 * @since [v4.0] - * @return string JSON */ - public function destroy($assetMaintenanceId) + public function destroy($assetMaintenanceId) : JsonResponse | array { $this->authorize('update', Asset::class); // Check if the asset maintenance exists - $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); - if (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { - return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot delete a maintenance for that asset')); - } + $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); $assetMaintenance->delete(); @@ -212,9 +207,8 @@ class AssetMaintenancesController extends Controller * @param int $assetMaintenanceId * @version v1.0 * @since [v4.0] - * @return string JSON */ - public function show($assetMaintenanceId) + public function show($assetMaintenanceId) : JsonResponse | array { $this->authorize('view', Asset::class); $assetMaintenance = AssetMaintenance::findOrFail($assetMaintenanceId); diff --git a/app/Http/Controllers/Api/AssetModelFilesController.php b/app/Http/Controllers/Api/AssetModelFilesController.php new file mode 100644 index 0000000000..d0a4747bf4 --- /dev/null +++ b/app/Http/Controllers/Api/AssetModelFilesController.php @@ -0,0 +1,184 @@ + + * + * @version v1.0 + * @author [T. Scarsbrook] [] + */ +class AssetModelFilesController extends Controller +{ + /** + * Accepts a POST to upload a file to the server. + * + * @param \App\Http\Requests\UploadFileRequest $request + * @param int $assetModelId + * @since [v7.0.12] + * @author [r-xyz] + */ + public function store(UploadFileRequest $request, $assetModelId = null) : JsonResponse + { + // Start by checking if the asset being acted upon exists + if (! $assetModel = AssetModel::find($assetModelId)) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404); + } + + // Make sure we are allowed to update this asset + $this->authorize('update', $assetModel); + + if ($request->hasFile('file')) { + // If the file storage directory doesn't exist; create it + if (! Storage::exists('private_uploads/assetmodels')) { + Storage::makeDirectory('private_uploads/assetmodels', 775); + } + + // Loop over the attached files and add them to the asset + foreach ($request->file('file') as $file) { + $file_name = $request->handleFile('private_uploads/assetmodels/','model-'.$assetModel->id, $file); + + $assetModel->logUpload($file_name, e($request->get('notes'))); + } + + // All done - report success + return response()->json(Helper::formatStandardApiResponse('success', $assetModel, trans('admin/models/message.upload.success'))); + } + + // We only reach here if no files were included in the POST, so tell the user this + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.upload.nofiles')), 500); + } + + /** + * List the files for an asset. + * + * @param int $assetmodel + * @since [v7.0.12] + * @author [r-xyz] + */ + public function list($assetmodel_id) : JsonResponse | array + { + // Start by checking if the asset being acted upon exists + if (! $assetModel = AssetModel::find($assetmodel_id)) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404); + } + + $assetmodel = AssetModel::with('uploads')->find($assetmodel_id); + $this->authorize('view', $assetmodel); + return (new AssetModelsTransformer)->transformAssetModelFiles($assetmodel, $assetmodel->uploads()->count()); + } + + /** + * Check for permissions and display the file. + * + * @param int $assetModelId + * @param int $fileId + * @return \Illuminate\Http\JsonResponse + * @throws \Illuminate\Auth\Access\AuthorizationException + * @since [v7.0.12] + * @author [r-xyz] + */ + public function show($assetModelId = null, $fileId = null) : JsonResponse | StreamedResponse | Storage | StorageHelper | BinaryFileResponse + { + // Start by checking if the asset being acted upon exists + if (! $assetModel = AssetModel::find($assetModelId)) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404); + } + + // the asset is valid + if (isset($assetModel->id)) { + $this->authorize('view', $assetModel); + + // Check that the file being requested exists for the asset + if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $assetModel->id)->find($fileId)) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.no_match', ['id' => $fileId])), 404); + } + + // Form the full filename with path + $file = 'private_uploads/assetmodels/'.$log->filename; + Log::debug('Checking for '.$file); + + if ($log->action_type == 'audit') { + $file = 'private_uploads/audits/'.$log->filename; + } + + // Check the file actually exists on the filesystem + if (! Storage::exists($file)) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.does_not_exist', ['id' => $fileId])), 404); + } + + if (request('inline') == 'true') { + + $headers = [ + 'Content-Disposition' => 'inline', + ]; + + return Storage::download($file, $log->filename, $headers); + } + + return StorageHelper::downloader($file); + } + + // Send back an error message + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.download.error', ['id' => $fileId])), 500); + } + + /** + * Delete the associated file + * + * @param int $assetModelId + * @param int $fileId + * @since [v7.0.12] + * @author [r-xyz] + */ + public function destroy($assetModelId = null, $fileId = null) : JsonResponse + { + // Start by checking if the asset being acted upon exists + if (! $assetModel = AssetModel::find($assetModelId)) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.does_not_exist')), 404); + } + + $rel_path = 'private_uploads/assetmodels'; + + // the asset is valid + if (isset($assetModel->id)) { + $this->authorize('update', $assetModel); + + // Check for the file + $log = Actionlog::find($fileId); + if ($log) { + // Check the file actually exists, and delete it + if (Storage::exists($rel_path.'/'.$log->filename)) { + Storage::delete($rel_path.'/'.$log->filename); + } + // Delete the record of the file + $log->delete(); + + // All deleting done - notify the user of success + return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/models/message.deletefile.success')), 200); + } + + // The file doesn't seem to really exist, so report an error + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500); + } + + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/models/message.deletefile.error')), 500); + } +} diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index f1fa5369d1..3b6b287334 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -4,15 +4,16 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; use App\Http\Controllers\Controller; +use App\Http\Requests\StoreAssetModelRequest; use App\Http\Transformers\AssetModelsTransformer; use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\SelectlistTransformer; use App\Models\Asset; use App\Models\AssetModel; use Illuminate\Http\Request; -use App\Http\Requests\ImageUploadRequest; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Log; +use Illuminate\Http\JsonResponse; /** * This class controls all actions related to asset models for @@ -28,9 +29,8 @@ class AssetModelsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', AssetModel::class); $allowed_columns = @@ -48,36 +48,61 @@ class AssetModelsController extends Controller 'assets_count', 'category', 'fieldset', + 'deleted_at', + 'updated_at', ]; $assetmodels = AssetModel::select([ 'models.id', 'models.image', 'models.name', - 'model_number', - 'min_amt', - 'eol', - 'requestable', + 'models.model_number', + 'models.min_amt', + 'models.eol', + 'models.created_by', + 'models.requestable', 'models.notes', 'models.created_at', - 'category_id', - 'manufacturer_id', - 'depreciation_id', - 'fieldset_id', + 'models.category_id', + 'models.manufacturer_id', + 'models.depreciation_id', + 'models.fieldset_id', 'models.deleted_at', 'models.updated_at', ]) - ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues') + ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser') ->withCount('assets as assets_count'); if ($request->input('status')=='deleted') { $assetmodels->onlyTrashed(); } + if ($request->filled('name')) { + $assetmodels = $assetmodels->where('models.name', '=', $request->input('name')); + } + + if ($request->filled('model_number')) { + $assetmodels = $assetmodels->where('models.model_number', '=', $request->input('model_number')); + } + + if ($request->input('requestable') == 'true') { + $assetmodels = $assetmodels->where('models.requestable', '=', '1'); + } elseif ($request->input('requestable') == 'false') { + $assetmodels = $assetmodels->where('models.requestable', '=', '0'); + } + + if ($request->filled('notes')) { + $assetmodels = $assetmodels->where('models.notes', '=', $request->input('notes')); + } + if ($request->filled('category_id')) { $assetmodels = $assetmodels->where('models.category_id', '=', $request->input('category_id')); } + if ($request->filled('depreciation_id')) { + $assetmodels = $assetmodels->where('models.depreciation_id', '=', $request->input('depreciation_id')); + } + if ($request->filled('search')) { $assetmodels->TextSearch($request->input('search')); } @@ -89,7 +114,7 @@ class AssetModelsController extends Controller $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at'; - switch ($sort) { + switch ($request->input('sort')) { case 'manufacturer': $assetmodels->OrderManufacturer($order); break; @@ -99,6 +124,9 @@ class AssetModelsController extends Controller case 'fieldset': $assetmodels->OrderFieldset($order); break; + case 'created_by': + $assetmodels->OrderByCreatedByName($order); + break; default: $assetmodels->orderBy($sort, $order); break; @@ -116,10 +144,9 @@ class AssetModelsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response + * @param \App\Http\Requests\StoreAssetModelRequest $request */ - public function store(ImageUploadRequest $request) + public function store(StoreAssetModelRequest $request) : JsonResponse { $this->authorize('create', AssetModel::class); $assetmodel = new AssetModel; @@ -140,9 +167,8 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', AssetModel::class); $assetmodel = AssetModel::withCount('assets as assets_count')->findOrFail($id); @@ -156,9 +182,8 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function assets($id) + public function assets($id) : array { $this->authorize('view', AssetModel::class); $assets = Asset::where('model_id', '=', $id)->get(); @@ -176,7 +201,7 @@ class AssetModelsController extends Controller * @param int $id * @return \Illuminate\Http\Response */ - public function update(ImageUploadRequest $request, $id) + public function update(StoreAssetModelRequest $request, $id) : JsonResponse { $this->authorize('update', AssetModel::class); $assetmodel = AssetModel::findOrFail($id); @@ -209,9 +234,8 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', AssetModel::class); $assetmodel = AssetModel::findOrFail($id); @@ -241,7 +265,7 @@ class AssetModelsController extends Controller * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $this->authorize('view.selectlists'); diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index dc20588b75..57f188d92f 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -4,7 +4,9 @@ namespace App\Http\Controllers\Api; use App\Events\CheckoutableCheckedIn; use App\Http\Requests\StoreAssetRequest; +use App\Http\Requests\UpdateAssetRequest; use App\Http\Traits\MigratesLegacyAssetLocations; +use App\Models\AccessoryCheckout; use App\Models\CheckoutAcceptance; use App\Models\LicenseSeat; use Illuminate\Database\Eloquent\Builder; @@ -25,18 +27,14 @@ use App\Models\License; use App\Models\Location; use App\Models\Setting; use App\Models\User; -use \Illuminate\Support\Facades\Auth; use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Http\Request; -use App\Http\Requests\ImageUploadRequest; use Illuminate\Support\Facades\Log; -use Paginator; -use Slack; -use Str; -use TCPDF; -use Validator; use Illuminate\Support\Facades\Route; +use App\View\Label; +use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Validator; /** @@ -56,11 +54,15 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return \Illuminate\Http\JsonResponse */ - public function index(Request $request, $action = null, $upcoming_status = null) + public function index(Request $request, $action = null, $upcoming_status = null) : JsonResponse | array { + + // This handles the legacy audit endpoints :( + if ($action == 'audit') { + $action = 'audits'; + } $filter_non_deprecable_assets = false; /** @@ -80,10 +82,10 @@ class AssetsController extends Controller $this->authorize('reports.view'); } else { $transformer = 'App\Http\Transformers\AssetsTransformer'; - $this->authorize('index', Asset::class); + $this->authorize('index', Asset::class); } - - + + $settings = Setting::getSettings(); $allowed_columns = [ @@ -126,8 +128,20 @@ class AssetsController extends Controller } $assets = Asset::select('assets.*') - ->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo', - 'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users. + ->with( + 'model', + 'location', + 'assetstatus', + 'company', + 'defaultLoc', + 'assignedTo', + 'adminuser', + 'model.depreciation', + 'model.category', + 'model.manufacturer', + 'model.fieldset', + 'supplier' + ); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users. if ($filter_non_deprecable_assets) { @@ -159,8 +173,8 @@ class AssetsController extends Controller * Handle due and overdue audits and checkin dates */ switch ($action) { + // Audit (singular) is left over from earlier legacy APIs case 'audits': - switch ($upcoming_status) { case 'due': $assets->DueForAudit($settings); @@ -187,7 +201,7 @@ class AssetsController extends Controller break; } break; - } + } /** * End handling due and overdue audits and checkin dates @@ -265,7 +279,6 @@ class AssetsController extends Controller $join->on('status_alias.id', '=', 'assets.status_id'); }); } - } @@ -285,9 +298,15 @@ class AssetsController extends Controller if ($request->input('requestable') == 'true') { $assets->where('assets.requestable', '=', '1'); } - + if ($request->filled('model_id')) { - $assets->InModelList([$request->input('model_id')]); + // If model_id is already an array, just use it as-is + if (is_array($request->input('model_id'))) { + $assets->InModelList($request->input('model_id')); + } else { + // Otherwise, turn it into an array + $assets->InModelList([$request->input('model_id')]); + } } if ($request->filled('category_id')) { @@ -345,7 +364,7 @@ class AssetsController extends Controller $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - + switch ($sort_override) { case 'model': $assets->OrderModels($order); @@ -376,8 +395,32 @@ class AssetsController extends Controller case 'assigned_to': $assets->OrderAssigned($order); break; + case 'created_by': + $assets->OrderByCreatedByName($order); + break; default: - $assets->orderBy($column_sort, $order); + $numeric_sort = false; + + // Search through the custom fields array to see if we're sorting on a custom field + if (array_search($column_sort, $all_custom_fields->pluck('db_column')->toArray()) !== false) { + + // Check to see if this is a numeric field type + foreach ($all_custom_fields as $field) { + if (($field->db_column == $sort_override) && ($field->format == 'NUMERIC')) { + $numeric_sort = true; + break; + } + } + + // This may not work for all databases, but it works for MySQL + if ($numeric_sort) { + $assets->orderByRaw(DB::getTablePrefix() . 'assets.' . $sort_override . ' * 1 ' . $order); + } else { + $assets->orderBy($sort_override, $order); + } + } else { + $assets->orderBy($column_sort, $order); + } break; } @@ -388,23 +431,17 @@ class AssetsController extends Controller $total = $assets->count(); $assets = $assets->skip($offset)->take($limit)->get(); - + /** * Include additional associated relationships - */ + */ if ($request->input('components')) { $assets->loadMissing(['components' => function ($query) { $query->orderBy('created_at', 'desc'); }]); } - - - /** - * Here we're just determining which Transformer (via $transformer) to use based on the - * variables we set earlier on in this method - we default to AssetsTransformer. - */ return (new $transformer)->transformAssets($assets, $total, $request); } @@ -415,9 +452,8 @@ class AssetsController extends Controller * @param string $tag * @since [v4.2.1] * @author [A. Gianotto] [] - * @return \Illuminate\Http\JsonResponse */ - public function showByTag(Request $request, $tag) + public function showByTag(Request $request, $tag): JsonResponse | array { $this->authorize('index', Asset::class); $assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo'); @@ -439,12 +475,10 @@ class AssetsController extends Controller } else { return (new AssetsTransformer)->transformAssets($assets, $assets->count()); } - } // If there are 0 results, return the "no such asset" response return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); - } /** @@ -455,23 +489,39 @@ class AssetsController extends Controller * @since [v4.2.1] * @return \Illuminate\Http\JsonResponse */ - public function showBySerial(Request $request, $serial) + public function showBySerial(Request $request, $serial): JsonResponse | array { $this->authorize('index', Asset::class); - $assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo'); + $assets = Asset::where('serial', $serial)->with([ + 'assetstatus', + 'assignedTo', + 'company', + 'defaultLoc', + 'location', + 'model.category', + 'model.depreciation', + 'model.fieldset', + 'model.manufacturer', + 'supplier', + ]); // Check if they've passed ?deleted=true if ($request->input('deleted', 'false') == 'true') { $assets = $assets->withTrashed(); } - - if (($assets = $assets->get()) && ($assets->count()) > 0) { - return (new AssetsTransformer)->transformAssets($assets, $assets->count()); + + $offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value'); + $limit = app('api_limit_value'); + + $total = $assets->count(); + $assets = $assets->skip($offset)->take($limit)->get(); + + if (($assets) && ($assets->count()) > 0) { + return (new AssetsTransformer)->transformAssets($assets, $total); } // If there are 0 results, return the "no such asset" response return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); - } /** @@ -482,19 +532,20 @@ class AssetsController extends Controller * @since [v4.0] * @return \Illuminate\Http\JsonResponse */ - public function show(Request $request, $id) + public function show(Request $request, $id): JsonResponse | array { - if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed() - ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) { + if ($asset = Asset::with('assetstatus') + ->with('assignedTo')->withTrashed() + ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->find($id) + ) { $this->authorize('view', $asset); - return (new AssetsTransformer)->transformAsset($asset, $request->input('components') ); + return (new AssetsTransformer)->transformAsset($asset, $request->input('components')); } - - + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); } - public function licenses(Request $request, $id) + public function licenses(Request $request, $id): array { $this->authorize('view', Asset::class); $this->authorize('view', License::class); @@ -502,7 +553,7 @@ class AssetsController extends Controller $licenses = $asset->licenses()->get(); return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count()); - } + } /** @@ -511,9 +562,8 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer - * @return \Illuminate\Http\JsonResponse */ - public function selectlist(Request $request) + public function selectlist(Request $request): array { $assets = Asset::select([ @@ -524,7 +574,12 @@ class AssetsController extends Controller 'assets.assigned_to', 'assets.assigned_type', 'assets.status_id', - ])->with('model', 'assetstatus', 'assignedTo')->NotArchived(); + ])->with('model', 'assetstatus', 'assignedTo') + ->NotArchived(); + + if ((Setting::getSettings()->full_multiple_companies_support=='1') && ($request->filled('companyId'))) { + $assets->where('assets.company_id', $request->input('companyId')); + } if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') { $assets = $assets->RTD(); @@ -534,7 +589,6 @@ class AssetsController extends Controller $assets = $assets->AssignedSearch($request->input('search')); } - $assets = $assets->paginate(50); // Loop through and set some custom properties for the transformer to use. @@ -546,12 +600,12 @@ class AssetsController extends Controller $asset->use_text = $asset->present()->fullName; if (($asset->checkedOutToUser()) && ($asset->assigned)) { - $asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute(); + $asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute(); } if ($asset->assetstatus->getStatuslabelType() == 'pending') { - $asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')'; + $asset->use_text .= '(' . $asset->assetstatus->getStatuslabelType() . ')'; } $asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null; @@ -574,15 +628,15 @@ class AssetsController extends Controller $asset->model()->associate(AssetModel::find((int) $request->get('model_id'))); $asset->fill($request->validated()); - $asset->user_id = Auth::id(); + $asset->created_by = auth()->id(); /** - * this is here just legacy reasons. Api\AssetController - * used image_source once to allow encoded image uploads. - */ + * this is here just legacy reasons. Api\AssetController + * used image_source once to allow encoded image uploads. + */ if ($request->has('image_source')) { $request->offsetSet('image', $request->offsetGet('image_source')); - } + } $asset = $request->handleImages($asset); @@ -599,16 +653,16 @@ class AssetsController extends Controller // If input value is null, use custom field's default value if ($field_val == null) { - Log::debug('Field value for '.$field->db_column.' is null'); + Log::debug('Field value for ' . $field->db_column . ' is null'); $field_val = $field->defaultValue($request->get('model_id')); - Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id'))); + Log::debug('Use the default fieldset value of ' . $field->defaultValue($request->get('model_id'))); } // if the field is set to encrypted, make sure we encrypt the value if ($field->field_encrypted == '1') { Log::debug('This model field is encrypted in this fieldset.'); - if (Gate::allows('admin')) { + if (Gate::allows('assets.view.encrypted_custom_fields')) { // If input value is null, use custom field's default value if (($field_val == null) && ($request->has('model_id') != '')) { @@ -619,7 +673,7 @@ class AssetsController extends Controller } } if ($field->element == 'checkbox') { - if(is_array($field_val)) { + if (is_array($field_val)) { $field_val = implode(',', $field_val); } } @@ -638,7 +692,7 @@ class AssetsController extends Controller $target = Location::find(request('assigned_location')); } if (isset($target)) { - $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name'))); + $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset creation', e($request->get('name'))); } if ($asset->image) { @@ -647,7 +701,9 @@ class AssetsController extends Controller return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success'))); - return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success'))); + // below is what we want the _eventual_ return to look like - in a more standardized format. + // return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success'))); + } return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200); @@ -658,95 +714,91 @@ class AssetsController extends Controller * Accepts a POST request to update an asset * * @author [A. Gianotto] [] - * @param \App\Http\Requests\ImageUploadRequest $request * @since [v4.0] - * @return \Illuminate\Http\JsonResponse */ - public function update(ImageUploadRequest $request, $id) + public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse { - $this->authorize('update', Asset::class); + $asset->fill($request->validated()); - if ($asset = Asset::find($id)) { - $asset->fill($request->all()); - - ($request->filled('model_id')) ? - $asset->model()->associate(AssetModel::find($request->get('model_id'))) : null; - ($request->filled('rtd_location_id')) ? - $asset->location_id = $request->get('rtd_location_id') : ''; - ($request->filled('company_id')) ? - $asset->company_id = Company::getIdForCurrentUser($request->get('company_id')) : ''; - - ($request->filled('rtd_location_id')) ? - $asset->location_id = $request->get('rtd_location_id') : null; - - /** - * this is here just legacy reasons. Api\AssetController - * used image_source once to allow encoded image uploads. - */ - if ($request->has('image_source')) { - $request->offsetSet('image', $request->offsetGet('image_source')); - } - - $asset = $request->handleImages($asset); - $model = AssetModel::find($asset->model_id); - - // Update custom fields - $problems_updating_encrypted_custom_fields = false; - if (($model) && (isset($model->fieldset))) { - foreach ($model->fieldset->fields as $field) { - $field_val = $request->input($field->db_column, null); - - if ($request->has($field->db_column)) { - if ($field->element == 'checkbox') { - if(is_array($field_val)) { - $field_val = implode(',', $field_val); - } - } - if ($field->field_encrypted == '1') { - if (Gate::allows('admin')) { - $field_val = Crypt::encrypt($field_val); - } else { - $problems_updating_encrypted_custom_fields = true; - continue; - } - } - $asset->{$field->db_column} = $field_val; - } - } - } - - - if ($asset->save()) { - if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { - $location = $target->location_id; - } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) { - $location = $target->location_id; - - Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $id) - ->update(['location_id' => $target->location_id]); - } elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) { - $location = $target->id; - } - - if (isset($target)) { - $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location); - } - - if ($asset->image) { - $asset->image = $asset->getImageUrl(); - } - - if ($problems_updating_encrypted_custom_fields) { - return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning'))); - } else { - return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success'))); - } - } - - return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200); + if ($request->has('model_id')) { + $asset->model()->associate(AssetModel::find($request->validated()['model_id'])); + } + if ($request->has('company_id')) { + $asset->company_id = Company::getIdForCurrentUser($request->validated()['company_id']); + } + if ($request->has('rtd_location_id') && !$request->has('location_id')) { + $asset->location_id = $request->validated()['rtd_location_id']; + } + if ($request->input('last_audit_date')) { + $asset->last_audit_date = Carbon::parse($request->input('last_audit_date'))->startOfDay()->format('Y-m-d H:i:s'); } - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); + /** + * this is here just legacy reasons. Api\AssetController + * used image_source once to allow encoded image uploads. + */ + if ($request->has('image_source')) { + $request->offsetSet('image', $request->offsetGet('image_source')); + } + + $asset = $request->handleImages($asset); + $model = $asset->model; + + // Update custom fields + $problems_updating_encrypted_custom_fields = false; + if (($model) && (isset($model->fieldset))) { + foreach ($model->fieldset->fields as $field) { + $field_val = $request->input($field->db_column, null); + + if ($request->has($field->db_column)) { + if ($field->element == 'checkbox') { + if (is_array($field_val)) { + $field_val = implode(',', $field_val); + } + } + if ($field->field_encrypted == '1') { + if (Gate::allows('assets.view.encrypted_custom_fields')) { + $field_val = Crypt::encrypt($field_val); + } else { + $problems_updating_encrypted_custom_fields = true; + continue; + } + } + $asset->{$field->db_column} = $field_val; + } + } + } + if ($asset->save()) { + if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { + $location = $target->location_id; + } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) { + $location = $target->location_id; + + Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id) + ->update(['location_id' => $target->location_id]); + } elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) { + $location = $target->id; + } + + if (isset($target)) { + $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location); + } + + if ($asset->image) { + $asset->image = $asset->getImageUrl(); + } + + if ($problems_updating_encrypted_custom_fields) { + return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning'))); + // Below is the *correct* return since it uses the transformer, but we have to use the old, flat return for now until we can update Jamf2Snipe and Kanji2Snipe + // return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.encrypted_warning'))); + } else { + return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success'))); + // Below is the *correct* return since it uses the transformer, but we have to use the old, flat return for now until we can update Jamf2Snipe and Kanji2Snipe + /// return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.success'))); + } + } + return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200); } @@ -756,18 +808,24 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return \Illuminate\Http\JsonResponse */ - public function destroy($id) + public function destroy($id): JsonResponse { $this->authorize('delete', Asset::class); if ($asset = Asset::find($id)) { $this->authorize('delete', $asset); - DB::table('assets') - ->where('id', $asset->id) - ->update(['assigned_to' => null]); + if ($asset->assignedTo) { + + $target = $asset->assignedTo; + $checkin_at = date('Y-m-d H:i:s'); + $originalValues = $asset->getRawOriginal(); + event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues)); + DB::table('assets') + ->where('id', $asset->id) + ->update(['assigned_to' => null]); + } $asset->delete(); @@ -777,7 +835,7 @@ class AssetsController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); } - + /** * Restore a soft-deleted asset. @@ -785,9 +843,8 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v5.1.18] - * @return \Illuminate\Http\JsonResponse */ - public function restore(Request $request, $assetId = null) + public function restore(Request $request, $assetId = null): JsonResponse { if ($asset = Asset::withTrashed()->find($assetId)) { @@ -806,7 +863,6 @@ class AssetsController extends Controller } return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); - } /** @@ -815,9 +871,8 @@ class AssetsController extends Controller * @author [N. Butler] * @param string $tag * @since [v6.0.5] - * @return \Illuminate\Http\JsonResponse */ - public function checkoutByTag(AssetCheckoutRequest $request, $tag) + public function checkoutByTag(AssetCheckoutRequest $request, $tag): JsonResponse { if ($asset = Asset::where('asset_tag', $tag)->first()) { return $this->checkout($request, $asset->id); @@ -831,15 +886,14 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return \Illuminate\Http\JsonResponse */ - public function checkout(AssetCheckoutRequest $request, $asset_id) + public function checkout(AssetCheckoutRequest $request, $asset_id): JsonResponse { $this->authorize('checkout', Asset::class); $asset = Asset::findOrFail($asset_id); if (! $asset->availableForCheckout()) { - return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available'))); + return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available'))); } $this->authorize('checkout', $asset); @@ -856,14 +910,12 @@ class AssetsController extends Controller $asset->location_id = ($target) ? $target->id : ''; $error_payload['target_id'] = $request->input('assigned_location'); $error_payload['target_type'] = 'location'; - } elseif (request('checkout_to_type') == 'asset') { $target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset')); // Override with the asset's location_id if it has one $asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : ''; $error_payload['target_id'] = $request->input('assigned_asset'); $error_payload['target_type'] = 'asset'; - } elseif (request('checkout_to_type') == 'user') { // Fetch the target and set the asset's new location_id $target = User::find(request('assigned_user')); @@ -877,7 +929,7 @@ class AssetsController extends Controller } if (! isset($target)) { - return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.')); + return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset ' . e($asset->asset_tag) . ' is invalid - ' . $error_payload['target_type'] . ' does not exist.')); } $checkout_at = request('checkout_at', date('Y-m-d H:i:s')); @@ -891,15 +943,15 @@ class AssetsController extends Controller // TODO: Follow up here. WTF. Commented out for now. -// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) { -// $asset->location_id = $target->rtd_location_id; -// } + // if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) { + // $asset->location_id = $target->rtd_location_id; + // } - if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) { - return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success'))); + if ($asset->checkOut($target, auth()->user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) { + return response()->json(Helper::formatStandardApiResponse('success', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.success'))); } - return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error'))); + return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.error'))); } @@ -909,9 +961,8 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v4.0] - * @return \Illuminate\Http\JsonResponse */ - public function checkin(Request $request, $asset_id) + public function checkin(Request $request, $asset_id): JsonResponse { $asset = Asset::with('model')->findOrFail($asset_id); $this->authorize('checkin', $asset); @@ -919,7 +970,7 @@ class AssetsController extends Controller $target = $asset->assignedTo; if (is_null($target)) { return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset_tag'=> e($asset->asset_tag), + 'asset_tag' => e($asset->asset_tag), 'model' => e($asset->model->name), 'model_number' => e($asset->model->model_number) ], trans('admin/hardware/message.checkin.already_checked_in'))); @@ -942,16 +993,16 @@ class AssetsController extends Controller if ($request->filled('location_id')) { $asset->location_id = $request->input('location_id'); - if ($request->input('update_default_location')){ + if ($request->input('update_default_location')) { $asset->rtd_location_id = $request->input('location_id'); } } - if ($request->has('status_id')) { + if ($request->filled('status_id')) { $asset->status_id = $request->input('status_id'); } - - $checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s'); + + $checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at') . ' ' . date('H:i:s') : date('Y-m-d H:i:s'); $originalValues = $asset->getRawOriginal(); if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) { @@ -969,23 +1020,24 @@ class AssetsController extends Controller [Asset::class], function (Builder $query) use ($asset) { $query->where('id', $asset->id); - }) + } + ) ->get() ->map(function ($acceptance) { $acceptance->delete(); }); if ($asset->save()) { - event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues)); + event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues)); return response()->json(Helper::formatStandardApiResponse('success', [ - 'asset_tag'=> e($asset->asset_tag), + 'asset_tag' => e($asset->asset_tag), 'model' => e($asset->model->name), 'model_number' => e($asset->model->model_number) ], trans('admin/hardware/message.checkin.success'))); } - return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error'))); + return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkin.error'))); } /** @@ -993,12 +1045,11 @@ class AssetsController extends Controller * * @author [A. Janes] [] * @since [v6.0] - * @return \Illuminate\Http\JsonResponse */ - public function checkinByTag(Request $request, $tag = null) + public function checkinByTag(Request $request, $tag = null): JsonResponse { $this->authorize('checkin', Asset::class); - if(null == $tag && null !== ($request->input('asset_tag'))) { + if (null == $tag && null !== ($request->input('asset_tag'))) { $tag = $request->input('asset_tag'); } $asset = Asset::where('asset_tag', $tag)->first(); @@ -1008,8 +1059,8 @@ class AssetsController extends Controller } return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset'=> e($tag) - ], 'Asset with tag '.e($tag).' not found')); + 'asset' => e($tag) + ], 'Asset with tag ' . e($tag) . ' not found')); } @@ -1019,9 +1070,8 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $id * @since [v4.0] - * @return \Illuminate\Http\JsonResponse */ - public function audit(Request $request) + public function audit(Request $request, Asset $asset): JsonResponse { $this->authorize('audit', Asset::class); @@ -1029,36 +1079,15 @@ class AssetsController extends Controller $settings = Setting::getSettings(); $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); - // No tag passed - return an error - if (!$request->filled('asset_tag')) { - return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset_tag'=> '', - 'error'=> trans('admin/hardware/message.no_tag'), - ], trans('admin/hardware/message.no_tag')), 200); + // Allow the asset tag to be passed in the payload (legacy method) + if ($request->filled('asset_tag')) { + $asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first(); } - - $asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first(); - - if ($asset) { - /** - * Even though we do a save() further down, we don't want to log this as a "normal" asset update, - * which would trigger the Asset Observer and would log an asset *update* log entry (because the - * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to - * the audit log entry we're creating through this controller. - * - * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass - * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher() - * will bypass normal model-level validation that's usually handled at the observer ) - * - * We handle validation on the save() by checking if the asset is valid via the ->isValid() method, - * which manually invokes Watson Validating to make sure the asset's model is valid. - * - * @see \App\Observers\AssetObserver::updating() - */ - $asset->unsetEventDispatcher(); + $originalValues = $asset->getRawOriginal(); + $asset->next_audit_date = $dt; if ($request->filled('next_audit_date')) { @@ -1073,35 +1102,88 @@ class AssetsController extends Controller $asset->last_audit_date = date('Y-m-d H:i:s'); + // Set up the payload for re-display in the API response + $payload = [ + 'id' => $asset->id, + 'asset_tag' => $asset->asset_tag, + 'note' => $request->input('note'), + 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date), + ]; + + + /** + * Update custom fields in the database. + * Validation for these fields is handled through the AssetRequest form request + * $model = AssetModel::find($request->get('model_id')); + */ + if (($asset->model) && ($asset->model->fieldset)) { + $payload['custom_fields'] = []; + foreach ($asset->model->fieldset->fields as $field) { + if (($field->display_audit=='1') && ($request->has($field->db_column))) { + if ($field->field_encrypted == '1') { + if (Gate::allows('assets.view.encrypted_custom_fields')) { + if (is_array($request->input($field->db_column))) { + $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); + } else { + $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); + } + } + } else { + if (is_array($request->input($field->db_column))) { + $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); + } else { + $asset->{$field->db_column} = $request->input($field->db_column); + } + } + $payload['custom_fields'][$field->db_column] = $request->input($field->db_column); + } + + } + } + + // Invoke the validation to see if the audit will complete successfully + $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); + + // Validate the rest of the data before we turn off the event dispatcher + if ($asset->isInvalid()) { + return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag' => $asset->asset_tag], $asset->getErrors())); + } + + + /** + * Even though we do a save() further down, we don't want to log this as a "normal" asset update, + * which would trigger the Asset Observer and would log an asset *update* log entry (because the + * de-normed fields like next_audit_date on the asset itself will change on save()) *in addition* to + * the audit log entry we're creating through this controller. + * + * To prevent this double-logging (one for update and one for audit), we skip the observer and bypass + * that de-normed update log entry by using unsetEventDispatcher(), BUT invoking unsetEventDispatcher() + * will bypass normal model-level validation that's usually handled at the observer) + * + * We handle validation on the save() by checking if the asset is valid via the ->isValid() method, + * which manually invokes Watson Validating to make sure the asset's model is valid. + * + * @see \App\Observers\AssetObserver::updating() + * @see \App\Models\Asset::save() + */ + + $asset->unsetEventDispatcher(); + + /** * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly. * We have to invoke this manually because of the unsetEventDispatcher() above.) */ if ($asset->isValid() && $asset->save()) { - $asset->logAudit(request('note'), request('location_id')); - - return response()->json(Helper::formatStandardApiResponse('success', [ - 'asset_tag'=> e($asset->asset_tag), - 'note'=> e($request->input('note')), - 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date), - ], trans('admin/hardware/message.audit.success'))); + $asset->logAudit(request('note'), request('location_id'), null, $originalValues); + return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/hardware/message.audit.success'))); } - // Asset failed validation or was not able to be saved - return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset_tag'=> e($asset->asset_tag), - 'error'=> $asset->getErrors()->first(), - ], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200); - } // No matching asset for the asset tag that was passed. - return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset_tag'=> e($request->input('asset_tag')), - 'error'=> trans('admin/hardware/message.audit.error'), - ], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200); - + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); } @@ -1112,9 +1194,8 @@ class AssetsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return \Illuminate\Http\JsonResponse */ - public function requestable(Request $request) + public function requestable(Request $request): JsonResponse | array { $this->authorize('viewRequestable', Asset::class); @@ -1135,8 +1216,18 @@ class AssetsController extends Controller } $assets = Asset::select('assets.*') - ->with('location', 'assetstatus', 'assetlog', 'company','assignedTo', - 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests'); + ->with( + 'location', + 'assetstatus', + 'assetlog', + 'company', + 'assignedTo', + 'model.category', + 'model.manufacturer', + 'model.fieldset', + 'supplier', + 'requests' + ); @@ -1144,7 +1235,7 @@ class AssetsController extends Controller if ($request->filled('search')) { $assets->TextSearch($request->input('search')); } - + // Search custom fields by column name foreach ($all_custom_fields as $field) { if ($request->filled($field->db_column_name())) { @@ -1185,4 +1276,115 @@ class AssetsController extends Controller return (new AssetsTransformer)->transformRequestedAssets($assets, $total); } + + + public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array + { + + return []; + // to do + } + + public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array + { + $this->authorize('view', Asset::class); + $this->authorize('view', $asset); + $accessory_checkouts = AccessoryCheckout::AssetsAssigned() + ->where('assigned_to', $asset->id) + ->with('adminuser') + ->with('accessories'); + + $offset = ($request->input('offset') > $accessory_checkouts->count()) ? $accessory_checkouts->count() : app('api_offset_value'); + $limit = app('api_limit_value'); + + $total = $accessory_checkouts->count(); + $accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get(); + return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total); + } + + + /** + * Generate asset labels by tag + * + * @author [Nebelkreis] [https://github.com/NebelKreis] + * + * @param Request $request Contains asset_tags array of asset tags to generate labels for + * @return JsonResponse Returns base64 encoded PDF on success, error message on failure + */ + public function getLabels(Request $request): JsonResponse + { + try { + $this->authorize('view', Asset::class); + + // Validate that asset tags were provided in the request + if (!$request->filled('asset_tags')) { + return response()->json(Helper::formatStandardApiResponse('error', null, + trans('admin/hardware/message.no_assets_selected')), 400); + } + + // Convert asset tags from request into collection and fetch matching assets + $asset_tags = collect($request->input('asset_tags')); + $assets = Asset::whereIn('asset_tag', $asset_tags)->get(); + + // Return error if no assets were found for the provided tags + if ($assets->isEmpty()) { + return response()->json(Helper::formatStandardApiResponse('error', null, + trans('admin/hardware/message.does_not_exist')), 404); + } + + try { + $settings = Setting::getSettings(); + + // Check if logo file exists in storage and disable logo if not found + // This prevents errors when trying to include a non-existent logo in the PDF + $settings->label_logo = ($original_logo = $settings->label_logo) && !Storage::disk('public')->exists('/' . $original_logo) ? null : $settings->label_logo; + + + $label = new Label(); + + if (!$label) { + throw new \Exception('Label object could not be created'); + } + + // Configure label with assets and settings + // bulkedit=false and count=0 are default values for label generation + $label = $label->with('assets', $assets) + ->with('settings', $settings) + ->with('bulkedit', false) + ->with('count', 0); + + // Generate PDF using callback function + // The callback captures the PDF content in $pdf_content variable + $pdf_content = ''; + $label->render(function($pdf) use (&$pdf_content) { + $pdf_content = $pdf->Output('', 'S'); + return $pdf; + }); + + // Verify PDF was generated successfully + if (empty($pdf_content)) { + throw new \Exception('PDF content is empty'); + } + + $encoded_content = base64_encode($pdf_content); + + return response()->json(Helper::formatStandardApiResponse('success', [ + 'pdf' => $encoded_content + ], trans('admin/hardware/message.labels_generated'))); + + } catch (\Exception $e) { + return response()->json(Helper::formatStandardApiResponse('error', [ + 'error_message' => $e->getMessage(), + 'error_line' => $e->getLine(), + 'error_file' => $e->getFile() + ], trans('admin/hardware/message.error_generating_labels')), 500); + } + } catch (\Exception $e) { + return response()->json(Helper::formatStandardApiResponse('error', [ + 'error_message' => $e->getMessage(), + 'error_line' => $e->getLine(), + 'error_file' => $e->getFile() + ], $e->getMessage()), 500); + } + } } diff --git a/app/Http/Controllers/Api/CategoriesController.php b/app/Http/Controllers/Api/CategoriesController.php index 2aa4b3741c..85cb70b126 100644 --- a/app/Http/Controllers/Api/CategoriesController.php +++ b/app/Http/Controllers/Api/CategoriesController.php @@ -8,9 +8,9 @@ use App\Http\Transformers\CategoriesTransformer; use App\Http\Transformers\SelectlistTransformer; use App\Models\Category; use Illuminate\Http\Request; +use Illuminate\Http\JsonResponse; use App\Http\Requests\ImageUploadRequest; use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Facades\Validator; class CategoriesController extends Controller { @@ -21,7 +21,7 @@ class CategoriesController extends Controller * @since [v4.0] * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : array { $this->authorize('view', Category::class); $allowed_columns = [ @@ -39,10 +39,12 @@ class CategoriesController extends Controller 'components_count', 'licenses_count', 'image', + 'notes', ]; $categories = Category::select([ 'id', + 'created_by', 'created_at', 'updated_at', 'name', 'category_type', @@ -50,8 +52,11 @@ class CategoriesController extends Controller 'eula_text', 'require_acceptance', 'checkin_email', - 'image' - ])->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count'); + 'image', + 'notes', + ]) + ->with('adminuser') + ->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count'); /* @@ -91,13 +96,33 @@ class CategoriesController extends Controller $categories->where('checkin_email', '=', $request->input('checkin_email')); } + if ($request->filled('created_by')) { + $categories->where('created_by', '=', $request->input('created_by')); + } + + if ($request->filled('created_at')) { + $categories->where('created_at', '=', $request->input('created_at')); + } + + if ($request->filled('updated_at')) { + $categories->where('updated_at', '=', $request->input('updated_at')); + } + // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'assets_count'; - $categories->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets_count'; + + switch ($sort_override) { + case 'created_by': + $categories = $categories->OrderByCreatedBy($order); + break; + default: + $categories = $categories->orderBy($column_sort, $order); + break; + } $total = $categories->count(); $categories = $categories->skip($offset)->take($limit)->get(); @@ -115,7 +140,7 @@ class CategoriesController extends Controller * @param \App\Http\Requests\ImageUploadRequest $request * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : JsonResponse { $this->authorize('create', Category::class); $category = new Category; @@ -136,9 +161,8 @@ class CategoriesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', Category::class); $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id); @@ -156,7 +180,7 @@ class CategoriesController extends Controller * @param int $id * @return \Illuminate\Http\Response */ - public function update(ImageUploadRequest $request, $id) + public function update(ImageUploadRequest $request, $id) : JsonResponse { $this->authorize('update', Category::class); $category = Category::findOrFail($id); @@ -164,7 +188,7 @@ class CategoriesController extends Controller // Don't allow the user to change the category_type once it's been created if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) { return response()->json( - Helper::formatStandardApiResponse('error', null, trans('admin/categories/message.update.cannot_change_category_type')) + Helper::formatStandardApiResponse('error', null, ['category_type' => trans('admin/categories/message.update.cannot_change_category_type')], 422) ); } $category->fill($request->all()); @@ -185,10 +209,10 @@ class CategoriesController extends Controller * @param int $id * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Category::class); - $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id); + $category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($id); if (! $category->isDeletable()) { return response()->json( @@ -208,7 +232,7 @@ class CategoriesController extends Controller * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request, $category_type = 'asset') + public function selectlist(Request $request, $category_type = 'asset') : array { $this->authorize('view.selectlists'); $categories = Category::select([ diff --git a/app/Http/Controllers/Api/CheckoutRequest.php b/app/Http/Controllers/Api/CheckoutRequest.php new file mode 100644 index 0000000000..9b66531ae4 --- /dev/null +++ b/app/Http/Controllers/Api/CheckoutRequest.php @@ -0,0 +1,44 @@ +user()); + return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.success'))); + } catch (AssetNotRequestable $e) { + return response()->json(Helper::formatStandardApiResponse('error', 'Asset is not requestable')); + } catch (AuthorizationException $e) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions'))); + } catch (Exception $e) { + report($e); + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong'))); + } + } + + public function destroy(Asset $asset): JsonResponse + { + try { + CancelCheckoutRequestAction::run($asset, auth()->user()); + return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.canceled'))); + } catch (AuthorizationException $e) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions'))); + } catch (Exception $e) { + report($e); + return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong'))); + } + } +} diff --git a/app/Http/Controllers/Api/CompaniesController.php b/app/Http/Controllers/Api/CompaniesController.php index 9c667973a3..fd7f57ddce 100644 --- a/app/Http/Controllers/Api/CompaniesController.php +++ b/app/Http/Controllers/Api/CompaniesController.php @@ -10,6 +10,7 @@ use App\Models\Company; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; use Illuminate\Support\Facades\Storage; +use Illuminate\Http\JsonResponse; class CompaniesController extends Controller { @@ -18,9 +19,8 @@ class CompaniesController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', Company::class); @@ -38,6 +38,7 @@ class CompaniesController extends Controller 'accessories_count', 'consumables_count', 'components_count', + 'notes', ]; $companies = Company::withCount(['assets as assets_count' => function ($query) { @@ -56,17 +57,29 @@ class CompaniesController extends Controller $companies->where('email', '=', $request->input('email')); } + if ($request->filled('created_by')) { + $companies->where('created_by', '=', $request->input('created_by')); + } + // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value'); $limit = app('api_limit_value'); - - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $companies->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; + + switch ($sort_override) { + case 'created_by': + $companies = $companies->OrderByCreatedBy($order); + break; + default: + $companies = $companies->orderBy($column_sort, $order); + break; + } $total = $companies->count(); + $companies = $companies->skip($offset)->take($limit)->get(); return (new CompaniesTransformer)->transformCompanies($companies, $total); @@ -79,9 +92,8 @@ class CompaniesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : JsonResponse { $this->authorize('create', Company::class); $company = new Company; @@ -102,9 +114,8 @@ class CompaniesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', Company::class); $company = Company::findOrFail($id); @@ -120,9 +131,8 @@ class CompaniesController extends Controller * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(ImageUploadRequest $request, $id) + public function update(ImageUploadRequest $request, $id) : JsonResponse { $this->authorize('update', Company::class); $company = Company::findOrFail($id); @@ -144,9 +154,8 @@ class CompaniesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Company::class); $company = Company::findOrFail($id); @@ -169,7 +178,7 @@ class CompaniesController extends Controller * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $this->authorize('view.selectlists'); $companies = Company::select([ diff --git a/app/Http/Controllers/Api/ComponentsController.php b/app/Http/Controllers/Api/ComponentsController.php index 4cf8173eac..8881628b39 100644 --- a/app/Http/Controllers/Api/ComponentsController.php +++ b/app/Http/Controllers/Api/ComponentsController.php @@ -5,16 +5,17 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Transformers\ComponentsTransformer; -use App\Models\Company; use App\Models\Component; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; use App\Events\CheckoutableCheckedIn; -use App\Events\ComponentCheckedIn; use App\Models\Asset; use Illuminate\Support\Facades\Validator; use Illuminate\Database\Query\Builder; use Illuminate\Support\Facades\Log; +use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\DB; +use Carbon\Carbon; class ComponentsController extends Controller { @@ -24,9 +25,8 @@ class ComponentsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', Component::class); @@ -38,6 +38,7 @@ class ComponentsController extends Controller 'name', 'min_amt', 'order_number', + 'model_number', 'serial', 'purchase_date', 'purchase_cost', @@ -47,7 +48,8 @@ class ComponentsController extends Controller ]; $components = Component::select('components.*') - ->with('company', 'location', 'category', 'assets', 'supplier'); + ->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer', 'uncontrainedAssets') + ->withSum('uncontrainedAssets', 'components_assets.assigned_qty'); if ($request->filled('search')) { $components = $components->TextSearch($request->input('search')); @@ -58,7 +60,7 @@ class ComponentsController extends Controller } if ($request->filled('company_id')) { - $components->where('company_id', '=', $request->input('company_id')); + $components->where('components.company_id', '=', $request->input('company_id')); } if ($request->filled('category_id')) { @@ -69,6 +71,14 @@ class ComponentsController extends Controller $components->where('supplier_id', '=', $request->input('supplier_id')); } + if ($request->filled('manufacturer_id')) { + $components->where('manufacturer_id', '=', $request->input('manufacturer_id')); + } + + if ($request->filled('model_number')) { + $components->where('model_number', '=', $request->input('model_number')); + } + if ($request->filled('location_id')) { $components->where('location_id', '=', $request->input('location_id')); } @@ -98,6 +108,12 @@ class ComponentsController extends Controller case 'supplier': $components = $components->OrderSupplier($order); break; + case 'manufacturer': + $components = $components->OrderManufacturer($order); + break; + case 'created_by': + $components = $components->OrderByCreatedBy($order); + break; default: $components = $components->orderBy($column_sort, $order); break; @@ -116,9 +132,8 @@ class ComponentsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : JsonResponse { $this->authorize('create', Component::class); $component = new Component; @@ -137,9 +152,8 @@ class ComponentsController extends Controller * * @author [A. Gianotto] [] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', Component::class); $component = Component::findOrFail($id); @@ -156,9 +170,8 @@ class ComponentsController extends Controller * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(ImageUploadRequest $request, $id) + public function update(ImageUploadRequest $request, $id) : JsonResponse { $this->authorize('update', Component::class); $component = Component::findOrFail($id); @@ -179,13 +192,17 @@ class ComponentsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Component::class); $component = Component::findOrFail($id); $this->authorize('delete', $component); + + if ($component->numCheckedOut() > 0) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.delete.error_qty'))); + } + $component->delete(); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.delete.success'))); @@ -198,9 +215,8 @@ class ComponentsController extends Controller * @since [v4.0] * @param Request $request * @param int $id - * @return \Illuminate\Http\Response */ - public function getAssets(Request $request, $id) + public function getAssets(Request $request, $id) : array { $this->authorize('view', \App\Models\Asset::class); @@ -241,10 +257,8 @@ class ComponentsController extends Controller * @since [v5.1.8] * @param Request $request * @param int $componentId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function checkout(Request $request, $componentId) + public function checkout(Request $request, $componentId) : JsonResponse { // Check if the component exists if (!$component = Component::find($componentId)) { @@ -275,9 +289,9 @@ class ComponentsController extends Controller $component->assets()->attach($component->id, [ 'component_id' => $component->id, - 'created_at' => \Carbon::now(), + 'created_at' => Carbon::now(), 'assigned_qty' => $request->get('assigned_qty', 1), - 'user_id' => \Auth::id(), + 'created_by' => auth()->id(), 'asset_id' => $request->get('assigned_to'), 'note' => $request->get('note'), ]); @@ -297,15 +311,11 @@ class ComponentsController extends Controller * @since [v5.1.8] * @param Request $request * @param $component_asset_id - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function checkin(Request $request, $component_asset_id) + public function checkin(Request $request, $component_asset_id) : JsonResponse { - if ($component_assets = \DB::table('components_assets')->find($component_asset_id)) { - + if ($component_assets = DB::table('components_assets')->find($component_asset_id)) { if (is_null($component = Component::find($component_assets->component_id))) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.not_found'))); } @@ -313,17 +323,13 @@ class ComponentsController extends Controller $max_to_checkin = $component_assets->assigned_qty; - if ($max_to_checkin > 1) { - - $validator = \Validator::make($request->all(), [ - "checkin_qty" => "required|numeric|between:1,$max_to_checkin" - ]); - - if ($validator->fails()) { - return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and '.$max_to_checkin)); - } + $validator = Validator::make($request->all(), [ + "checkin_qty" => "required|numeric|between:1,$max_to_checkin" + ]); + + if ($validator->fails()) { + return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and ' . $max_to_checkin)); } - // Validation passed, so let's figure out what we have to do here. $qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty', 1)); @@ -333,28 +339,23 @@ class ComponentsController extends Controller $component_assets->assigned_qty = $qty_remaining_in_checkout; Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id); - - \DB::table('components_assets')->where('id', - $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]); + + DB::table('components_assets')->where('id', $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]); // If the checked-in qty is exactly the same as the assigned_qty, // we can simply delete the associated components_assets record - if ($qty_remaining_in_checkout == 0) { - \DB::table('components_assets')->where('id', '=', $component_asset_id)->delete(); + if ($qty_remaining_in_checkout === 0) { + DB::table('components_assets')->where('id', '=', $component_asset_id)->delete(); } - $asset = Asset::find($component_assets->asset_id); - event(new CheckoutableCheckedIn($component, $asset, \Auth::user(), $request->input('note'), \Carbon::now())); + event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now())); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success'))); - } return response()->json(Helper::formatStandardApiResponse('error', null, 'No matching checkouts for that component join record')); - - } } diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index 956a176580..7bddde070c 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use App\Events\CheckoutableCheckedOut; use App\Helpers\Helper; use App\Http\Controllers\Controller; +use App\Http\Requests\StoreConsumableRequest; use App\Http\Transformers\ConsumablesTransformer; use App\Http\Transformers\SelectlistTransformer; use App\Models\Company; @@ -12,8 +13,8 @@ use App\Models\Consumable; use App\Models\User; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; +use Illuminate\Http\JsonResponse; class ConsumablesController extends Controller { @@ -22,34 +23,13 @@ class ConsumablesController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : array { $this->authorize('index', Consumable::class); - // This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations - // Relations will be handled in query scopes a little further down. - $allowed_columns = - [ - 'id', - 'name', - 'order_number', - 'min_amt', - 'purchase_date', - 'purchase_cost', - 'company', - 'category', - 'model_number', - 'item_no', - 'qty', - 'image', - 'notes', - ]; - - $consumables = Consumable::select('consumables.*') - ->with('company', 'location', 'category', 'users', 'manufacturer'); + $consumables = Consumable::with('company', 'location', 'category', 'supplier', 'manufacturer') + ->withCount('users as consumables_users_count'); if ($request->filled('search')) { $consumables = $consumables->TextSearch(e($request->input('search'))); @@ -60,7 +40,7 @@ class ConsumablesController extends Controller } if ($request->filled('company_id')) { - $consumables->where('company_id', '=', $request->input('company_id')); + $consumables->where('consumables.company_id', '=', $request->input('company_id')); } if ($request->filled('category_id')) { @@ -91,15 +71,9 @@ class ConsumablesController extends Controller // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : app('api_offset_value'); $limit = app('api_limit_value'); - - $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'; - $sort_override = $request->input('sort'); - $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; - - - switch ($sort_override) { + switch ($request->input('sort')) { case 'category': $consumables = $consumables->OrderCategory($order); break; @@ -112,11 +86,37 @@ class ConsumablesController extends Controller case 'company': $consumables = $consumables->OrderCompany($order); break; + case 'remaining': + $consumables = $consumables->OrderRemaining($order); + break; case 'supplier': - $components = $consumables->OrderSupplier($order); + $consumables = $consumables->OrderSupplier($order); + break; + case 'created_by': + $consumables = $consumables->OrderByCreatedBy($order); break; default: - $consumables = $consumables->orderBy($column_sort, $order); + // This array is what determines which fields should be allowed to be sorted on ON the table itself. + // These must match a column on the consumables table directly. + $allowed_columns = [ + 'id', + 'name', + 'order_number', + 'min_amt', + 'purchase_date', + 'purchase_cost', + 'company', + 'category', + 'model_number', + 'item_no', + 'manufacturer', + 'location', + 'qty', + 'image' + ]; + + $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; + $consumables = $consumables->orderBy($sort, $order); break; } @@ -132,9 +132,8 @@ class ConsumablesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(StoreConsumableRequest $request) : JsonResponse { $this->authorize('create', Consumable::class); $consumable = new Consumable; @@ -153,9 +152,8 @@ class ConsumablesController extends Controller * * @author [A. Gianotto] [] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', Consumable::class); $consumable = Consumable::with('users')->findOrFail($id); @@ -170,9 +168,8 @@ class ConsumablesController extends Controller * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(ImageUploadRequest $request, $id) + public function update(StoreConsumableRequest $request, $id) : JsonResponse { $this->authorize('update', Consumable::class); $consumable = Consumable::findOrFail($id); @@ -192,9 +189,8 @@ class ConsumablesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Consumable::class); $consumable = Consumable::findOrFail($id); @@ -211,14 +207,13 @@ class ConsumablesController extends Controller * @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form. * @since [v1.0] * @param int $consumableId - * @return array */ - public function getDataView($consumableId) + public function getDataView($consumableId) : array { $consumable = Consumable::with(['consumableAssignments'=> function ($query) { $query->orderBy($query->getModel()->getTable().'.created_at', 'DESC'); }, - 'consumableAssignments.admin'=> function ($query) { + 'consumableAssignments.adminuser'=> function ($query) { }, 'consumableAssignments.user'=> function ($query) { }, @@ -236,7 +231,8 @@ class ConsumablesController extends Controller 'name' => ($consumable_assignment->user) ? $consumable_assignment->user->present()->nameUrl() : 'Deleted User', 'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'), 'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null, - 'admin' => ($consumable_assignment->admin) ? $consumable_assignment->admin->present()->nameUrl() : null, + 'admin' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, // legacy, so we don't change the shape of the response + 'created_by' => ($consumable_assignment->adminuser) ? $consumable_assignment->adminuser->present()->nameUrl() : null, ]; } @@ -252,9 +248,8 @@ class ConsumablesController extends Controller * @author [A. Gutierrez] [] * @param int $id * @since [v4.9.5] - * @return JsonResponse */ - public function checkout(Request $request, $id) + public function checkout(Request $request, $id) : JsonResponse { // Check if the consumable exists if (!$consumable = Consumable::with('users')->find($id)) { @@ -263,6 +258,8 @@ class ConsumablesController extends Controller $this->authorize('checkout', $consumable); + $consumable->checkout_qty = $request->input('checkout_qty', 1); + // Make sure there is at least one available to checkout if ($consumable->numRemaining() <= 0) { return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable'))); @@ -273,27 +270,35 @@ class ConsumablesController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]))); } + // Make sure there is at least one available to checkout + if ($consumable->numRemaining() <= 0 || $consumable->checkout_qty > $consumable->numRemaining()) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable', ['requested' => $consumable->checkout_qty, 'remaining' => $consumable->numRemaining() ]))); + } + + // Check if the user exists - @TODO: this should probably be handled via validation, not here?? if (!$user = User::find($request->input('assigned_to'))) { // Return error message return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found')); - Log::debug('No valid user'); } // Update the consumable data $consumable->assigned_to = $request->input('assigned_to'); - $consumable->users()->attach($consumable->id, + for ($i = 0; $i < $consumable->checkout_qty; $i++) { + $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, - 'user_id' => $user->id, + 'created_by' => $user->id, 'assigned_to' => $request->input('assigned_to'), 'note' => $request->input('note'), ] ); + } - event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note'))); + + event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/consumables/message.checkout.success'))); @@ -304,7 +309,7 @@ class ConsumablesController extends Controller * * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $consumables = Consumable::select([ 'consumables.id', diff --git a/app/Http/Controllers/Api/CustomFieldsController.php b/app/Http/Controllers/Api/CustomFieldsController.php index 470d672ccc..207a92e274 100644 --- a/app/Http/Controllers/Api/CustomFieldsController.php +++ b/app/Http/Controllers/Api/CustomFieldsController.php @@ -8,7 +8,8 @@ use App\Http\Transformers\CustomFieldsTransformer; use App\Models\CustomField; use App\Models\CustomFieldset; use Illuminate\Http\Request; -use Validator; +use Illuminate\Support\Facades\Validator; +use Illuminate\Http\JsonResponse; class CustomFieldsController extends Controller { @@ -20,7 +21,7 @@ class CustomFieldsController extends Controller * @since [v3.0] * @return array */ - public function index() + public function index() : array { $this->authorize('index', CustomField::class); $fields = CustomField::get(); @@ -33,9 +34,8 @@ class CustomFieldsController extends Controller * @author [V. Cordes] [] * @param int $id * @since [v4.1.10] - * @return View */ - public function show($id) + public function show($id) : JsonResponse | array { $this->authorize('view', CustomField::class); if ($field = CustomField::find($id)) { @@ -52,9 +52,8 @@ class CustomFieldsController extends Controller * @since [v4.1.10] * @param \Illuminate\Http\Request $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(Request $request, $id) : JsonResponse { $this->authorize('update', CustomField::class); $field = CustomField::findOrFail($id); @@ -86,9 +85,8 @@ class CustomFieldsController extends Controller * @author [V. Cordes] [] * @since [v4.1.10] * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(Request $request) : JsonResponse { $this->authorize('create', CustomField::class); $field = new CustomField; @@ -136,7 +134,7 @@ class CustomFieldsController extends Controller return $fieldset->fields()->sync($fields); } - public function associate(Request $request, $field_id) + public function associate(Request $request, $field_id) : JsonResponse { $this->authorize('update', CustomFieldset::class); @@ -155,10 +153,9 @@ class CustomFieldsController extends Controller return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success'))); } - public function disassociate(Request $request, $field_id) + public function disassociate(Request $request, $field_id) : JsonResponse { $this->authorize('update', CustomFieldset::class); - $field = CustomField::findOrFail($field_id); $fieldset_id = $request->input('fieldset_id'); @@ -179,9 +176,8 @@ class CustomFieldsController extends Controller * * @author [Brady Wetherington] [] * @since [v1.8] - * @return \Illuminate\Http\RedirectResponse */ - public function destroy($field_id) + public function destroy($field_id) : JsonResponse { $field = CustomField::findOrFail($field_id); diff --git a/app/Http/Controllers/Api/CustomFieldsetsController.php b/app/Http/Controllers/Api/CustomFieldsetsController.php index 2fa22f5491..5dbd507917 100644 --- a/app/Http/Controllers/Api/CustomFieldsetsController.php +++ b/app/Http/Controllers/Api/CustomFieldsetsController.php @@ -9,8 +9,7 @@ use App\Http\Transformers\CustomFieldsTransformer; use App\Models\CustomFieldset; use App\Models\CustomField; use Illuminate\Http\Request; -use Redirect; -use View; +use Illuminate\Http\JsonResponse; /** * This controller handles all actions related to Custom Asset Fieldsets for @@ -30,9 +29,8 @@ class CustomFieldsetsController extends Controller * @author [Josh Gibson] * @param int $id * @since [v1.8] - * @return View */ - public function index() + public function index() : array { $this->authorize('index', CustomField::class); $fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get(); @@ -46,9 +44,8 @@ class CustomFieldsetsController extends Controller * @author [Josh Gibson] * @param int $id * @since [v1.8] - * @return View */ - public function show($id) + public function show($id) : JsonResponse | array { $this->authorize('view', CustomField::class); if ($fieldset = CustomFieldset::find($id)) { @@ -65,9 +62,8 @@ class CustomFieldsetsController extends Controller * @since [v4.0] * @param \Illuminate\Http\Request $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(Request $request, $id) : JsonResponse { $this->authorize('update', CustomField::class); $fieldset = CustomFieldset::findOrFail($id); @@ -86,9 +82,8 @@ class CustomFieldsetsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(Request $request) : JsonResponse { $this->authorize('create', CustomField::class); $fieldset = new CustomFieldset; @@ -118,9 +113,8 @@ class CustomFieldsetsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return \Illuminate\Http\RedirectResponse */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', CustomField::class); $fieldset = CustomFieldset::findOrFail($id); @@ -147,7 +141,7 @@ class CustomFieldsetsController extends Controller * @param $fieldsetId * @return string JSON */ - public function fields($id) + public function fields($id) : array { $this->authorize('view', CustomField::class); $set = CustomFieldset::findOrFail($id); @@ -164,14 +158,11 @@ class CustomFieldsetsController extends Controller * @param $fieldsetId * @return string JSON */ - public function fieldsWithDefaultValues($fieldsetId, $modelId) + public function fieldsWithDefaultValues($fieldsetId, $modelId) : array { $this->authorize('view', CustomField::class); - $set = CustomFieldset::findOrFail($fieldsetId); - $fields = $set->fields; - return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count()); } } diff --git a/app/Http/Controllers/Api/DepartmentsController.php b/app/Http/Controllers/Api/DepartmentsController.php index 485e614b25..167d5cbc2a 100644 --- a/app/Http/Controllers/Api/DepartmentsController.php +++ b/app/Http/Controllers/Api/DepartmentsController.php @@ -6,12 +6,11 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Transformers\DepartmentsTransformer; use App\Http\Transformers\SelectlistTransformer; -use App\Models\Company; use App\Models\Department; -use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; use Illuminate\Support\Facades\Storage; +use Illuminate\Http\JsonResponse; class DepartmentsController extends Controller { @@ -20,12 +19,11 @@ class DepartmentsController extends Controller * * @author [Godfrey Martinez] [] * @since [v4.0] - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', Department::class); - $allowed_columns = ['id', 'name', 'image', 'users_count']; + $allowed_columns = ['id', 'name', 'image', 'users_count', 'notes']; $departments = Department::select( 'departments.id', @@ -37,7 +35,8 @@ class DepartmentsController extends Controller 'departments.manager_id', 'departments.created_at', 'departments.updated_at', - 'departments.image' + 'departments.image', + 'departments.notes', )->with('users')->with('location')->with('manager')->with('company')->withCount('users as users_count'); if ($request->filled('search')) { @@ -74,6 +73,9 @@ class DepartmentsController extends Controller case 'manager': $departments->OrderManager($order); break; + case 'company': + $departments->OrderCompany($order); + break; default: $departments->orderBy($sort, $order); break; @@ -91,16 +93,15 @@ class DepartmentsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : JsonResponse { $this->authorize('create', Department::class); $department = new Department; $department->fill($request->all()); $department = $request->handleImages($department); - $department->user_id = Auth::user()->id; + $department->created_by = auth()->id(); $department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null); if ($department->save()) { @@ -116,13 +117,11 @@ class DepartmentsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', Department::class); $department = Department::findOrFail($id); - return (new DepartmentsTransformer)->transformDepartment($department); } @@ -133,9 +132,8 @@ class DepartmentsController extends Controller * @since [v5.0] * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(ImageUploadRequest $request, $id) + public function update(ImageUploadRequest $request, $id) : JsonResponse { $this->authorize('update', Department::class); $department = Department::findOrFail($id); @@ -156,9 +154,8 @@ class DepartmentsController extends Controller * @author [A. Gianotto] [] * @param int $locationId * @since [v4.0] - * @return \Illuminate\Http\RedirectResponse */ - public function destroy($id) + public function destroy($id) : JsonResponse { $department = Department::findOrFail($id); @@ -180,7 +177,7 @@ class DepartmentsController extends Controller * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $this->authorize('view.selectlists'); diff --git a/app/Http/Controllers/Api/DepreciationsController.php b/app/Http/Controllers/Api/DepreciationsController.php index 502a0741b7..254a72c98e 100644 --- a/app/Http/Controllers/Api/DepreciationsController.php +++ b/app/Http/Controllers/Api/DepreciationsController.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Controller; use App\Http\Transformers\DepreciationsTransformer; use App\Models\Depreciation; use Illuminate\Http\Request; +use Illuminate\Http\JsonResponse; class DepreciationsController extends Controller { @@ -15,14 +16,27 @@ class DepreciationsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', Depreciation::class); - $allowed_columns = ['id','name','months','depreciation_min','created_at']; + $allowed_columns = [ + 'id', + 'name', + 'months', + 'depreciation_min', + 'depreciation_type', + 'created_at', + 'assets_count', + 'models_count', + 'licenses_count', + ]; - $depreciations = Depreciation::select('id','name','months','depreciation_min','user_id','created_at','updated_at'); + $depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','created_at','updated_at', 'created_by') + ->with('adminuser') + ->withCount('assets as assets_count') + ->withCount('models as models_count') + ->withCount('licenses as licenses_count'); if ($request->filled('search')) { $depreciations = $depreciations->TextSearch($request->input('search')); @@ -31,10 +45,18 @@ class DepreciationsController extends Controller // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $depreciations->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; + + switch ($sort_override) { + case 'created_by': + $depreciations = $depreciations->OrderByCreatedBy($order); + break; + default: + $depreciations = $depreciations->orderBy($column_sort, $order); + break; + } $total = $depreciations->count(); $depreciations = $depreciations->skip($offset)->take($limit)->get(); @@ -48,9 +70,8 @@ class DepreciationsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(Request $request) : JsonResponse { $this->authorize('create', Depreciation::class); $depreciation = new Depreciation; @@ -69,9 +90,8 @@ class DepreciationsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : JsonResponse | array { $this->authorize('view', Depreciation::class); $depreciation = Depreciation::findOrFail($id); @@ -86,9 +106,8 @@ class DepreciationsController extends Controller * @since [v4.0] * @param \Illuminate\Http\Request $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(Request $request, $id) : JsonResponse { $this->authorize('update', Depreciation::class); $depreciation = Depreciation::findOrFail($id); @@ -107,9 +126,8 @@ class DepreciationsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Depreciation::class); $depreciation = Depreciation::withCount('models as models_count')->findOrFail($id); diff --git a/app/Http/Controllers/Api/GroupsController.php b/app/Http/Controllers/Api/GroupsController.php index f1eafd49dc..0f474ece83 100644 --- a/app/Http/Controllers/Api/GroupsController.php +++ b/app/Http/Controllers/Api/GroupsController.php @@ -7,7 +7,7 @@ use App\Http\Controllers\Controller; use App\Http\Transformers\GroupsTransformer; use App\Models\Group; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\JsonResponse; class GroupsController extends Controller @@ -17,16 +17,14 @@ class GroupsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('superadmin'); $this->authorize('view', Group::class); - $allowed_columns = ['id', 'name', 'created_at', 'users_count']; - $groups = Group::select('id', 'name', 'permissions', 'created_at', 'updated_at', 'created_by')->with('admin')->withCount('users as users_count'); + $groups = Group::select('id', 'name', 'permissions', 'notes', 'created_at', 'updated_at', 'created_by')->with('adminuser')->withCount('users as users_count'); if ($request->filled('search')) { $groups = $groups->TextSearch($request->input('search')); @@ -36,13 +34,29 @@ class GroupsController extends Controller $groups->where('name', '=', $request->input('name')); } - // Make sure the offset and limit are actually integers and do not exceed system limits + $offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $groups->orderBy($sort, $order); + + switch ($request->input('sort')) { + case 'created_by': + $groups = $groups->OrderByCreatedBy($order); + break; + default: + // This array is what determines which fields should be allowed to be sorted on ON the table itself. + // These must match a column on the consumables table directly. + $allowed_columns = [ + 'id', + 'name', + 'created_at', + 'users_count', + ]; + + $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; + $groups = $groups->orderBy($sort, $order); + break; + } $total = $groups->count(); $groups = $groups->skip($offset)->take($limit)->get(); @@ -56,18 +70,18 @@ class GroupsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(Request $request) : JsonResponse { $this->authorize('superadmin'); $group = new Group; // Get all the available permissions - $permissions = config('permissions'); + $permissions = json_encode(config('permissions')); $groupPermissions = Helper::selectedPermissionsArray($permissions, $permissions); $group->name = $request->input('name'); - $group->created_by = Auth::user()->id; + $group->created_by = auth()->id(); + $group->notes = $request->input('notes'); $group->permissions = json_encode($request->input('permissions', $groupPermissions)); if ($group->save()) { @@ -83,9 +97,8 @@ class GroupsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('superadmin'); $group = Group::findOrFail($id); @@ -99,14 +112,14 @@ class GroupsController extends Controller * @since [v4.0] * @param \Illuminate\Http\Request $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(Request $request, $id) : JsonResponse { $this->authorize('superadmin'); $group = Group::findOrFail($id); $group->name = $request->input('name'); + $group->notes = $request->input('notes'); $group->permissions = $request->input('permissions'); // Todo - some JSON validation stuff here if ($group->save()) { @@ -122,9 +135,8 @@ class GroupsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('superadmin'); $group = Group::findOrFail($id); diff --git a/app/Http/Controllers/Api/ImportController.php b/app/Http/Controllers/Api/ImportController.php index d5323bfdd4..c18df26683 100644 --- a/app/Http/Controllers/Api/ImportController.php +++ b/app/Http/Controllers/Api/ImportController.php @@ -9,27 +9,28 @@ use App\Http\Transformers\ImportsTransformer; use App\Models\Asset; use App\Models\Company; use App\Models\Import; +use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Artisan; use Illuminate\Database\Eloquent\JsonEncodingException; use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Storage; use League\Csv\Reader; +use Onnov\DetectEncoding\EncodingDetector; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Illuminate\Support\Facades\Log; +use Illuminate\Http\JsonResponse; class ImportController extends Controller { /** * Display a listing of the resource. * - * @return \Illuminate\Http\Response */ - public function index() + public function index() : JsonResponse | array { $this->authorize('import'); - $imports = Import::latest()->get(); - + $imports = Import::with('adminuser')->latest()->get(); return (new ImportsTransformer)->transformImports($imports); } @@ -37,9 +38,8 @@ class ImportController extends Controller * Process and store a CSV upload file. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\JsonResponse */ - public function store() + public function store() : JsonResponse { $this->authorize('import'); if (! config('app.lock_passwords')) { @@ -47,6 +47,8 @@ class ImportController extends Controller $path = config('app.private_uploads').'/imports'; $results = []; $import = new Import; + $detector = new EncodingDetector(); + foreach ($files as $file) { if (! in_array($file->getMimeType(), [ 'application/vnd.ms-excel', @@ -57,7 +59,6 @@ class ImportController extends Controller 'text/comma-separated-values', 'text/tsv', ])) { $results['error'] = 'File type must be CSV. Uploaded file is '.$file->getMimeType(); - return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 422); } @@ -65,6 +66,40 @@ class ImportController extends Controller if (! ini_get('auto_detect_line_endings')) { ini_set('auto_detect_line_endings', '1'); } + if (function_exists('iconv')) { + $file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it? + $encoding = $detector->getEncoding($file_contents); + \Log::warning("Discovered encoding: $encoding in uploaded CSV"); + $reader = null; + if (strcasecmp($encoding, 'UTF-8') != 0) { + $transliterated = false; + try { + $transliterated = iconv(strtoupper($encoding), 'UTF-8', $file_contents); + } catch (\Exception $e) { + $transliterated = false; //blank out the partially-decoded string + return response()->json( + Helper::formatStandardApiResponse( + 'error', + null, + trans('admin/hardware/message.import.transliterate_failure', ["encoding" => $encoding]) + ), + 422 + ); + } + if ($transliterated !== false) { + $tmpname = tempnam(sys_get_temp_dir(), ''); + $tmpresults = file_put_contents($tmpname, $transliterated); + $transliterated = null; //save on memory? + if ($tmpresults !== false) { + $newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded' + if ($newfile->isValid()) { + $file = $newfile; + } + } + } + } + $file_contents = null; //try to save on memory, I guess? + } $reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak? try { @@ -134,7 +169,7 @@ class ImportController extends Controller } $import->filesize = filesize($path.'/'.$file_name); - + $import->created_by = auth()->id(); $import->save(); $results[] = $import; } @@ -152,9 +187,8 @@ class ImportController extends Controller * Processes the specified Import. * * @param int $import_id - * @return \Illuminate\Http\Response */ - public function process(ItemImportRequest $request, $import_id) + public function process(ItemImportRequest $request, $import_id) : JsonResponse { $this->authorize('import'); @@ -179,6 +213,9 @@ class ImportController extends Controller case 'asset': $redirectTo = 'hardware.index'; break; + case 'assetModel': + $redirectTo = 'models.index'; + break; case 'accessory': $redirectTo = 'accessories.index'; break; @@ -197,6 +234,15 @@ class ImportController extends Controller case 'location': $redirectTo = 'locations.index'; break; + case 'supplier': + $redirectTo = 'suppliers.index'; + break; + case 'manufacturer': + $redirectTo = 'manufacturers.index'; + break; + case 'category': + $redirectTo = 'categories.index'; + break; } if ($errors) { //Failure @@ -212,9 +258,8 @@ class ImportController extends Controller * Remove the specified resource from storage. * * @param int $import_id - * @return \Illuminate\Http\Response */ - public function destroy($import_id) + public function destroy($import_id) : JsonResponse { $this->authorize('create', Asset::class); @@ -231,6 +276,8 @@ class ImportController extends Controller return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning'))); } + } + return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning'))); } } diff --git a/app/Http/Controllers/Api/LabelsController.php b/app/Http/Controllers/Api/LabelsController.php index 126fe99438..0b54235422 100644 --- a/app/Http/Controllers/Api/LabelsController.php +++ b/app/Http/Controllers/Api/LabelsController.php @@ -8,7 +8,7 @@ use App\Http\Transformers\LabelsTransformer; use App\Models\Labels\Label; use Illuminate\Http\Request; use Illuminate\Support\ItemNotFoundException; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\JsonResponse; class LabelsController extends Controller { @@ -16,9 +16,8 @@ class LabelsController extends Controller * Returns JSON listing of all labels. * * @author Grant Le Roux - * @return JsonResponse */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', Label::class); @@ -50,9 +49,8 @@ class LabelsController extends Controller * * @author Grant Le Roux * @param string $labelName - * @return JsonResponse */ - public function show(string $labelName) + public function show(string $labelName) : JsonResponse | array { $labelName = str_replace('/', '\\', $labelName); try { diff --git a/app/Http/Controllers/Api/LicenseSeatsController.php b/app/Http/Controllers/Api/LicenseSeatsController.php index 317b098768..babca4a319 100644 --- a/app/Http/Controllers/Api/LicenseSeatsController.php +++ b/app/Http/Controllers/Api/LicenseSeatsController.php @@ -9,7 +9,7 @@ use App\Models\Asset; use App\Models\License; use App\Models\LicenseSeat; use App\Models\User; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class LicenseSeatsController extends Controller @@ -19,11 +19,10 @@ class LicenseSeatsController extends Controller * * @param \Illuminate\Http\Request $request * @param int $licenseId - * @return \Illuminate\Http\Response */ - public function index(Request $request, $licenseId) + public function index(Request $request, $licenseId) : JsonResponse | array { - // + if ($license = License::find($licenseId)) { $this->authorize('view', $license); @@ -35,7 +34,7 @@ class LicenseSeatsController extends Controller if ($request->input('sort') == 'department') { $seats->OrderDepartments($order); } else { - $seats->orderBy('id', $order); + $seats->orderBy('updated_at', $order); } $total = $seats->count(); @@ -64,11 +63,10 @@ class LicenseSeatsController extends Controller * * @param int $licenseId * @param int $seatId - * @return \Illuminate\Http\Response */ - public function show($licenseId, $seatId) + public function show($licenseId, $seatId) : JsonResponse | array { - // + $this->authorize('view', License::class); // sanity checks: // 1. does the license seat exist? @@ -89,19 +87,18 @@ class LicenseSeatsController extends Controller * @param \Illuminate\Http\Request $request * @param int $licenseId * @param int $seatId - * @return \Illuminate\Http\Response */ - public function update(Request $request, $licenseId, $seatId) + public function update(Request $request, $licenseId, $seatId) : JsonResponse | array { $this->authorize('checkout', License::class); - // sanity checks: - // 1. does the license seat exist? + if (! $licenseSeat = LicenseSeat::find($seatId)) { return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found')); } - // 2. does the seat belong to the specified license? - if (! $license = $licenseSeat->license()->first() || $license->id != intval($licenseId)) { + + $license = $licenseSeat->license()->first(); + if (!$license || $license->id != intval($licenseId)) { return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license')); } @@ -110,7 +107,7 @@ class LicenseSeatsController extends Controller // attempt to update the license seat $licenseSeat->fill($request->all()); - $licenseSeat->user_id = Auth::user()->id; + $licenseSeat->created_by = auth()->id(); // check if this update is a checkin operation // 1. are relevant fields touched at all? @@ -139,13 +136,13 @@ class LicenseSeatsController extends Controller if ($licenseSeat->save()) { if ($is_checkin) { - $licenseSeat->logCheckin($target, $request->input('note')); + $licenseSeat->logCheckin($target, $request->input('notes')); return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success'))); } // in this case, relevant fields are touched but it's not a checkin operation. so it must be a checkout operation. - $licenseSeat->logCheckout($request->input('note'), $target); + $licenseSeat->logCheckout($request->input('notes'), $target); return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success'))); } diff --git a/app/Http/Controllers/Api/LicensesController.php b/app/Http/Controllers/Api/LicensesController.php index c35b669d70..db39f987aa 100644 --- a/app/Http/Controllers/Api/LicensesController.php +++ b/app/Http/Controllers/Api/LicensesController.php @@ -4,14 +4,12 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; use App\Http\Controllers\Controller; -use App\Http\Transformers\LicenseSeatsTransformer; use App\Http\Transformers\LicensesTransformer; use App\Http\Transformers\SelectlistTransformer; -use App\Models\Company; use App\Models\License; -use App\Models\LicenseSeat; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Illuminate\Http\JsonResponse; class LicensesController extends Controller { @@ -21,16 +19,15 @@ class LicensesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', License::class); - $licenses = License::with('company', 'manufacturer', 'supplier','category')->withCount('freeSeats as free_seats_count'); + $licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count'); if ($request->filled('company_id')) { - $licenses->where('company_id', '=', $request->input('company_id')); + $licenses->where('licenses.company_id', '=', $request->input('company_id')); } if ($request->filled('name')) { @@ -73,6 +70,9 @@ class LicensesController extends Controller $licenses->where('depreciation_id', '=', $request->input('depreciation_id')); } + if ($request->filled('created_by')) { + $licenses->where('created_by', '=', $request->input('created_by')); + } if (($request->filled('maintained')) && ($request->input('maintained')=='true')) { $licenses->where('maintained','=',1); @@ -116,6 +116,9 @@ class LicensesController extends Controller case 'company': $licenses = $licenses->leftJoin('companies', 'licenses.company_id', '=', 'companies.id')->orderBy('companies.name', $order); break; + case 'created_by': + $licenses = $licenses->OrderByCreatedBy($order); + break; default: $allowed_columns = [ @@ -156,11 +159,9 @@ class LicensesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(Request $request) : JsonResponse { - // $this->authorize('create', License::class); $license = new License; $license->fill($request->all()); @@ -177,12 +178,11 @@ class LicensesController extends Controller * * @author [A. Gianotto] [] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : JsonResponse | array { $this->authorize('view', License::class); - $license = License::withCount('freeSeats')->findOrFail($id); + $license = License::withCount('freeSeats as free_seats_count')->findOrFail($id); $license = $license->load('assignedusers', 'licenseSeats.user', 'licenseSeats.asset'); return (new LicensesTransformer)->transformLicense($license); @@ -195,9 +195,8 @@ class LicensesController extends Controller * @since [v4.0] * @param \Illuminate\Http\Request $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(Request $request, $id) : JsonResponse | array { // $this->authorize('update', License::class); @@ -218,11 +217,9 @@ class LicensesController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { - // $license = License::findOrFail($id); $this->authorize('delete', $license); @@ -248,7 +245,7 @@ class LicensesController extends Controller * * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $licenses = License::select([ 'licenses.id', diff --git a/app/Http/Controllers/Api/LocationsController.php b/app/Http/Controllers/Api/LocationsController.php index 78cf43c671..b5c911a6ca 100644 --- a/app/Http/Controllers/Api/LocationsController.php +++ b/app/Http/Controllers/Api/LocationsController.php @@ -3,11 +3,19 @@ namespace App\Http\Controllers\Api; use App\Helpers\Helper; -use App\Http\Requests\ImageUploadRequest; use App\Http\Controllers\Controller; +use App\Http\Requests\ImageUploadRequest; +use App\Http\Transformers\AccessoriesTransformer; +use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\LocationsTransformer; use App\Http\Transformers\SelectlistTransformer; +use App\Models\Accessory; +use App\Models\AccessoryCheckout; +use App\Models\Asset; +use App\Models\Company; use App\Models\Location; +use App\Models\Setting; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; @@ -21,30 +29,34 @@ class LocationsController extends Controller * @since [v4.0] * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', Location::class); $allowed_columns = [ - 'id', - 'name', + 'accessories_count', 'address', 'address2', + 'assets_count', + 'assets_count', + 'assigned_accessories_count', + 'assigned_assets_count', + 'assigned_assets_count', 'city', - 'state', 'country', - 'zip', 'created_at', - 'updated_at', - 'manager_id', - 'image', - 'assigned_assets_count', - 'users_count', - 'assets_count', - 'assigned_assets_count', - 'assets_count', - 'rtd_assets_count', 'currency', + 'id', + 'image', 'ldap_ou', + 'company_id', + 'manager_id', + 'name', + 'rtd_assets_count', + 'state', + 'updated_at', + 'users_count', + 'zip', + 'notes', ]; $locations = Location::with('parent', 'manager', 'children')->select([ @@ -65,11 +77,23 @@ class LocationsController extends Controller 'locations.image', 'locations.ldap_ou', 'locations.currency', - ])->withCount('assignedAssets as assigned_assets_count') + 'locations.company_id', + 'locations.notes', + ]) + ->withCount('assignedAssets as assigned_assets_count') + ->withCount('assignedAssets as assigned_assets_count') ->withCount('assets as assets_count') + ->withCount('assignedAccessories as assigned_accessories_count') + ->withCount('accessories as accessories_count') ->withCount('rtd_assets as rtd_assets_count') ->withCount('children as children_count') - ->withCount('users as users_count'); + ->withCount('users as users_count') + ->with('adminuser'); + + // Only scope locations if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $locations = Company::scopeCompanyables($locations); + } if ($request->filled('search')) { $locations = $locations->TextSearch($request->input('search')); @@ -103,6 +127,10 @@ class LocationsController extends Controller $locations->where('locations.manager_id', '=', $request->input('manager_id')); } + if ($request->filled('company_id')) { + $locations->where('locations.company_id', '=', $request->input('company_id')); + } + // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $locations->count()) ? $locations->count() : app('api_offset_value'); $limit = app('api_limit_value'); @@ -119,6 +147,9 @@ class LocationsController extends Controller case 'manager': $locations->OrderManager($order); break; + case 'company': + $locations->OrderCompany($order); + break; default: $locations->orderBy($sort, $order); break; @@ -138,15 +169,23 @@ class LocationsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : JsonResponse { $this->authorize('create', Location::class); $location = new Location; $location->fill($request->all()); $location = $request->handleImages($location); + // Only scope location if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $location->company_id = Company::getIdForCurrentUser($request->get('company_id')); + // check if parent is set and has a different company + if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) { + response()->json(Helper::formatStandardApiResponse('error', null, 'different company than parent')); + } + } + if ($location->save()) { return response()->json(Helper::formatStandardApiResponse('success', (new LocationsTransformer)->transformLocation($location), trans('admin/locations/message.create.success'))); } @@ -160,12 +199,11 @@ class LocationsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : JsonResponse | array { $this->authorize('view', Location::class); - $location = Location::with('parent', 'manager', 'children') + $location = Location::with('parent', 'manager', 'children', 'company') ->select([ 'locations.id', 'locations.name', @@ -181,6 +219,8 @@ class LocationsController extends Controller 'locations.updated_at', 'locations.image', 'locations.currency', + 'locations.company_id', + 'locations.notes', ]) ->withCount('assignedAssets as assigned_assets_count') ->withCount('assets as assets_count') @@ -199,9 +239,8 @@ class LocationsController extends Controller * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id - * @return \Illuminate\Http\JsonResponse */ - public function update(ImageUploadRequest $request, $id) + public function update(ImageUploadRequest $request, $id) : JsonResponse { $this->authorize('update', Location::class); $location = Location::findOrFail($id); @@ -209,6 +248,19 @@ class LocationsController extends Controller $location->fill($request->all()); $location = $request->handleImages($location); + if ($request->filled('company_id')) { + // Only scope location if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $location->company_id = Company::getIdForCurrentUser($request->get('company_id')); + // check if there are related objects with different company + if (Helper::test_locations_fmcs(false, $id, $location->company_id)) { + return response()->json(Helper::formatStandardApiResponse('error', null, 'error scoped locations')); + } + } else { + $location->company_id = $request->get('company_id'); + } + } + if ($location->isValid()) { $location->save(); @@ -224,15 +276,47 @@ class LocationsController extends Controller return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors())); } + + public function assets(Request $request, Location $location) : JsonResponse | array + { + $this->authorize('view', Asset::class); + $this->authorize('view', $location); + $assets = Asset::where('location_id', '=', $location->id)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc'); + $assets = $assets->get(); + return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request); + } + + public function assignedAssets(Request $request, Location $location) : JsonResponse | array + { + $this->authorize('view', Asset::class); + $this->authorize('view', $location); + $assets = Asset::where('assigned_to', '=', $location->id)->where('assigned_type', '=', Location::class)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc'); + $assets = $assets->get(); + return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request); + } + + public function assignedAccessories(Request $request, Location $location) : JsonResponse | array + { + $this->authorize('view', Accessory::class); + $this->authorize('view', $location); + $accessory_checkouts = AccessoryCheckout::LocationAssigned()->where('assigned_to', $location->id)->with('adminuser')->with('accessories'); + + $offset = ($request->input('offset') > $accessory_checkouts->count()) ? $accessory_checkouts->count() : app('api_offset_value'); + $limit = app('api_limit_value'); + + $total = $accessory_checkouts->count(); + $accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get(); + return (new LocationsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total); + } + /** * Remove the specified resource from storage. * * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Location::class); $location = Location::withCount('assignedAssets as assigned_assets_count') @@ -240,6 +324,7 @@ class LocationsController extends Controller ->withCount('rtd_assets as rtd_assets_count') ->withCount('children as children_count') ->withCount('users as users_count') + ->withCount('accessories as accessories_count') ->findOrFail($id); if (! $location->isDeletable()) { @@ -280,7 +365,7 @@ class LocationsController extends Controller * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { // If a user is in the process of editing their profile, as determined by the referrer, // then we check that they have permission to edit their own location. @@ -296,6 +381,11 @@ class LocationsController extends Controller 'locations.image', ]); + // Only scope locations if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $locations = Company::scopeCompanyables($locations); + } + $page = 1; if ($request->filled('page')) { $page = $request->input('page'); @@ -325,7 +415,6 @@ class LocationsController extends Controller $paginated_results = new LengthAwarePaginator($locations_formatted->forPage($page, 500), $locations_formatted->count(), 500, $page, []); - //return []; return (new SelectlistTransformer)->transformSelectlist($paginated_results); } } diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php index 6b5a130229..652fad1cfc 100644 --- a/app/Http/Controllers/Api/ManufacturersController.php +++ b/app/Http/Controllers/Api/ManufacturersController.php @@ -10,8 +10,8 @@ use App\Models\Actionlog; use App\Models\Manufacturer; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; +use Illuminate\Http\JsonResponse; class ManufacturersController extends Controller { @@ -22,14 +22,48 @@ class ManufacturersController extends Controller * @since [v4.0] * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', Manufacturer::class); - $allowed_columns = ['id', 'name', 'url', 'support_url', 'support_email', 'warranty_lookup_url', 'support_phone', 'created_at', 'updated_at', 'image', 'assets_count', 'consumables_count', 'components_count', 'licenses_count']; + $allowed_columns = [ + 'id', + 'name', + 'url', + 'support_url', + 'support_email', + 'warranty_lookup_url', + 'support_phone', + 'created_at', + 'updated_at', + 'image', + 'assets_count', + 'consumables_count', + 'components_count', + 'licenses_count', + 'notes', + ]; - $manufacturers = Manufacturer::select( - ['id', 'name', 'url', 'support_url', 'warranty_lookup_url', 'support_email', 'support_phone', 'created_at', 'updated_at', 'image', 'deleted_at'] - )->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count'); + $manufacturers = Manufacturer::select([ + 'id', + 'name', + 'url', + 'support_url', + 'warranty_lookup_url', + 'support_email', + 'support_phone', + 'created_by', + 'created_at', + 'updated_at', + 'image', + 'deleted_at', + 'notes', + ]) + ->with('adminuser') + ->withCount('assets as assets_count') + ->withCount('licenses as licenses_count') + ->withCount('consumables as consumables_count') + ->withCount('accessories as accessories_count') + ->withCount('components as components_count'); if ($request->input('deleted') == 'true') { $manufacturers->onlyTrashed(); @@ -66,10 +100,18 @@ class ManufacturersController extends Controller // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $manufacturers->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; + + switch ($sort_override) { + case 'created_by': + $manufacturers = $manufacturers->OrderByCreatedBy($order); + break; + default: + $manufacturers = $manufacturers->orderBy($column_sort, $order); + break; + } $total = $manufacturers->count(); $manufacturers = $manufacturers->skip($offset)->take($limit)->get(); @@ -83,9 +125,8 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : JsonResponse { $this->authorize('create', Manufacturer::class); $manufacturer = new Manufacturer; @@ -105,9 +146,8 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : JsonResponse | array { $this->authorize('view', Manufacturer::class); $manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id); @@ -122,9 +162,8 @@ class ManufacturersController extends Controller * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(ImageUploadRequest $request, $id) + public function update(ImageUploadRequest $request, $id) : JsonResponse { $this->authorize('update', Manufacturer::class); $manufacturer = Manufacturer::findOrFail($id); @@ -144,9 +183,8 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Manufacturer::class); $manufacturer = Manufacturer::findOrFail($id); @@ -167,10 +205,9 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @since [v6.3.4] * @param int $id - * @return \Illuminate\Http\JsonResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function restore($id) + public function restore($id) : JsonResponse { $this->authorize('delete', Manufacturer::class); @@ -186,7 +223,7 @@ class ManufacturersController extends Controller $logaction->item_type = Manufacturer::class; $logaction->item_id = $manufacturer->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = Auth::user()->id; + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); return response()->json(Helper::formatStandardApiResponse('success', trans('admin/manufacturers/message.restore.success')), 200); @@ -206,7 +243,7 @@ class ManufacturersController extends Controller * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $this->authorize('view.selectlists'); diff --git a/app/Http/Controllers/Api/PredefinedKitsController.php b/app/Http/Controllers/Api/PredefinedKitsController.php index b398dbfae2..24f1320185 100644 --- a/app/Http/Controllers/Api/PredefinedKitsController.php +++ b/app/Http/Controllers/Api/PredefinedKitsController.php @@ -7,6 +7,8 @@ use App\Http\Controllers\Controller; use App\Http\Transformers\PredefinedKitsTransformer; use App\Models\PredefinedKit; use Illuminate\Http\Request; +use Illuminate\Http\JsonResponse; +use App\Http\Transformers\SelectlistTransformer; /** * @author [D. Minaev.] [] @@ -18,12 +20,11 @@ class PredefinedKitsController extends Controller * * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { $this->authorize('view', PredefinedKit::class); - $allowed_columns = ['id', 'name']; - $kits = PredefinedKit::query(); + $kits = PredefinedKit::query()->with('adminuser'); if ($request->filled('search')) { $kits = $kits->TextSearch($request->input('search')); @@ -34,8 +35,25 @@ class PredefinedKitsController extends Controller $limit = app('api_limit_value'); $order = $request->input('order') === 'desc' ? 'desc' : 'asc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'name'; - $kits->orderBy($sort, $order); + + switch ($request->input('sort')) { + case 'created_by': + $kits = $kits->OrderByCreatedBy($order); + break; + default: + // This array is what determines which fields should be allowed to be sorted on ON the table itself. + // These must match a column on the consumables table directly. + $allowed_columns = [ + 'id', + 'name', + 'created_at', + 'updated_at', + ]; + + $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; + $kits = $kits->orderBy($sort, $order); + break; + } $total = $kits->count(); $kits = $kits->skip($offset)->take($limit)->get(); @@ -47,9 +65,8 @@ class PredefinedKitsController extends Controller * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(Request $request) : JsonResponse { $this->authorize('create', PredefinedKit::class); $kit = new PredefinedKit; @@ -66,9 +83,8 @@ class PredefinedKitsController extends Controller * Display the specified resource. * * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', PredefinedKit::class); $kit = PredefinedKit::findOrFail($id); @@ -81,9 +97,8 @@ class PredefinedKitsController extends Controller * * @param \Illuminate\Http\Request $request * @param int $id kit id - * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(Request $request, $id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($id); @@ -100,9 +115,8 @@ class PredefinedKitsController extends Controller * Remove the specified resource from storage. * * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', PredefinedKit::class); $kit = PredefinedKit::findOrFail($id); @@ -123,7 +137,7 @@ class PredefinedKitsController extends Controller * * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $kits = PredefinedKit::select([ 'id', @@ -145,7 +159,7 @@ class PredefinedKitsController extends Controller * @param int $id * @return \Illuminate\Http\Response */ - public function indexLicenses($kit_id) + public function indexLicenses($kit_id) : array { $this->authorize('view', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -160,7 +174,7 @@ class PredefinedKitsController extends Controller * @param int $id * @return \Illuminate\Http\Response */ - public function storeLicense(Request $request, $kit_id) + public function storeLicense(Request $request, $kit_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); @@ -186,9 +200,8 @@ class PredefinedKitsController extends Controller * * @param \Illuminate\Http\Request $request * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function updateLicense(Request $request, $kit_id, $license_id) + public function updateLicense(Request $request, $kit_id, $license_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -205,9 +218,8 @@ class PredefinedKitsController extends Controller * Remove the specified resource from storage. * * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function detachLicense($kit_id, $license_id) + public function detachLicense($kit_id, $license_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -221,9 +233,8 @@ class PredefinedKitsController extends Controller * Display the specified resource. * * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function indexModels($kit_id) + public function indexModels($kit_id) : array { $this->authorize('view', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -236,9 +247,8 @@ class PredefinedKitsController extends Controller * Store the specified resource. * * @param int $id - * @return \Illuminate\Http\Response */ - public function storeModel(Request $request, $kit_id) + public function storeModel(Request $request, $kit_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); @@ -252,7 +262,7 @@ class PredefinedKitsController extends Controller $relation = $kit->models(); if ($relation->find($model_id)) { - return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => 'Model already attached to kit'])); + return response()->json(Helper::formatStandardApiResponse('error', null, ['model' => trans('admin/kits/general.model_already_attached')])); } $relation->attach($model_id, ['quantity' => $quantity]); @@ -264,9 +274,8 @@ class PredefinedKitsController extends Controller * * @param \Illuminate\Http\Request $request * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function updateModel(Request $request, $kit_id, $model_id) + public function updateModel(Request $request, $kit_id, $model_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -283,9 +292,8 @@ class PredefinedKitsController extends Controller * Remove the specified resource from storage. * * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function detachModel($kit_id, $model_id) + public function detachModel($kit_id, $model_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -299,9 +307,8 @@ class PredefinedKitsController extends Controller * Display the specified resource. * * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function indexConsumables($kit_id) + public function indexConsumables($kit_id) : array { $this->authorize('view', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -314,9 +321,8 @@ class PredefinedKitsController extends Controller * Store the specified resource. * * @param int $id - * @return \Illuminate\Http\Response */ - public function storeConsumable(Request $request, $kit_id) + public function storeConsumable(Request $request, $kit_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); @@ -342,9 +348,8 @@ class PredefinedKitsController extends Controller * * @param \Illuminate\Http\Request $request * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function updateConsumable(Request $request, $kit_id, $consumable_id) + public function updateConsumable(Request $request, $kit_id, $consumable_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -361,9 +366,8 @@ class PredefinedKitsController extends Controller * Remove the specified resource from storage. * * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function detachConsumable($kit_id, $consumable_id) + public function detachConsumable($kit_id, $consumable_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -377,9 +381,8 @@ class PredefinedKitsController extends Controller * Display the specified resource. * * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function indexAccessories($kit_id) + public function indexAccessories($kit_id) : array { $this->authorize('view', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -392,9 +395,8 @@ class PredefinedKitsController extends Controller * Store the specified resource. * * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function storeAccessory(Request $request, $kit_id) + public function storeAccessory(Request $request, $kit_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); @@ -420,9 +422,8 @@ class PredefinedKitsController extends Controller * * @param \Illuminate\Http\Request $request * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function updateAccessory(Request $request, $kit_id, $accessory_id) + public function updateAccessory(Request $request, $kit_id, $accessory_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); @@ -439,9 +440,8 @@ class PredefinedKitsController extends Controller * Remove the specified resource from storage. * * @param int $kit_id - * @return \Illuminate\Http\Response */ - public function detachAccessory($kit_id, $accessory_id) + public function detachAccessory($kit_id, $accessory_id) : JsonResponse { $this->authorize('update', PredefinedKit::class); $kit = PredefinedKit::findOrFail($kit_id); diff --git a/app/Http/Controllers/Api/ProfileController.php b/app/Http/Controllers/Api/ProfileController.php index b6752dd1ed..f7f91e094e 100644 --- a/app/Http/Controllers/Api/ProfileController.php +++ b/app/Http/Controllers/Api/ProfileController.php @@ -6,13 +6,13 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\CheckoutRequest; use Illuminate\Http\Response; -use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use Laravel\Passport\TokenRepository; use Illuminate\Contracts\Validation\Factory as ValidationFactory; use Illuminate\Support\Facades\Gate; use App\Models\CustomField; use Illuminate\Support\Facades\DB; +use Illuminate\Http\JsonResponse; class ProfileController extends Controller { @@ -42,12 +42,10 @@ class ProfileController extends Controller * * @author [A. Gianotto] [] * @since [v4.3.0] - * - * @return array */ - public function requestedAssets() + public function requestedAssets() : array { - $checkoutRequests = CheckoutRequest::where('user_id', '=', Auth::user()->id)->get(); + $checkoutRequests = CheckoutRequest::where('user_id', '=', auth()->id())->get(); $results = array(); $show_field = array(); @@ -95,10 +93,9 @@ class ProfileController extends Controller * * @author [A. Gianotto] [] * @since [v6.0.5] - * - * @return \Illuminate\Http\Response */ - public function createApiToken(Request $request) { + public function createApiToken(Request $request) : JsonResponse + { if (!Gate::allows('self.api')) { abort(403); @@ -106,14 +103,14 @@ class ProfileController extends Controller $accessTokenName = $request->input('name', 'Auth Token'); - if ($accessToken = Auth::user()->createToken($accessTokenName)->accessToken) { + if ($accessToken = auth()->user()->createToken($accessTokenName)->accessToken) { // Get the ID so we can return that with the payload - $token = DB::table('oauth_access_tokens')->where('user_id', '=', Auth::user()->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first(); + $token = DB::table('oauth_access_tokens')->where('user_id', '=', auth()->id())->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first(); $accessTokenData['id'] = $token->id; $accessTokenData['token'] = $accessToken; $accessTokenData['name'] = $accessTokenName; - return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, 'Personal access token '.$accessTokenName.' created successfully')); + return response()->json(Helper::formatStandardApiResponse('success', $accessTokenData, trans('account/general.personal_api_keys_success', ['key' => $accessTokenName]))); } return response()->json(Helper::formatStandardApiResponse('error', null, 'Token could not be created.')); @@ -125,17 +122,16 @@ class ProfileController extends Controller * * @author [A. Gianotto] [] * @since [v6.0.5] - * - * @return \Illuminate\Http\Response */ - public function deleteApiToken($tokenId) { + public function deleteApiToken($tokenId) : Response + { if (!Gate::allows('self.api')) { abort(403); } $token = $this->tokenRepository->findForUser( - $tokenId, Auth::user()->getAuthIdentifier() + $tokenId, auth()->user()->getAuthIdentifier() ); if (is_null($token)) { @@ -154,16 +150,15 @@ class ProfileController extends Controller * * @author [A. Gianotto] [] * @since [v6.0.5] - * - * @return \Illuminate\Http\Response */ - public function showApiTokens(Request $request) { + public function showApiTokens() : JsonResponse + { if (!Gate::allows('self.api')) { abort(403); } - $tokens = $this->tokenRepository->forUser(Auth::user()->getAuthIdentifier()); + $tokens = $this->tokenRepository->forUser(auth()->user()->getAuthIdentifier()); $token_values = $tokens->load('client')->filter(function ($token) { return $token->client->personal_access_client && ! $token->revoked; })->values(); diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php index 8761c9442b..c03dddf6b7 100644 --- a/app/Http/Controllers/Api/ReportsController.php +++ b/app/Http/Controllers/Api/ReportsController.php @@ -5,7 +5,10 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Http\Transformers\ActionlogsTransformer; use App\Models\Actionlog; +use App\Models\Company; +use App\Models\Setting; use Illuminate\Http\Request; +use Illuminate\Http\JsonResponse; class ReportsController extends Controller { @@ -14,13 +17,13 @@ class ReportsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return View */ - public function index(Request $request) + public function index(Request $request) : JsonResponse | array { - $this->authorize('reports.view'); + $this->authorize('activity.view'); + + $actionlogs = Actionlog::with('item', 'user', 'adminuser', 'target', 'location'); - $actionlogs = Actionlog::with('item', 'user', 'admin', 'target', 'location'); if ($request->filled('search')) { $actionlogs = $actionlogs->TextSearch(e($request->input('search'))); @@ -45,36 +48,40 @@ class ReportsController extends Controller } if ($request->filled('action_type')) { - $actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc'); + $actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type')); } - if ($request->filled('user_id')) { - $actionlogs = $actionlogs->where('user_id', '=', $request->input('user_id')); + if ($request->filled('created_by')) { + $actionlogs = $actionlogs->where('created_by', '=', $request->input('created_by')); } if ($request->filled('action_source')) { - $actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'))->orderBy('created_at', 'desc'); + $actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source')); + } + + if ($request->filled('remote_ip')) { + $actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip')); } - if ($request->filled('remote_ip')) { - $actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip'))->orderBy('created_at', 'desc'); - } if ($request->filled('uploads')) { - $actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc'); + $actionlogs = $actionlogs->whereNotNull('filename'); } $allowed_columns = [ 'id', 'created_at', 'target_id', - 'user_id', + 'created_by', 'accept_signature', 'action_type', 'note', 'remote_ip', 'user_agent', + 'target_type', + 'item_type', 'action_source', + 'action_date', ]; @@ -83,11 +90,19 @@ class ReportsController extends Controller $offset = ($request->input('offset') > $total) ? $total : app('api_offset_value'); $limit = app('api_limit_value'); - $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; $order = ($request->input('order') == 'asc') ? 'asc' : 'desc'; + switch ($request->input('sort')) { + case 'created_by': + $actionlogs->OrderByCreatedBy($order); + break; + default: + $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'action_logs.created_at'; + $actionlogs = $actionlogs->orderBy($sort, $order); + break; + } - $actionlogs = $actionlogs->orderBy($sort, $order)->skip($offset)->take($limit)->get(); + $actionlogs = $actionlogs->skip($offset)->take($limit)->get(); return response()->json((new ActionlogsTransformer)->transformActionlogs($actionlogs, $total), 200, ['Content-Type' => 'application/json;charset=utf8'], JSON_UNESCAPED_UNICODE); } diff --git a/app/Http/Controllers/Api/SettingsController.php b/app/Http/Controllers/Api/SettingsController.php index d9059988f2..7eb28a4815 100644 --- a/app/Http/Controllers/Api/SettingsController.php +++ b/app/Http/Controllers/Api/SettingsController.php @@ -9,26 +9,22 @@ use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Models\Ldap; use App\Models\Setting; -use Mail; -use App\Notifications\SlackTest; use App\Notifications\MailTest; -use GuzzleHttp\Client; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Response; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Facades\Validator; -use App\Http\Requests\SlackSettingsRequest; +use Illuminate\Support\Facades\Validator; use App\Http\Transformers\LoginAttemptsTransformer; +use Symfony\Component\HttpFoundation\BinaryFileResponse; class SettingsController extends Controller { - public function ldaptest() + public function ldaptest() : JsonResponse { $settings = Setting::getSettings(); @@ -88,7 +84,7 @@ class SettingsController extends Controller } - public function ldaptestlogin(Request $request) + public function ldaptestlogin(Request $request) : JsonResponse { if (Setting::getSettings()->ldap_enabled != '1') { @@ -148,9 +144,8 @@ class SettingsController extends Controller * * @author [A. Gianotto] [] * @since [v3.0] - * @return JsonResponse */ - public function ajaxTestEmail() + public function ajaxTestEmail() : JsonResponse { if (!config('app.lock_passwords')) { try { @@ -170,9 +165,8 @@ class SettingsController extends Controller * * @author [A. Gianotto] [] * @since [v5.0.0] - * @return JsonResponse */ - public function purgeBarcodes() + public function purgeBarcodes() : JsonResponse { $file_count = 0; $files = Storage::disk('public')->files('barcodes'); @@ -211,9 +205,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * @since [v5.0.0] * @param \Illuminate\Http\Request $request - * @return array | JsonResponse */ - public function showLoginAttempts(Request $request) + public function showLoginAttempts(Request $request) : array { $allowed_columns = ['id', 'username', 'remote_ip', 'user_agent', 'successful', 'created_at']; @@ -233,9 +226,9 @@ class SettingsController extends Controller * Lists backup files * * @author [A. Gianotto] - * @return array | JsonResponse */ - public function listBackups() { + public function listBackups() : array + { $settings = Setting::getSettings(); $path = 'app/backups'; $backup_files = Storage::files($path); @@ -276,9 +269,9 @@ class SettingsController extends Controller * exhausts memory on larger files. * * @author [A. Gianotto] - * @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse */ - public function downloadBackup($file) { + public function downloadBackup($file) : JsonResponse | BinaryFileResponse + { $path = storage_path('app/backups'); @@ -296,9 +289,9 @@ class SettingsController extends Controller * * @author [A. Gianotto] * @since [v6.3.1] - * @return JsonResponse|\Symfony\Component\HttpFoundation\BinaryFileResponse */ - public function downloadLatestBackup() { + public function downloadLatestBackup() : JsonResponse | BinaryFileResponse + { $fileData = collect(); foreach (Storage::files('app/backups') as $file) { diff --git a/app/Http/Controllers/Api/StatuslabelsController.php b/app/Http/Controllers/Api/StatuslabelsController.php index 7c8e260c2f..b3503ff152 100644 --- a/app/Http/Controllers/Api/StatuslabelsController.php +++ b/app/Http/Controllers/Api/StatuslabelsController.php @@ -8,10 +8,11 @@ use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\StatuslabelsTransformer; use App\Models\Asset; +use App\Models\Setting; use App\Models\Statuslabel; use Illuminate\Http\Request; use App\Http\Transformers\PieChartTransformer; -use Illuminate\Support\Arr; +use Illuminate\Http\JsonResponse; class StatuslabelsController extends Controller { @@ -20,14 +21,21 @@ class StatuslabelsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request) : array { $this->authorize('view', Statuslabel::class); - $allowed_columns = ['id', 'name', 'created_at', 'assets_count', 'color', 'notes', 'default_label']; + $allowed_columns = [ + 'id', + 'name', + 'created_at', + 'assets_count', + 'color', + 'notes', + 'default_label' + ]; - $statuslabels = Statuslabel::withCount('assets as assets_count'); + $statuslabels = Statuslabel::with('adminuser')->withCount('assets as assets_count'); if ($request->filled('search')) { $statuslabels = $statuslabels->TextSearch($request->input('search')); @@ -54,10 +62,18 @@ class StatuslabelsController extends Controller // Make sure the offset and limit are actually integers and do not exceed system limits $offset = ($request->input('offset') > $statuslabels->count()) ? $statuslabels->count() : app('api_offset_value'); $limit = app('api_limit_value'); - $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $statuslabels->orderBy($sort, $order); + $sort_override = $request->input('sort'); + $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at'; + + switch ($sort_override) { + case 'created_by': + $statuslabels = $statuslabels->OrderByCreatedBy($order); + break; + default: + $statuslabels = $statuslabels->orderBy($column_sort, $order); + break; + } $total = $statuslabels->count(); $statuslabels = $statuslabels->skip($offset)->take($limit)->get(); @@ -72,15 +88,15 @@ class StatuslabelsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response */ - public function store(Request $request) + public function store(Request $request) : JsonResponse { $this->authorize('create', Statuslabel::class); $request->except('deployable', 'pending', 'archived'); if (! $request->filled('type')) { - return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']]), 500); + + return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']])); } $statuslabel = new Statuslabel; @@ -108,9 +124,8 @@ class StatuslabelsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', Statuslabel::class); $statuslabel = Statuslabel::findOrFail($id); @@ -126,9 +141,8 @@ class StatuslabelsController extends Controller * @since [v4.0] * @param \Illuminate\Http\Request $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(Request $request, $id) + public function update(Request $request, $id) : JsonResponse { $this->authorize('update', Statuslabel::class); $statuslabel = Statuslabel::findOrFail($id); @@ -163,9 +177,8 @@ class StatuslabelsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Statuslabel::class); $statuslabel = Statuslabel::findOrFail($id); @@ -188,13 +201,18 @@ class StatuslabelsController extends Controller * * @author [A. Gianotto] [] * @since [v3.0] - * @return array */ - public function getAssetCountByStatuslabel() + public function getAssetCountByStatuslabel() : array { $this->authorize('view', Statuslabel::class); - $statuslabels = Statuslabel::withCount('assets')->get(); - $total = Array(); + + if (Setting::getSettings()->show_archived_in_list == 0 ) { + $statuslabels = Statuslabel::withCount('assets')->where('archived','0')->get(); + } else { + $statuslabels = Statuslabel::withCount('assets')->get(); + } + + $total = []; foreach ($statuslabels as $statuslabel) { @@ -215,9 +233,8 @@ class StatuslabelsController extends Controller * * @author [A. Gianotto] [] * @since [v6.0.11] - * @return array */ - public function getAssetCountByMetaStatus() + public function getAssetCountByMetaStatus() : array { $this->authorize('view', Statuslabel::class); @@ -245,9 +262,8 @@ class StatuslabelsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function assets(Request $request, $id) + public function assets(Request $request, $id) : array { $this->authorize('view', Statuslabel::class); $this->authorize('index', Asset::class); @@ -274,19 +290,20 @@ class StatuslabelsController extends Controller /** * Returns a boolean response based on whether the status label - * is one that is deployable. + * is one that is deployable or pending. * * This is used by the hardware create/edit view to determine whether - * we should provide a dropdown of users for them to check the asset out to. + * we should provide a dropdown of users for them to check the asset out to, + * and whether we show a warning that the asset will be checked in if it's already + * assigned but the status is changed to one that isn't pending or deployable * * @author [A. Gianotto] [] * @since [v4.0] - * @return bool */ - public function checkIfDeployable($id) + public function checkIfDeployable($id) : string { $statuslabel = Statuslabel::findOrFail($id); - if ($statuslabel->getStatuslabelType() == 'deployable') { + if (($statuslabel->getStatuslabelType() == 'pending') || ($statuslabel->getStatuslabelType() == 'deployable')) { return '1'; } @@ -300,7 +317,7 @@ class StatuslabelsController extends Controller * @since [v6.1.1] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $this->authorize('view.selectlists'); diff --git a/app/Http/Controllers/Api/SuppliersController.php b/app/Http/Controllers/Api/SuppliersController.php index 3e3d637be0..ab0db67909 100644 --- a/app/Http/Controllers/Api/SuppliersController.php +++ b/app/Http/Controllers/Api/SuppliersController.php @@ -10,6 +10,7 @@ use App\Models\Supplier; use Illuminate\Http\Request; use App\Http\Requests\ImageUploadRequest; use Illuminate\Support\Facades\Storage; +use Illuminate\Http\JsonResponse; class SuppliersController extends Controller { @@ -20,13 +21,18 @@ class SuppliersController extends Controller * @since [v4.0] * @return \Illuminate\Http\Response */ - public function index(Request $request) + public function index(Request $request): array { $this->authorize('view', Supplier::class); - $allowed_columns = [' - id', + $allowed_columns = [ + 'id', 'name', 'address', + 'address2', + 'city', + 'state', + 'country', + 'zip', 'phone', 'contact', 'fax', @@ -38,21 +44,24 @@ class SuppliersController extends Controller 'components_count', 'consumables_count', 'url', + 'notes', ]; $suppliers = Supplier::select( - ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes', 'url']) + ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'created_by', 'updated_at', 'deleted_at', 'image', 'notes', 'url', 'zip']) ->withCount('assets as assets_count') ->withCount('licenses as licenses_count') ->withCount('accessories as accessories_count') ->withCount('components as components_count') - ->withCount('consumables as consumables_count'); + ->withCount('consumables as consumables_count') + ->with('adminuser'); if ($request->filled('search')) { - $suppliers = $suppliers->TextSearch($request->input('search')); + $suppliers->TextSearch($request->input('search')); } + if ($request->filled('name')) { $suppliers->where('name', '=', $request->input('name')); } @@ -99,7 +108,15 @@ class SuppliersController extends Controller $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; - $suppliers->orderBy($sort, $order); + + switch ($request->input('sort')) { + case 'created_by': + $suppliers->OrderByCreatedByName($order); + break; + default: + $suppliers->orderBy($sort, $order); + break; + } $total = $suppliers->count(); $suppliers = $suppliers->skip($offset)->take($limit)->get(); @@ -114,9 +131,8 @@ class SuppliersController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request - * @return \Illuminate\Http\Response */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : JsonResponse { $this->authorize('create', Supplier::class); $supplier = new Supplier; @@ -136,9 +152,8 @@ class SuppliersController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function show($id) + public function show($id) : array { $this->authorize('view', Supplier::class); $supplier = Supplier::findOrFail($id); @@ -154,9 +169,8 @@ class SuppliersController extends Controller * @since [v4.0] * @param \App\Http\Requests\ImageUploadRequest $request * @param int $id - * @return \Illuminate\Http\Response */ - public function update(ImageUploadRequest $request, $id) + public function update(ImageUploadRequest $request, $id) : JsonResponse { $this->authorize('update', Supplier::class); $supplier = Supplier::findOrFail($id); @@ -176,9 +190,8 @@ class SuppliersController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\Response */ - public function destroy($id) + public function destroy($id) : JsonResponse { $this->authorize('delete', Supplier::class); $supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->findOrFail($id); @@ -209,7 +222,7 @@ class SuppliersController extends Controller * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $this->authorize('view.selectlists'); diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index 3374f1907e..994ad15f25 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -6,6 +6,7 @@ use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\SaveUserRequest; use App\Http\Transformers\AccessoriesTransformer; +use App\Http\Transformers\ActionlogsTransformer; use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\ConsumablesTransformer; use App\Http\Transformers\LicensesTransformer; @@ -14,16 +15,19 @@ use App\Http\Transformers\UsersTransformer; use App\Models\Actionlog; use App\Models\Asset; use App\Models\Accessory; +use App\Models\Company; use App\Models\Consumable; use App\Models\License; use App\Models\User; use App\Notifications\CurrentInventory; use Illuminate\Support\Facades\Auth; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Log; use App\Http\Requests\DeleteUserRequest; +use Illuminate\Http\JsonResponse; class UsersController extends Controller { @@ -35,19 +39,20 @@ class UsersController extends Controller * * @return array */ - public function index(Request $request) + public function index(Request $request) : array { $this->authorize('view', User::class); $users = User::select([ 'users.activated', - 'users.created_by', 'users.address', 'users.avatar', 'users.city', 'users.company_id', 'users.country', + 'users.created_by', 'users.created_at', + 'users.updated_at', 'users.deleted_at', 'users.department_id', 'users.email', @@ -66,7 +71,6 @@ class UsersController extends Controller 'users.state', 'users.two_factor_enrolled', 'users.two_factor_optin', - 'users.updated_at', 'users.username', 'users.zip', 'users.remote', @@ -77,8 +81,17 @@ class UsersController extends Controller 'users.autoassign_licenses', 'users.website', - ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations') - ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count'); + ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy', 'managesUsers', 'managedLocations', 'eulas') + ->withCount([ + 'assets as assets_count' => function(Builder $query) { + $query->withoutTrashed(); + }, + 'licenses as licenses_count', + 'accessories as accessories_count', + 'consumables as consumables_count', + 'managesUsers as manages_users_count', + 'managedLocations as manages_locations_count' + ]); if ($request->filled('search') != '') { @@ -194,17 +207,21 @@ class UsersController extends Controller } if ($request->filled('manages_users_count')) { - $users->has('manages_users_count', '=', $request->input('manages_users_count')); + $users->has('managesUsers', '=', $request->input('manages_users_count')); } if ($request->filled('manages_locations_count')) { - $users->has('manages_locations_count', '=', $request->input('manages_locations_count')); + $users->has('managedLocations', '=', $request->input('manages_locations_count')); } if ($request->filled('autoassign_licenses')) { $users->where('autoassign_licenses', '=', $request->input('autoassign_licenses')); } + if ($request->filled('locale')) { + $users = $users->where('users.locale', '=', $request->input('locale')); + } + if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) { $users = $users->onlyTrashed(); @@ -247,13 +264,10 @@ class UsersController extends Controller 'jobtitle', 'username', 'employee_num', - 'assets', - 'accessories', - 'consumables', - 'licenses', 'groups', 'activated', 'created_at', + 'updated_at', 'two_factor_enrolled', 'two_factor_optin', 'last_login', @@ -279,6 +293,8 @@ class UsersController extends Controller 'end_date', 'autoassign_licenses', 'website', + 'locale', + 'notes', ]; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name'; @@ -305,7 +321,7 @@ class UsersController extends Controller * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) + public function selectlist(Request $request) : array { $users = User::select( [ @@ -324,6 +340,7 @@ class UsersController extends Controller $users = $users->where(function ($query) use ($request) { $query->SimpleNameSearch($request->get('search')) ->orWhere('username', 'LIKE', '%'.$request->get('search').'%') + ->orWhere('email', 'LIKE', '%'.$request->get('search').'%') ->orWhere('employee_num', 'LIKE', '%'.$request->get('search').'%'); }); } @@ -361,21 +378,21 @@ class UsersController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param \Illuminate\Http\Request $request - * @return array | \Illuminate\Http\JsonResponse */ - public function store(SaveUserRequest $request) + public function store(SaveUserRequest $request) : JsonResponse { $this->authorize('create', User::class); $user = new User; $user->fill($request->all()); - $user->created_by = Auth::user()->id; + $user->company_id = Company::getIdForCurrentUser($request->input('company_id')); + $user->created_by = auth()->id(); if ($request->has('permissions')) { $permissions_array = $request->input('permissions'); // Strip out the superuser permission if the API user isn't a superadmin - if (! Auth::user()->isSuperUser()) { + if (! auth()->user()->isSuperUser()) { unset($permissions_array['superuser']); } $user->permissions = $permissions_array; @@ -408,9 +425,8 @@ class UsersController extends Controller * * @author [A. Gianotto] [] * @param int $id - * @return array | \Illuminate\Http\JsonResponse */ - public function show($id) + public function show($id) : JsonResponse | array { $this->authorize('view', User::class); @@ -431,15 +447,11 @@ class UsersController extends Controller * @since [v4.0] * @param \Illuminate\Http\Request $request * @param int $id - * @return \Illuminate\Http\JsonResponse */ - public function update(SaveUserRequest $request, $id) + public function update(SaveUserRequest $request, User $user): JsonResponse { $this->authorize('update', User::class); - if ($user = User::find($id)) { - - $this->authorize('update', $user); /** @@ -449,14 +461,16 @@ class UsersController extends Controller * */ - - if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) { + if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) { return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.')); } - $user->fill($request->all()); + if ($request->filled('company_id')) { + $user->company_id = Company::getIdForCurrentUser($request->input('company_id')); + } + if ($user->id == $request->input('manager_id')) { return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot be your own manager')); } @@ -472,25 +486,23 @@ class UsersController extends Controller $permissions_array = $request->input('permissions'); // Strip out the individual superuser permission if the API user isn't a superadmin - if (!Auth::user()->isSuperUser()) { + if (!auth()->user()->isSuperUser()) { unset($permissions_array['superuser']); } $user->permissions = $permissions_array; } - - // Update the location of any assets checked out to this user - Asset::where('assigned_type', User::class) - ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); - - + if($request->has('location_id')) { + // Update the location of any assets checked out to this user + Asset::where('assigned_type', User::class) + ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); + } app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); if ($user->save()) { - // Check if the request has groups passed and has a value, AND that the user us a superuser - if (($request->has('groups')) && (Auth::user()->isSuperUser())) { + if (($request->has('groups')) && (auth()->user()->isSuperUser())) { $validator = Validator::make($request->only('groups'), [ 'groups.*' => 'integer|exists:permission_groups,id', @@ -502,18 +514,10 @@ class UsersController extends Controller // Sync the groups since the user is a superuser and the groups pass validation $user->groups()->sync($request->input('groups')); - - } - return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update'))); } - return response()->json(Helper::formatStandardApiResponse('error', null, $user->getErrors())); - } - - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id')))); - } /** @@ -522,9 +526,8 @@ class UsersController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param int $id - * @return \Illuminate\Http\JsonResponse */ - public function destroy(DeleteUserRequest $request, $id) + public function destroy(DeleteUserRequest $request, $id) : JsonResponse { $this->authorize('delete', User::class); @@ -560,9 +563,8 @@ class UsersController extends Controller * @author [A. Gianotto] [] * @since [v3.0] * @param $userId - * @return array | \Illuminate\Http\JsonResponse */ - public function assets(Request $request, $id) + public function assets(Request $request, $id) : JsonResponse | array { $this->authorize('view', User::class); $this->authorize('view', Asset::class); @@ -605,9 +607,9 @@ class UsersController extends Controller * @since [v6.0.13] * @param Request $request * @param $id - * @return string JSON */ - public function emailAssetList(Request $request, $id) + public function emailAssetList(Request $request, $id) : JsonResponse + { $this->authorize('update', User::class); @@ -633,9 +635,8 @@ class UsersController extends Controller * @author [A. Gianotto] [] * @since [v3.0] * @param $userId - * @return array | \Illuminate\Http\JsonResponse */ - public function consumables(Request $request, $id) + public function consumables(Request $request, $id) : array { $this->authorize('view', User::class); $this->authorize('view', Consumable::class); @@ -651,9 +652,8 @@ class UsersController extends Controller * @author [A. Gianotto] [] * @since [v4.6.14] * @param $userId - * @return array */ - public function accessories($id) + public function accessories($id) : array { $this->authorize('view', User::class); $user = User::findOrFail($id); @@ -670,9 +670,8 @@ class UsersController extends Controller * @author [N. Mathar] [] * @since [v5.0] * @param $userId - * @return array | \Illuminate\Http\JsonResponse */ - public function licenses($id) + public function licenses($id) : JsonResponse | array { $this->authorize('view', User::class); $this->authorize('view', License::class); @@ -693,9 +692,8 @@ class UsersController extends Controller * @author [A. Gianotto] [] * @since [v3.0] * @param $userId - * @return string JSON */ - public function postTwoFactorReset(Request $request) + public function postTwoFactorReset(Request $request) : JsonResponse { $this->authorize('update', User::class); @@ -714,7 +712,7 @@ class UsersController extends Controller $logaction->item_type = User::class; $logaction->item_id = $user->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = Auth::user()->id; + $logaction->created_by = auth()->id(); $logaction->logaction('2FA reset'); return response()->json(['message' => trans('admin/settings/general.two_factor_reset_success')], 200); @@ -733,22 +731,39 @@ class UsersController extends Controller * @author [Juan Font] [] * @since [v4.4.2] * @param \Illuminate\Http\Request $request - * @return array */ - public function getCurrentUserInfo(Request $request) + public function getCurrentUserInfo(Request $request) : array { return (new UsersTransformer)->transformUser($request->user()); } + /** + * Display the EULAs accepted by the user. + * + * @param \App\Models\User $user + * @param \App\Http\Transformers\ActionlogsTransformer $transformer + * @return \Illuminate\Http\JsonResponse + *@since [v8.1.16] + * @author [Godfrey Martinez] [] + */ + public function eulas(User $user, ActionlogsTransformer $transformer) + { + $this->authorize('view', Asset::class); + + $eulas = $user->eulas; + return response()->json( + $transformer->transformActionlogs($eulas, $eulas->count()) + ); + } + /** * Restore a soft-deleted user. * * @author [E. Taylor] [] * @param int $userId * @since [v6.0.0] - * @return \Illuminate\Http\JsonResponse */ - public function restore($userId) + public function restore($userId) : JsonResponse { $this->authorize('delete', User::class); @@ -766,7 +781,7 @@ class UsersController extends Controller $logaction->item_type = User::class; $logaction->item_id = $user->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = Auth::user()->id; + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')), 200); diff --git a/app/Http/Controllers/AssetMaintenancesController.php b/app/Http/Controllers/AssetMaintenancesController.php index c9ffea9a4c..0e46118af7 100644 --- a/app/Http/Controllers/AssetMaintenancesController.php +++ b/app/Http/Controllers/AssetMaintenancesController.php @@ -2,17 +2,14 @@ namespace App\Http\Controllers; -use App\Helpers\Helper; use App\Models\Asset; use App\Models\AssetMaintenance; use App\Models\Company; use Illuminate\Support\Facades\Auth; use Carbon\Carbon; use Illuminate\Http\Request; -use Slack; -use Str; -use TCPDF; -use View; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; /** * This controller handles all actions related to Asset Maintenance for @@ -22,20 +19,6 @@ use View; */ class AssetMaintenancesController extends Controller { - /** - * Checks for permissions for this action. - * - * @todo This should be replaced with middleware and/or policies - * @author Vincent Sposato - * @version v1.0 - * @since [v1.8] - * @return View - */ - private static function getInsufficientPermissionsRedirect() - { - return redirect()->route('maintenances.index') - ->with('error', trans('general.insufficient_permissions')); - } /** * Returns a view that invokes the ajax tables which actually contains @@ -46,9 +29,8 @@ class AssetMaintenancesController extends Controller * @author Vincent Sposato * @version v1.0 * @since [v1.8] - * @return View */ - public function index() + public function index() : View { $this->authorize('view', Asset::class); return view('asset_maintenances/index'); @@ -63,7 +45,7 @@ class AssetMaintenancesController extends Controller * @since [v1.8] * @return mixed */ - public function create() + public function create() : View { $this->authorize('update', Asset::class); $asset = null; @@ -72,16 +54,10 @@ class AssetMaintenancesController extends Controller // We have to set this so that the correct property is set in the select2 ajax dropdown $asset->asset_id = $asset->id; } - - // Prepare Asset Maintenance Type List - $assetMaintenanceType = [ - '' => 'Select an asset maintenance type', - ] + AssetMaintenance::getImprovementOptions(); - // Mark the selected asset, if it came in - + return view('asset_maintenances/edit') + ->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions()) ->with('asset', $asset) - ->with('assetMaintenanceType', $assetMaintenanceType) ->with('item', new AssetMaintenance); } @@ -92,48 +68,49 @@ class AssetMaintenancesController extends Controller * @author Vincent Sposato * @version v1.0 * @since [v1.8] - * @return mixed */ - public function store(Request $request) + public function store(Request $request) : RedirectResponse { $this->authorize('update', Asset::class); - // create a new model instance - $assetMaintenance = new AssetMaintenance(); - $assetMaintenance->supplier_id = $request->input('supplier_id'); - $assetMaintenance->is_warranty = $request->input('is_warranty'); - $assetMaintenance->cost = $request->input('cost'); - $assetMaintenance->notes = $request->input('notes'); - $asset = Asset::find($request->input('asset_id')); - if ((! Company::isCurrentUserHasAccess($asset)) && ($asset != null)) { - return static::getInsufficientPermissionsRedirect(); + $assets = Asset::whereIn('id', $request->input('selected_assets'))->get(); + + // Loop through the selected assets + foreach ($assets as $asset) { + + $assetMaintenance = new AssetMaintenance(); + $assetMaintenance->supplier_id = $request->input('supplier_id'); + $assetMaintenance->is_warranty = $request->input('is_warranty'); + $assetMaintenance->cost = $request->input('cost'); + $assetMaintenance->notes = $request->input('notes'); + + // Save the asset maintenance data + $assetMaintenance->asset_id = $asset->id; + $assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type'); + $assetMaintenance->title = $request->input('title'); + $assetMaintenance->start_date = $request->input('start_date'); + $assetMaintenance->completion_date = $request->input('completion_date'); + $assetMaintenance->created_by = auth()->id(); + + if (($assetMaintenance->completion_date !== null) + && ($assetMaintenance->start_date !== '') + && ($assetMaintenance->start_date !== '0000-00-00') + ) { + $startDate = Carbon::parse($assetMaintenance->start_date); + $completionDate = Carbon::parse($assetMaintenance->completion_date); + $assetMaintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true); + } + + + // Was the asset maintenance created? + if (!$assetMaintenance->save()) { + return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors()); + } } - // Save the asset maintenance data - $assetMaintenance->asset_id = $request->input('asset_id'); - $assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type'); - $assetMaintenance->title = $request->input('title'); - $assetMaintenance->start_date = $request->input('start_date'); - $assetMaintenance->completion_date = $request->input('completion_date'); - $assetMaintenance->user_id = Auth::id(); + return redirect()->route('maintenances.index') + ->with('success', trans('admin/asset_maintenances/message.create.success')); - if (($assetMaintenance->completion_date !== null) - && ($assetMaintenance->start_date !== '') - && ($assetMaintenance->start_date !== '0000-00-00') - ) { - $startDate = Carbon::parse($assetMaintenance->start_date); - $completionDate = Carbon::parse($assetMaintenance->completion_date); - $assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate); - } - - // Was the asset maintenance created? - if ($assetMaintenance->save()) { - // Redirect to the new asset maintenance page - return redirect()->route('maintenances.index') - ->with('success', trans('admin/asset_maintenances/message.create.success')); - } - - return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors()); } /** @@ -141,39 +118,19 @@ class AssetMaintenancesController extends Controller * * @see AssetMaintenancesController::postEdit() method that stores the data * @author Vincent Sposato - * @param int $assetMaintenanceId * @version v1.0 * @since [v1.8] - * @return mixed */ - public function edit($assetMaintenanceId = null) + public function edit(AssetMaintenance $maintenance) : View | RedirectResponse { $this->authorize('update', Asset::class); - // Check if the asset maintenance exists - $this->authorize('update', Asset::class); - // Check if the asset maintenance exists - if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) { - // Redirect to the asset maintenance management page - return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found')); - } elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) { - // Redirect to the asset maintenance management page - return redirect()->route('maintenances.index')->with('error', 'asset does not exist'); - } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { - return static::getInsufficientPermissionsRedirect(); - } + $this->authorize('update', $maintenance->asset); - - // Prepare Improvement Type List - $assetMaintenanceType = [ - '' => 'Select an improvement type', - ] + AssetMaintenance::getImprovementOptions(); - - // Get Supplier List - // Render the view return view('asset_maintenances/edit') - ->with('selectedAsset', null) - ->with('assetMaintenanceType', $assetMaintenanceType) - ->with('item', $assetMaintenance); + ->with('selected_assets', $maintenance->asset->pluck('id')->toArray()) + ->with('asset_ids', request()->input('asset_ids', [])) + ->with('assetMaintenanceType', AssetMaintenance::getImprovementOptions()) + ->with('item', $maintenance); } /** @@ -183,69 +140,49 @@ class AssetMaintenancesController extends Controller * @author Vincent Sposato * @param Request $request * @param int $assetMaintenanceId - * @return mixed * @version v1.0 * @since [v1.8] */ - public function update(Request $request, $assetMaintenanceId = null) + public function update(Request $request, AssetMaintenance $maintenance) : View | RedirectResponse { $this->authorize('update', Asset::class); - // Check if the asset maintenance exists - if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) { - // Redirect to the asset maintenance management page - return redirect()->route('maintenances.index')->with('error', trans('admin/asset_maintenances/message.not_found')); - } elseif ((!$assetMaintenance->asset) || ($assetMaintenance->asset->deleted_at!='')) { - // Redirect to the asset maintenance management page - return redirect()->route('maintenances.index')->with('error', 'asset does not exist'); - } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { - return static::getInsufficientPermissionsRedirect(); - } + $this->authorize('update', $maintenance->asset); - $assetMaintenance->supplier_id = $request->input('supplier_id'); - $assetMaintenance->is_warranty = $request->input('is_warranty'); - $assetMaintenance->cost = $request->input('cost'); - $assetMaintenance->notes = $request->input('notes'); + $maintenance->supplier_id = $request->input('supplier_id'); + $maintenance->is_warranty = $request->input('is_warranty', 0); + $maintenance->cost = $request->input('cost'); + $maintenance->notes = $request->input('notes'); + $maintenance->asset_maintenance_type = $request->input('asset_maintenance_type'); + $maintenance->title = $request->input('title'); + $maintenance->start_date = $request->input('start_date'); + $maintenance->completion_date = $request->input('completion_date'); - $asset = Asset::find(request('asset_id')); - if (! Company::isCurrentUserHasAccess($asset)) { - return static::getInsufficientPermissionsRedirect(); - } - - // Save the asset maintenance data - $assetMaintenance->asset_id = $request->input('asset_id'); - $assetMaintenance->asset_maintenance_type = $request->input('asset_maintenance_type'); - $assetMaintenance->title = $request->input('title'); - $assetMaintenance->start_date = $request->input('start_date'); - $assetMaintenance->completion_date = $request->input('completion_date'); - - if (($assetMaintenance->completion_date == null) - ) { - if (($assetMaintenance->asset_maintenance_time !== 0) - || (! is_null($assetMaintenance->asset_maintenance_time)) + // Todo - put this in a getter/setter? + if (($maintenance->completion_date == null)) + { + if (($maintenance->asset_maintenance_time !== 0) + || (! is_null($maintenance->asset_maintenance_time)) ) { - $assetMaintenance->asset_maintenance_time = null; + $maintenance->asset_maintenance_time = null; } } - if (($assetMaintenance->completion_date !== null) - && ($assetMaintenance->start_date !== '') - && ($assetMaintenance->start_date !== '0000-00-00') + if (($maintenance->completion_date !== null) + && ($maintenance->start_date !== '') + && ($maintenance->start_date !== '0000-00-00') ) { - $startDate = Carbon::parse($assetMaintenance->start_date); - $completionDate = Carbon::parse($assetMaintenance->completion_date); - $assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate); + $startDate = Carbon::parse($maintenance->start_date); + $completionDate = Carbon::parse($maintenance->completion_date); + $maintenance->asset_maintenance_time = (int) $completionDate->diffInDays($startDate, true); } - // Was the asset maintenance created? - if ($assetMaintenance->save()) { - - // Redirect to the new asset maintenance page + if ($maintenance->save()) { return redirect()->route('maintenances.index') - ->with('success', trans('admin/asset_maintenances/message.edit.success')); + ->with('success', trans('admin/asset_maintenances/message.edit.success')); } - return redirect()->back()->withInput()->withErrors($assetMaintenance->getErrors()); + return redirect()->back()->withInput()->withErrors($maintenance->getErrors()); } /** @@ -255,23 +192,13 @@ class AssetMaintenancesController extends Controller * @param int $assetMaintenanceId * @version v1.0 * @since [v1.8] - * @return mixed */ - public function destroy($assetMaintenanceId) + public function destroy(AssetMaintenance $maintenance) : RedirectResponse { $this->authorize('update', Asset::class); - // Check if the asset maintenance exists - if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) { - // Redirect to the asset maintenance management page - return redirect()->route('maintenances.index') - ->with('error', trans('admin/asset_maintenances/message.not_found')); - } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { - return static::getInsufficientPermissionsRedirect(); - } - + $this->authorize('update', $maintenance->asset); // Delete the asset maintenance - $assetMaintenance->delete(); - + $maintenance->delete(); // Redirect to the asset_maintenance management page return redirect()->route('maintenances.index') ->with('success', trans('admin/asset_maintenances/message.delete.success')); @@ -284,21 +211,9 @@ class AssetMaintenancesController extends Controller * @param int $assetMaintenanceId * @version v1.0 * @since [v1.8] - * @return View */ - public function show($assetMaintenanceId) + public function show(AssetMaintenance $maintenance) : View | RedirectResponse { - $this->authorize('view', Asset::class); - - // Check if the asset maintenance exists - if (is_null($assetMaintenance = AssetMaintenance::find($assetMaintenanceId))) { - // Redirect to the asset maintenance management page - return redirect()->route('maintenances.index') - ->with('error', trans('admin/asset_maintenances/message.not_found')); - } elseif (! Company::isCurrentUserHasAccess($assetMaintenance->asset)) { - return static::getInsufficientPermissionsRedirect(); - } - - return view('asset_maintenances/view')->with('assetMaintenance', $assetMaintenance); + return view('asset_maintenances/view')->with('assetMaintenance', $maintenance); } } diff --git a/app/Http/Controllers/AssetModelsController.php b/app/Http/Controllers/AssetModelsController.php index a2deb616b0..0795588776 100755 --- a/app/Http/Controllers/AssetModelsController.php +++ b/app/Http/Controllers/AssetModelsController.php @@ -4,19 +4,22 @@ namespace App\Http\Controllers; use App\Helpers\Helper; use App\Http\Requests\ImageUploadRequest; +use App\Http\Requests\StoreAssetModelRequest; use App\Models\Actionlog; -use App\Models\Asset; use App\Models\AssetModel; use App\Models\CustomField; +use App\Models\SnipeModel; use App\Models\User; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\Validator; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; -use Symfony\Component\HttpFoundation\JsonResponse; use Illuminate\Support\Facades\Log; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; +use Illuminate\Support\MessageBag; + /** * This class controls all actions related to asset models for @@ -27,16 +30,15 @@ use Illuminate\Support\Facades\Log; */ class AssetModelsController extends Controller { + protected MessageBag $validatorErrors; /** * Returns a view that invokes the ajax tables which actually contains * the content for the accessories listing, which is generated in getDatatable. * * @author [A. Gianotto] [] * @since [v1.0] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View { $this->authorize('index', AssetModel::class); @@ -48,10 +50,8 @@ class AssetModelsController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { $this->authorize('create', AssetModel::class); @@ -66,16 +66,12 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param ImageUploadRequest $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(StoreAssetModelRequest $request) : RedirectResponse { $this->authorize('create', AssetModel::class); - // Create a new asset model $model = new AssetModel; - // Save the model data $model->eol = $request->input('eol'); $model->depreciation_id = $request->input('depreciation_id'); $model->name = $request->input('name'); @@ -84,7 +80,7 @@ class AssetModelsController extends Controller $model->manufacturer_id = $request->input('manufacturer_id'); $model->category_id = $request->input('category_id'); $model->notes = $request->input('notes'); - $model->user_id = Auth::id(); + $model->created_by = auth()->id(); $model->requestable = $request->has('requestable'); if ($request->input('fieldset_id') != '') { @@ -93,7 +89,6 @@ class AssetModelsController extends Controller $model = $request->handleImages($model); - // Was it created? if ($model->save()) { if ($this->shouldAddDefaultValues($request->input())) { if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){ @@ -113,21 +108,12 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param int $modelId - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($modelId = null) + public function edit(AssetModel $model) : View | RedirectResponse { $this->authorize('update', AssetModel::class); - if ($item = AssetModel::find($modelId)) { - $category_type = 'asset'; - $view = View::make('models/edit', compact('item', 'category_type')); - $view->with('depreciation_list', Helper::depreciationList()); - - return $view; - } - - return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist')); + $category_type = 'asset'; + return view('models/edit', compact('category_type'))->with('item', $model)->with('depreciation_list', Helper::depreciationList()); } @@ -142,17 +128,11 @@ class AssetModelsController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(ImageUploadRequest $request, $modelId = null) + public function update(StoreAssetModelRequest $request, AssetModel $model) : RedirectResponse { $this->authorize('update', AssetModel::class); - // Check if the model exists - if (is_null($model = AssetModel::find($modelId))) { - // Redirect to the models management page - return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist')); - } $model = $request->handleImages($model); - $model->depreciation_id = $request->input('depreciation_id'); $model->eol = $request->input('eol'); $model->name = $request->input('name'); @@ -163,20 +143,17 @@ class AssetModelsController extends Controller $model->notes = $request->input('notes'); $model->requestable = $request->input('requestable', '0'); - $this->removeCustomFieldsDefaultValues($model); - $model->fieldset_id = $request->input('fieldset_id'); - if ($this->shouldAddDefaultValues($request->input())) { - if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))){ - return redirect()->back()->withInput()->with('error', trans('admin/custom_fields/message.fieldset_default_value.error')); - } - } - - - - if ($model->save()) { + $this->removeCustomFieldsDefaultValues($model); + + if ($this->shouldAddDefaultValues($request->input())) { + if (!$this->assignCustomFieldsDefaultValues($model, $request->input('default_values'))) { + return redirect()->back()->withInput()->withErrors($this->validatorErrors); + } + } + if ($model->wasChanged('eol')) { if ($model->eol > 0) { $newEol = $model->eol; @@ -200,30 +177,17 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param int $modelId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($modelId) + public function destroy(AssetModel $model) : RedirectResponse { $this->authorize('delete', AssetModel::class); - // Check if the model exists - if (is_null($model = AssetModel::find($modelId))) { - return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist')); - } + if ($model->assets()->count() > 0) { // Throw an error that this model is associated with assets return redirect()->route('models.index')->with('error', trans('admin/models/message.assoc_users')); } - if ($model->image) { - try { - Storage::disk('public')->delete('models/'.$model->image); - } catch (\Exception $e) { - Log::info($e); - } - } - // Delete the model $model->delete(); @@ -237,10 +201,8 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param int $id - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function getRestore($id) + public function getRestore($id) : RedirectResponse { $this->authorize('create', AssetModel::class); @@ -252,10 +214,10 @@ class AssetModelsController extends Controller if ($model->restore()) { $logaction = new Actionlog(); - $logaction->item_type = User::class; + $logaction->item_type = AssetModel::class; $logaction->item_id = $model->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = Auth::user()->id; + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); @@ -282,19 +244,11 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param int $modelId - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($modelId = null) + public function show(AssetModel $model) : View | RedirectResponse { $this->authorize('view', AssetModel::class); - $model = AssetModel::withTrashed()->find($modelId); - - if (isset($model->id)) { - return view('models/view', compact('model')); - } - - return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist')); + return view('models/view', compact('model')); } /** @@ -303,25 +257,21 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param int $modelId - * @return View */ - public function getClone($modelId = null) + public function getClone(AssetModel $model) : View | RedirectResponse { $this->authorize('create', AssetModel::class); - // Check if the model exists - if (is_null($model_to_clone = AssetModel::find($modelId))) { - return redirect()->route('models.index')->with('error', trans('admin/models/message.does_not_exist')); - } - $model = clone $model_to_clone; + $cloned_model = clone $model; $model->id = null; + $model->deleted_at = null; // Show the page return view('models/edit') ->with('depreciation_list', Helper::depreciationList()) ->with('item', $model) - ->with('model_id', $model_to_clone->id) - ->with('clone_model', $model_to_clone); + ->with('model_id', $model->id) + ->with('clone_model', $cloned_model); } @@ -331,9 +281,8 @@ class AssetModelsController extends Controller * @author [B. Wetherington] [] * @since [v2.0] * @param int $modelId - * @return View */ - public function getCustomFields($modelId) + public function getCustomFields($modelId) : View { return view('models.custom_fields_form')->with('model', AssetModel::find($modelId)); } @@ -341,13 +290,12 @@ class AssetModelsController extends Controller /** - * Returns a view that allows the user to bulk edit model attrbutes + * Returns a view that allows the user to bulk edit model attributes * * @author [A. Gianotto] [] * @since [v1.7] - * @return \Illuminate\Contracts\View\View */ - public function postBulkEdit(Request $request) + public function postBulkEdit(Request $request) : View | RedirectResponse { $models_raw_array = $request->input('ids'); @@ -389,9 +337,8 @@ class AssetModelsController extends Controller * * @author [A. Gianotto] [] * @since [v1.7] - * @return \Illuminate\Contracts\View\View */ - public function postBulkEditSave(Request $request) + public function postBulkEditSave(Request $request) : RedirectResponse { $models_raw_array = $request->input('ids'); $update_array = []; @@ -429,9 +376,8 @@ class AssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param int $modelId - * @return \Illuminate\Http\RedirectResponse */ - public function postBulkDelete(Request $request) + public function postBulkDelete(Request $request) : RedirectResponse { $models_raw_array = $request->input('ids'); @@ -470,9 +416,8 @@ class AssetModelsController extends Controller * any default values were entered into the form. * * @param array $input - * @return bool */ - private function shouldAddDefaultValues(array $input) + private function shouldAddDefaultValues(array $input) : bool { return ! empty($input['add_default_values']) && ! empty($input['default_values']) @@ -484,9 +429,8 @@ class AssetModelsController extends Controller * * @param AssetModel $model * @param array $defaultValues - * @return void */ - private function assignCustomFieldsDefaultValues(AssetModel $model, array $defaultValues): bool + private function assignCustomFieldsDefaultValues(AssetModel|SnipeModel $model, array $defaultValues): bool { $data = array(); foreach ($defaultValues as $customFieldId => $defaultValue) { @@ -495,22 +439,28 @@ class AssetModelsController extends Controller $data[$customField->db_column] = $defaultValue; } - $fieldsets = $model->fieldset->validation_rules(); + $allRules = $model->fieldset->validation_rules(); $rules = array(); - foreach ($fieldsets as $fieldset => $validation){ + foreach ($allRules as $field => $validation) { // If the field is marked as required, eliminate the rule so it doesn't interfere with the default values // (we are at model level, the rule still applies when creating a new asset using this model) $index = array_search('required', $validation); if ($index !== false){ $validation[$index] = 'nullable'; } - $rules[$fieldset] = $validation; + $rules[$field] = $validation; } - $validator = Validator::make($data, $rules); + $attributes = []; + foreach ($model->fieldset->fields as $field) { + $attributes[$field->db_column] = trim(preg_replace('/_+|snipeit|\d+/', ' ', $field->db_column)); + } + + $validator = Validator::make($data, $rules)->setAttributeNames($attributes); if($validator->fails()){ + $this->validatorErrors = $validator->errors(); return false; } @@ -527,9 +477,8 @@ class AssetModelsController extends Controller /** * Removes all default values * - * @return void */ - private function removeCustomFieldsDefaultValues(AssetModel $model) + private function removeCustomFieldsDefaultValues(AssetModel|SnipeModel $model): void { $model->defaultValues()->detach(); } diff --git a/app/Http/Controllers/AssetModelsFilesController.php b/app/Http/Controllers/AssetModelsFilesController.php index c8247ffc15..14b2c1fc0b 100644 --- a/app/Http/Controllers/AssetModelsFilesController.php +++ b/app/Http/Controllers/AssetModelsFilesController.php @@ -6,8 +6,11 @@ use App\Helpers\StorageHelper; use App\Http\Requests\UploadFileRequest; use App\Models\Actionlog; use App\Models\AssetModel; -use Illuminate\Support\Facades\Response; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Response; use Illuminate\Support\Facades\Storage; +use \Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\BinaryFileResponse; class AssetModelsFilesController extends Controller { @@ -21,7 +24,7 @@ class AssetModelsFilesController extends Controller *@since [v1.0] * @author [A. Gianotto] [] */ - public function store(UploadFileRequest $request, $modelId = null) + public function store(UploadFileRequest $request, $modelId = null) : RedirectResponse { if (! $model = AssetModel::find($modelId)) { return redirect()->route('models.index')->with('error', trans('admin/hardware/message.does_not_exist')); @@ -41,10 +44,10 @@ class AssetModelsFilesController extends Controller $model->logUpload($file_name, $request->get('notes')); } - return redirect()->back()->with('success', trans('general.file_upload_success')); + return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success')); } - return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles')); + return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles')); } /** @@ -54,14 +57,10 @@ class AssetModelsFilesController extends Controller * @param int $modelId * @param int $fileId * @since [v1.0] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($modelId = null, $fileId = null) + public function show(AssetModel $model, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse { - $model = AssetModel::find($modelId); - // the asset is valid - if (isset($model->id)) { + $this->authorize('view', $model); if (! $log = Actionlog::find($fileId)) { @@ -86,12 +85,6 @@ class AssetModelsFilesController extends Controller } return StorageHelper::downloader($file); - } - // Prepare the error message - $error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]); - - // Redirect to the hardware management page - return redirect()->route('hardware.index')->with('error', $error); } /** @@ -101,33 +94,22 @@ class AssetModelsFilesController extends Controller * @param int $modelId * @param int $fileId * @since [v1.0] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($modelId = null, $fileId = null) + public function destroy(AssetModel $model, $fileId = null) : RedirectResponse { - $model = AssetModel::find($modelId); - $this->authorize('update', $model); $rel_path = 'private_uploads/assetmodels'; - - // the asset is valid - if (isset($model->id)) { - $this->authorize('update', $model); - $log = Actionlog::find($fileId); - if ($log) { - if (Storage::exists($rel_path.'/'.$log->filename)) { - Storage::delete($rel_path.'/'.$log->filename); - } - $log->delete(); - - return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success')); + $this->authorize('update', $model); + $log = Actionlog::find($fileId); + if ($log) { + if (Storage::exists($rel_path.'/'.$log->filename)) { + Storage::delete($rel_path.'/'.$log->filename); } + $log->delete(); - return redirect()->back() - ->with('success', trans('admin/hardware/message.deletefile.success')); + return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); } - // Redirect to the hardware management page - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); + return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); + } } diff --git a/app/Http/Controllers/Assets/AssetCheckinController.php b/app/Http/Controllers/Assets/AssetCheckinController.php index 73f4838a6a..3e1b75a4d7 100644 --- a/app/Http/Controllers/Assets/AssetCheckinController.php +++ b/app/Http/Controllers/Assets/AssetCheckinController.php @@ -11,10 +11,10 @@ use App\Models\Asset; use App\Models\CheckoutAcceptance; use App\Models\LicenseSeat; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Session; -use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\Log; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Validator; class AssetCheckinController extends Controller { @@ -26,22 +26,14 @@ class AssetCheckinController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @param string $backto - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v1.0] */ - public function create($assetId, $backto = null) + public function create(Asset $asset, $backto = null) : View | RedirectResponse { - // Check if the asset exists - if (is_null($asset = Asset::find($assetId))) { - // Redirect to the asset management page with error - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); - } $this->authorize('checkin', $asset); // This asset is already checked in, redirect - if (is_null($asset->assignedTo)) { return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.already_checked_in')); } @@ -50,7 +42,23 @@ class AssetCheckinController extends Controller return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix')); } - return view('hardware/checkin', compact('asset'))->with('statusLabel_list', Helper::statusLabelList())->with('backto', $backto)->with('table_name', 'Assets'); + // Invoke the validation to see if the audit will complete successfully + $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); + + if ($asset->isInvalid()) { + return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors()); + } + + $target_option = match ($asset->assigned_type) { + 'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset_previous')]), + 'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]), + default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]), + }; + return view('hardware/checkin', compact('asset', 'target_option')) + ->with('item', $asset) + ->with('statusLabel_list', Helper::statusLabelList()) + ->with('backto', $backto) + ->with('table_name', 'Assets'); } /** @@ -60,11 +68,9 @@ class AssetCheckinController extends Controller * @param AssetCheckinRequest $request * @param int $assetId * @param null $backto - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v1.0] */ - public function store(AssetCheckinRequest $request, $assetId = null, $backto = null) + public function store(AssetCheckinRequest $request, $assetId = null, $backto = null) : RedirectResponse { // Check if the asset exists if (is_null($asset = Asset::find($assetId))) { @@ -82,13 +88,14 @@ class AssetCheckinController extends Controller $this->authorize('checkin', $asset); - if ($asset->assignedType() == Asset::USER) { - $user = $asset->assignedTo; - } + session()->put('checkedInFrom', $asset->assignedTo->id); + session()->put('checkout_to_type', match ($asset->assigned_type) { + 'App\Models\User' => 'user', + 'App\Models\Location' => 'location', + 'App\Models\Asset' => 'asset', + }); $asset->expected_checkin = null; - //$asset->last_checkout = null; - $asset->last_checkin = now(); $asset->assignedTo()->disassociate($asset); $asset->accepted = null; $asset->name = $request->get('name'); @@ -97,6 +104,9 @@ class AssetCheckinController extends Controller $asset->status_id = e($request->get('status_id')); } + // Add any custom fields that should be included in the checkout + $asset->customFieldsForCheckinCheckout('display_checkin'); + $this->migrateLegacyLocations($asset); $asset->location_id = $asset->rtd_location_id; @@ -112,11 +122,14 @@ class AssetCheckinController extends Controller $originalValues = $asset->getRawOriginal(); + // Handle last checkin date $checkin_at = date('Y-m-d H:i:s'); if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) { $originalValues['action_date'] = $checkin_at; $checkin_at = $request->get('checkin_at'); + } + $asset->last_checkin = $checkin_at; $asset->licenseseats->each(function (LicenseSeat $seat) { $seat->update(['assigned_to' => null]); @@ -132,12 +145,15 @@ class AssetCheckinController extends Controller $acceptance->delete(); }); - Session::put('redirect_option', $request->get('redirect_option')); - // Was the asset updated? + session()->put('redirect_option', $request->get('redirect_option')); + + // Add any custom fields that should be included in the checkout + $asset->customFieldsForCheckinCheckout('display_checkin'); + if ($asset->save()) { - event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at, $originalValues)); - return Helper::getRedirectOption($asset, $assetId, 'Assets'); + event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues)); + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.checkin.success')); } // Redirect to the asset management page with error return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkin.error').$asset->getErrors()); diff --git a/app/Http/Controllers/Assets/AssetCheckoutController.php b/app/Http/Controllers/Assets/AssetCheckoutController.php index 36f00f0fc6..da2c0a2550 100644 --- a/app/Http/Controllers/Assets/AssetCheckoutController.php +++ b/app/Http/Controllers/Assets/AssetCheckoutController.php @@ -9,8 +9,10 @@ use App\Http\Controllers\Controller; use App\Http\Requests\AssetCheckoutRequest; use App\Models\Asset; use Illuminate\Database\Eloquent\ModelNotFoundException; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Validator; class AssetCheckoutController extends Controller { @@ -23,29 +25,35 @@ class AssetCheckoutController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] - * @return View + * @return \Illuminate\Contracts\View\View */ - public function create($assetId) + public function create(Asset $asset) : View | RedirectResponse { - // Check if the asset exists - if (is_null($asset = Asset::with('company')->find(e($assetId)))) { - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); - } $this->authorize('checkout', $asset); if (!$asset->model) { - return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix')); + return redirect()->route('hardware.show', $asset) + ->with('error', trans('admin/hardware/general.model_invalid_fix')); } + // Invoke the validation to see if the audit will complete successfully + $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); + + if ($asset->isInvalid()) { + return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors()); + } + + if ($asset->availableForCheckout()) { return view('hardware/checkout', compact('asset')) ->with('statusLabel_list', Helper::deployableStatusLabelList()) - ->with('table_name', 'Assets'); + ->with('table_name', 'Assets') + ->with('item', $asset); } - - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.checkout.not_available')); + return redirect()->route('hardware.index') + ->with('error', trans('admin/hardware/message.checkout.not_available')); } /** @@ -53,11 +61,9 @@ class AssetCheckoutController extends Controller * * @author [A. Gianotto] [] * @param AssetCheckoutRequest $request - * @param int $assetId - * @return \Illuminate\Http\RedirectResponse * @since [v1.0] */ - public function store(AssetCheckoutRequest $request, $assetId) + public function store(AssetCheckoutRequest $request, $assetId) : RedirectResponse { try { // Check if the asset exists @@ -69,10 +75,10 @@ class AssetCheckoutController extends Controller $this->authorize('checkout', $asset); if (!$asset->model) { - return redirect()->route('hardware.show', $asset->id)->with('error', trans('admin/hardware/general.model_invalid_fix')); + return redirect()->route('hardware.show', $asset)->with('error', trans('admin/hardware/general.model_invalid_fix')); } - $admin = Auth::user(); + $admin = auth()->user(); $target = $this->determineCheckoutTarget(); @@ -92,6 +98,7 @@ class AssetCheckoutController extends Controller $asset->status_id = $request->get('status_id'); } + if(!empty($asset->licenseseats->all())){ if(request('checkout_to_type') == 'user') { foreach ($asset->licenseseats as $seat){ @@ -101,22 +108,26 @@ class AssetCheckoutController extends Controller } } + // Add any custom fields that should be included in the checkout + $asset->customFieldsForCheckinCheckout('display_checkout'); + $settings = \App\Models\Setting::getSettings(); // We have to check whether $target->company_id is null here since locations don't have a company yet if (($settings->full_multiple_companies_support) && ((!is_null($target->company_id)) && (!is_null($asset->company_id)))) { if ($target->company_id != $asset->company_id){ - return redirect()->to("hardware/$assetId/checkout")->with('error', trans('general.error_user_company')); + return redirect()->route('hardware.checkout.create', $asset)->with('error', trans('general.error_user_company')); } } - Session::put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); if ($asset->checkOut($target, $admin, $checkout_at, $expected_checkin, $request->get('note'), $request->get('name'))) { - return Helper::getRedirectOption($request, $assetId, 'Assets'); + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + ->with('success', trans('admin/hardware/message.checkout.success')); } // Redirect to the asset management page with error - return redirect()->to("hardware/$assetId/checkout")->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors()); + return redirect()->route("hardware.checkout.create", $asset)->with('error', trans('admin/hardware/message.checkout.error').$asset->getErrors()); } catch (ModelNotFoundException $e) { return redirect()->back()->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($asset->getErrors()); } catch (CheckoutNotAllowed $e) { diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php index 01ff0288ef..cf119edddc 100644 --- a/app/Http/Controllers/Assets/AssetFilesController.php +++ b/app/Http/Controllers/Assets/AssetFilesController.php @@ -7,8 +7,12 @@ use App\Http\Controllers\Controller; use App\Http\Requests\UploadFileRequest; use App\Models\Actionlog; use App\Models\Asset; -use Illuminate\Support\Facades\Response; +use \Illuminate\Http\Response; use Illuminate\Support\Facades\Storage; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\BinaryFileResponse; class AssetFilesController extends Controller { @@ -22,11 +26,8 @@ class AssetFilesController extends Controller *@since [v1.0] * @author [A. Gianotto] [] */ - public function store(UploadFileRequest $request, $assetId = null) + public function store(UploadFileRequest $request, Asset $asset) : RedirectResponse { - if (! $asset = Asset::find($assetId)) { - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); - } $this->authorize('update', $asset); @@ -41,7 +42,7 @@ class AssetFilesController extends Controller $asset->logUpload($file_name, $request->get('notes')); } - return redirect()->back()->with('success', trans('admin/hardware/message.upload.success')); + return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.upload.success')); } return redirect()->back()->with('error', trans('admin/hardware/message.upload.nofiles')); @@ -54,48 +55,30 @@ class AssetFilesController extends Controller * @param int $assetId * @param int $fileId * @since [v1.0] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($assetId = null, $fileId = null) + public function show(Asset $asset, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse { - $asset = Asset::find($assetId); - // the asset is valid - if (isset($asset->id)) { - $this->authorize('view', $asset); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } + $this->authorize('view', $asset); + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { $file = 'private_uploads/assets/'.$log->filename; if ($log->action_type == 'audit') { $file = 'private_uploads/audits/'.$log->filename; } - if (! Storage::exists($file)) { - return response('File '.$file.' not found on server', 404) - ->header('Content-Type', 'text/plain'); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('hardware.show', $asset)->with('error', trans('general.file_not_found')); } - if (request('inline') == 'true') { - - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); - } - - return StorageHelper::downloader($file); } - // Prepare the error message - $error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]); - // Redirect to the hardware management page - return redirect()->route('hardware.index')->with('error', $error); + return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found')); + + } /** @@ -105,33 +88,21 @@ class AssetFilesController extends Controller * @param int $assetId * @param int $fileId * @since [v1.0] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($assetId = null, $fileId = null) + public function destroy(Asset $asset, $fileId = null) : RedirectResponse { - $asset = Asset::find($assetId); $this->authorize('update', $asset); $rel_path = 'private_uploads/assets'; - // the asset is valid - if (isset($asset->id)) { - $this->authorize('update', $asset); - $log = Actionlog::find($fileId); - if ($log) { - if (Storage::exists($rel_path.'/'.$log->filename)) { - Storage::delete($rel_path.'/'.$log->filename); - } - $log->delete(); - - return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success')); + if ($log = Actionlog::find($fileId)) { + if (Storage::exists($rel_path.'/'.$log->filename)) { + Storage::delete($rel_path.'/'.$log->filename); } - - return redirect()->back() - ->with('success', trans('admin/hardware/message.deletefile.success')); + $log->delete(); + return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); } - // Redirect to the hardware management page - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); + return redirect()->route('hardware.show', $asset)->with('error', trans('general.log_record_not_found')); } + } diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index bf3a3ec3a8..03486ffe82 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -2,9 +2,11 @@ namespace App\Http\Controllers\Assets; +use App\Events\CheckoutableCheckedIn; use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\ImageUploadRequest; +use App\Http\Requests\UpdateAssetRequest; use App\Models\Actionlog; use App\Http\Requests\UploadFileRequest; use Illuminate\Support\Facades\Log; @@ -16,18 +18,20 @@ use App\Models\Location; use App\Models\Setting; use App\Models\Statuslabel; use App\Models\User; -use Illuminate\Support\Facades\Auth; use App\View\Label; use Carbon\Carbon; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\Gate; use Illuminate\Http\Request; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; use League\Csv\Reader; -use Illuminate\Support\Facades\Redirect; +use Illuminate\Http\Response; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use TypeError; /** * This class controls all actions related to assets for @@ -55,10 +59,8 @@ class AssetsController extends Controller * @see AssetController::getDatatable() method that generates the JSON response * @since [v1.0] * @param Request $request - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index(Request $request) + public function index(Request $request) : View { $this->authorize('index', Asset::class); $company = Company::find($request->input('company_id')); @@ -72,13 +74,12 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param Request $request - * @return View * @internal param int $model_id */ - public function create(Request $request) + public function create(Request $request) : View { $this->authorize('create', Asset::class); - $view = View::make('hardware/edit') + $view = view('hardware/edit') ->with('statuslabel_list', Helper::statusLabelList()) ->with('item', new Asset) ->with('statuslabel_types', Helper::statusTypeList()); @@ -96,9 +97,8 @@ class AssetsController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return \Illuminate\Http\RedirectResponse */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : RedirectResponse { $this->authorize(Asset::class); @@ -113,8 +113,10 @@ class AssetsController extends Controller $settings = Setting::getSettings(); - $success = false; + $successes = []; + $failures = []; $serials = $request->input('serials'); + $asset = null; for ($a = 1; $a <= count($asset_tags); $a++) { $asset = new Asset(); @@ -134,7 +136,7 @@ class AssetsController extends Controller $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); $asset->notes = $request->input('notes'); - $asset->user_id = Auth::id(); + $asset->created_by = auth()->id(); $asset->status_id = request('status_id'); $asset->warranty_months = request('warranty_months', null); $asset->purchase_cost = request('purchase_cost'); @@ -147,7 +149,7 @@ class AssetsController extends Controller $asset->byod = request('byod', 0); if (! empty($settings->audit_interval)) { - $asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); + $asset->next_audit_date = Carbon::now()->addMonths((int) $settings->audit_interval)->toDateString(); } // Set location_id to rtd_location_id ONLY if the asset isn't being checked out @@ -167,7 +169,7 @@ class AssetsController extends Controller if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { if ($field->field_encrypted == '1') { - if (Gate::allows('admin')) { + if (Gate::allows('assets.view.encrypted_custom_fields')) { if (is_array($request->input($field->db_column))) { $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); } else { @@ -186,61 +188,85 @@ class AssetsController extends Controller // Validate the asset before saving if ($asset->isValid() && $asset->save()) { - if (request('assigned_user')) { - $target = User::find(request('assigned_user')); + $target = null; + $location = null; + + if ($userId = request('assigned_user')) { + $target = User::find($userId); + + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.user')); + } $location = $target->location_id; - } elseif (request('assigned_asset')) { - $target = Asset::find(request('assigned_asset')); + + } elseif ($assetId = request('assigned_asset')) { + $target = Asset::find($assetId); + + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.asset')); + } $location = $target->location_id; - } elseif (request('assigned_location')) { - $target = Location::find(request('assigned_location')); + + } elseif ($locationId = request('assigned_location')) { + $target = Location::find($locationId); + + if (!$target) { + return redirect()->back()->withInput()->with('error', trans('admin/hardware/message.create.target_not_found.location')); + } $location = $target->id; } if (isset($target)) { - $asset->checkOut($target, Auth::user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location); + $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location); } - $success = true; - + $successes[] = "" . e($asset->asset_tag) . ""; + + } else { + $failures[] = join(",", $asset->getErrors()->all()); } } - if ($success) { - Log::debug(e($asset->asset_tag)); - return redirect()->route('hardware.index') - ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset->id), 'id', 'tag' => e($asset->asset_tag)])); - - + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + + + if ($successes) { + if ($failures) { + //some succeeded, some failed + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) //FIXME - not tested + ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)])) + ->with('warning', trans_choice('admin/hardware/message.create.partial_failure', $failures, ['failures' => join("; ", $failures)])); + } else { + if (count($successes) == 1) { + //the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed + //and re-translated + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', $asset), 'id', 'tag' => e($asset->asset_tag)])); + } else { + //multi-success + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)])); + } + } + } return redirect()->back()->withInput()->withErrors($asset->getErrors()); } - public function getOptionCookie(Request $request){ - $value = $request->cookie('optional_info'); - echo $value; - return $value; - } /** * Returns a view that presents a form to edit an existing asset. * * @author [A. Gianotto] [] - * @param int $assetId * @since [v1.0] - * @return View + * @return \Illuminate\Contracts\View\View */ - public function edit($assetId = null) + public function edit(Asset $asset) : View | RedirectResponse { - if (! $item = Asset::find($assetId)) { - // Redirect to the asset management page with error - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); - } - //Handles company checks and permissions. - $this->authorize($item); - - return view('hardware/edit', compact('item')) + $this->authorize($asset); + return view('hardware/edit') + ->with('item', $asset) ->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_types', Helper::statusTypeList()); } @@ -252,17 +278,16 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] - * @return View + * @return \Illuminate\Contracts\View\View */ - public function show($assetId = null) + public function show(Asset $asset) : View | RedirectResponse { - $asset = Asset::withTrashed()->find($assetId); $this->authorize('view', $asset); $settings = Setting::getSettings(); if (isset($asset)) { $audit_log = Actionlog::where('action_type', '=', 'audit') - ->where('item_id', '=', $assetId) + ->where('item_id', '=', $asset->id) ->where('item_type', '=', Asset::class) ->orderBy('created_at', 'DESC')->first(); @@ -278,7 +303,7 @@ class AssetsController extends Controller $qr_code = (object) [ 'display' => $settings->qr_code == '1', - 'url' => route('qr_code/hardware', $asset->id), + 'url' => route('qr_code/hardware', $asset), ]; return view('hardware/view', compact('asset', 'qr_code', 'settings')) @@ -292,17 +317,12 @@ class AssetsController extends Controller * Validate and process asset edit form. * * @param int $assetId - * @return \Illuminate\Http\RedirectResponse|Redirect * @since [v1.0] * @author [A. Gianotto] [] */ - public function update(ImageUploadRequest $request, $assetId = null) + public function update(ImageUploadRequest $request, Asset $asset) : RedirectResponse { - // Check if the asset exists - if (! $asset = Asset::find($assetId)) { - // Redirect to the asset management page with error - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); - } + $this->authorize($asset); $asset->status_id = $request->input('status_id', null); @@ -316,7 +336,7 @@ class AssetsController extends Controller $asset->eol_explicit = false; } elseif ($request->filled('asset_eol_date')) { $asset->asset_eol_date = $request->input('asset_eol_date', null); - $months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date); + $months = (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true); if($asset->model->eol) { if($months != $asset->model->eol > 0) { $asset->eol_explicit = true; @@ -332,23 +352,22 @@ class AssetsController extends Controller } $asset->supplier_id = $request->input('supplier_id', null); $asset->expected_checkin = $request->input('expected_checkin', null); - - // If the box isn't checked, it's not in the request at all. - $asset->requestable = $request->filled('requestable'); + $asset->requestable = $request->input('requestable', 0); $asset->rtd_location_id = $request->input('rtd_location_id', null); $asset->byod = $request->input('byod', 0); - $status = Statuslabel::find($asset->status_id); + $status = Statuslabel::find($request->input('status_id')); - if($status->archived){ + // This is an archived or undeployable - we should check the asset back in. + // Pending is allowed here + if (($status) && (($status->getStatuslabelType() != 'pending') && ($status->getStatuslabelType() != 'deployable')) && ($target = $asset->assignedTo)) { + $originalValues = $asset->getRawOriginal(); $asset->assigned_to = null; + $asset->assigned_type = null; + $asset->accepted = null; + event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on asset update with '.$status->getStatuslabelType().' status', date('Y-m-d H:i:s'), $originalValues)); } - if ($asset->assigned_to == '') { - $asset->location_id = $request->input('rtd_location_id', null); - } - - if ($request->filled('image_delete')) { try { unlink(public_path().'/uploads/assets/'.$asset->image); @@ -359,46 +378,60 @@ class AssetsController extends Controller } // Update the asset data - $asset_tag = $request->input('asset_tags'); + $serial = $request->input('serials'); + $asset->serial = $request->input('serials'); + + if (is_array($request->input('serials'))) { + $asset->serial = $serial[1]; + } + $asset->name = $request->input('name'); - $asset->serial = $serial[1]; $asset->company_id = Company::getIdForCurrentUser($request->input('company_id')); $asset->model_id = $request->input('model_id'); $asset->order_number = $request->input('order_number'); - $asset->asset_tag = $asset_tag[1]; + + $asset_tags = $request->input('asset_tags'); + $asset->asset_tag = $request->input('asset_tags'); + + if (is_array($request->input('asset_tags'))) { + $asset->asset_tag = $asset_tags[1]; + } + $asset->notes = $request->input('notes'); $asset = $request->handleImages($asset); // Update custom fields in the database. - // Validation for these fields is handlded through the AssetRequest form request // FIXME: No idea why this is returning a Builder error on db_column_name. // Need to investigate and fix. Using static method for now. $model = AssetModel::find($request->get('model_id')); if (($model) && ($model->fieldset)) { foreach ($model->fieldset->fields as $field) { - if ($field->field_encrypted == '1') { - if (Gate::allows('admin')) { - if (is_array($request->input($field->db_column))) { - $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); - } else { - $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); + if ($request->has($field->db_column)) { + if ($field->field_encrypted == '1') { + if (Gate::allows('assets.view.encrypted_custom_fields')) { + if (is_array($request->input($field->db_column))) { + $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); + } else { + $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); + } } - } - } else { - if (is_array($request->input($field->db_column))) { - $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); } else { - $asset->{$field->db_column} = $request->input($field->db_column); + if (is_array($request->input($field->db_column))) { + $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); + } else { + $asset->{$field->db_column} = $request->input($field->db_column); + } } } } } + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); if ($asset->save()) { - return redirect()->route('hardware.show', $assetId) + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) ->with('success', trans('admin/hardware/message.update.success')); } @@ -411,9 +444,8 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] - * @return \Illuminate\Http\RedirectResponse */ - public function destroy($assetId) + public function destroy(Request $request, $assetId) : RedirectResponse { // Check if the asset exists if (is_null($asset = Asset::find($assetId))) { @@ -423,9 +455,17 @@ class AssetsController extends Controller $this->authorize('delete', $asset); - DB::table('assets') - ->where('id', $asset->id) - ->update(['assigned_to' => null]); + if ($asset->assignedTo) { + + $target = $asset->assignedTo; + $checkin_at = date('Y-m-d H:i:s'); + $originalValues = $asset->getRawOriginal(); + event(new CheckoutableCheckedIn($asset, $target, auth()->user(), 'Checkin on delete', $checkin_at, $originalValues)); + DB::table('assets') + ->where('id', $asset->id) + ->update(['assigned_to' => null, 'assigned_type' => null]); + } + if ($asset->image) { try { @@ -435,6 +475,7 @@ class AssetsController extends Controller } } + $asset->delete(); return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.delete.success')); @@ -445,9 +486,8 @@ class AssetsController extends Controller * * @author [A. Gianotto] [] * @since [v3.0] - * @return \Illuminate\Http\RedirectResponse */ - public function getAssetBySerial(Request $request) + public function getAssetBySerial(Request $request) : RedirectResponse { $topsearch = ($request->get('topsearch')=="true"); @@ -465,14 +505,21 @@ class AssetsController extends Controller * @since [v3.0] * @return \Illuminate\Http\RedirectResponse */ - public function getAssetByTag(Request $request, $tag=null) + public function getAssetByTag(Request $request, $tag=null) : RedirectResponse { $tag = $tag ? $tag : $request->get('assetTag'); $topsearch = ($request->get('topsearch') == 'true'); - if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) { - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); + // Search for an exact and unique asset tag match + $assets = Asset::where('asset_tag', '=', $tag); + + // If not a unique result, redirect to the index view + if ($assets->count() != 1) { + return redirect()->route('hardware.index') + ->with('search', $tag) + ->with('warning', trans('admin/hardware/message.does_not_exist_var', [ 'asset_tag' => $tag ])); } + $asset = $assets->first(); $this->authorize('view', $asset); return redirect()->route('hardware.show', $asset->id)->with('topsearch', $topsearch); @@ -485,16 +532,15 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] - * @return Response */ - public function getQrCode($assetId = null) + public function getQrCode(Asset $asset) : Response | BinaryFileResponse | string | bool { $settings = Setting::getSettings(); - if ($settings->qr_code == '1') { - $asset = Asset::withTrashed()->find($assetId); + if ($settings->label2_2d_type !== 'none') { + if ($asset) { - $size = Helper::barcodeDimensions($settings->barcode_type); + $size = Helper::barcodeDimensions($settings->label2_2d_type); $qr_file = public_path().'/uploads/barcodes/qr-'.str_slug($asset->asset_tag).'-'.str_slug($asset->id).'.png'; if (isset($asset->id, $asset->asset_tag)) { @@ -504,7 +550,7 @@ class AssetsController extends Controller return response()->file($qr_file, $header); } else { $barcode = new \Com\Tecnick\Barcode\Barcode(); - $barcode_obj = $barcode->getBarcodeObj($settings->barcode_type, route('hardware.show', $asset->id), $size['height'], $size['width'], 'black', [-2, -2, -2, -2]); + $barcode_obj = $barcode->getBarcodeObj($settings->label2_2d_type, route('hardware.show', $asset->id), $size['height'], $size['width'], 'black', [-2, -2, -2, -2]); file_put_contents($qr_file, $barcode_obj->getPngData()); return response($barcode_obj->getPngData())->header('Content-type', 'image/png'); @@ -514,6 +560,7 @@ class AssetsController extends Controller return 'That asset is invalid'; } + return false; } /** @@ -528,7 +575,7 @@ class AssetsController extends Controller { $settings = Setting::getSettings(); if ($asset = Asset::withTrashed()->find($assetId)) { - $barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->alt_barcode).'-'.str_slug($asset->asset_tag).'.png'; + $barcode_file = public_path().'/uploads/barcodes/'.str_slug($settings->label2_1d_type).'-'.str_slug($asset->asset_tag).'.png'; if (isset($asset->id, $asset->asset_tag)) { if (file_exists($barcode_file)) { @@ -541,11 +588,11 @@ class AssetsController extends Controller $barcode = new \Com\Tecnick\Barcode\Barcode(); try { - $barcode_obj = $barcode->getBarcodeObj($settings->alt_barcode, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50); + $barcode_obj = $barcode->getBarcodeObj($settings->label2_1d_type, $asset->asset_tag, ($barcode_width < 300 ? $barcode_width : 300), 50); file_put_contents($barcode_file, $barcode_obj->getPngData()); return response($barcode_obj->getPngData())->header('Content-type', 'image/png'); - } catch (\Exception $e) { + } catch (\Exception|TypeError $e) { Log::debug('The barcode format is invalid.'); return response(file_get_contents(public_path('uploads/barcodes/invalid_barcode.gif')))->header('Content-type', 'image/gif'); @@ -561,7 +608,7 @@ class AssetsController extends Controller * * @author [L. Swartzendruber] [ * @param int $assetId - * @return View + * @return \Illuminate\Contracts\View\View */ public function getLabel($assetId = null) { @@ -585,28 +632,22 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] - * @return View + * @return \Illuminate\Contracts\View\View */ - public function getClone($assetId = null) + public function getClone(Asset $asset) { - // Check if the asset exists - if (is_null($asset_to_clone = Asset::find($assetId))) { - // Redirect to the asset management page - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); - } - - $this->authorize('create', $asset_to_clone); - - $asset = clone $asset_to_clone; - $asset->id = null; - $asset->asset_tag = ''; - $asset->serial = ''; - $asset->assigned_to = ''; + $this->authorize('create', $asset); + $cloned = clone $asset; + $cloned->id = null; + $cloned->asset_tag = ''; + $cloned->serial = ''; + $cloned->assigned_to = ''; + $cloned->deleted_at = ''; return view('hardware/edit') ->with('statuslabel_list', Helper::statusLabelList()) ->with('statuslabel_types', Helper::statusTypeList()) - ->with('item', $asset); + ->with('item', $cloned); } /** @@ -614,7 +655,7 @@ class AssetsController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return View + * @return \Illuminate\Contracts\View\View */ public function getImportHistory() { @@ -636,7 +677,7 @@ class AssetsController extends Controller * * @author [A. Gianotto] [] * @since [v3.3] - * @return View + * @return \Illuminate\Contracts\View\View */ public function postImportHistory(Request $request) { @@ -730,8 +771,8 @@ class AssetsController extends Controller Actionlog::firstOrCreate([ 'item_id' => $asset->id, 'item_type' => Asset::class, - 'user_id' => Auth::user()->id, - 'note' => 'Checkout imported by '.Auth::user()->present()->fullName().' from history importer', + 'created_by' => auth()->id(), + 'note' => 'Checkout imported by '.auth()->user()->present()->fullName().' from history importer', 'target_id' => $item[$asset_tag][$batch_counter]['user_id'], 'target_type' => User::class, 'created_at' => $item[$asset_tag][$batch_counter]['checkout_date'], @@ -758,8 +799,8 @@ class AssetsController extends Controller Actionlog::firstOrCreate([ 'item_id' => $item[$asset_tag][$batch_counter]['asset_id'], 'item_type' => Asset::class, - 'user_id' => Auth::user()->id, - 'note' => 'Checkin imported by '.Auth::user()->present()->fullName().' from history importer', + 'created_by' => auth()->id(), + 'note' => 'Checkin imported by '.auth()->user()->present()->fullName().' from history importer', 'target_id' => null, 'created_at' => $checkin_date, 'action_type' => 'checkin', @@ -796,7 +837,7 @@ class AssetsController extends Controller * @author [A. Gianotto] [] * @param int $assetId * @since [v1.0] - * @return View + * @return \Illuminate\Contracts\View\View */ public function getRestore($assetId = null) { @@ -826,8 +867,8 @@ class AssetsController extends Controller public function quickScan() { $this->authorize('audit', Asset::class); - $dt = Carbon::now()->addMonths(12)->toDateString(); - + $settings = Setting::getSettings(); + $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); return view('hardware/quickscan')->with('next_audit_date', $dt); } @@ -835,18 +876,9 @@ class AssetsController extends Controller { $this->authorize('checkin', Asset::class); - return view('hardware/quickscan-checkin'); + return view('hardware/quickscan-checkin')->with('statusLabel_list', Helper::statusLabelList()); } - public function audit($id) - { - $settings = Setting::getSettings(); - $this->authorize('audit', Asset::class); - $dt = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); - $asset = Asset::findOrFail($id); - - return view('hardware/audit')->with('asset', $asset)->with('next_audit_date', $dt)->with('locations_list'); - } public function dueForAudit() { @@ -863,22 +895,73 @@ class AssetsController extends Controller } - public function auditStore(UploadFileRequest $request, $id) + public function audit(Asset $asset): View | RedirectResponse { $this->authorize('audit', Asset::class); + $settings = Setting::getSettings(); - $rules = [ - 'location_id' => 'exists:locations,id|nullable|numeric', - 'next_audit_date' => 'date|nullable', - ]; - $validator = Validator::make($request->all(), $rules); + // Invoke the validation to see if the audit will complete successfully + $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); - if ($validator->fails()) { - return response()->json(Helper::formatStandardApiResponse('error', null, $validator->errors()->all())); + if ($asset->isInvalid()) { + return redirect()->route('hardware.edit', $asset)->withErrors($asset->getErrors()); } - $asset = Asset::findOrFail($id); + $dt = Carbon::now()->addMonths( (int) $settings->audit_interval)->toDateString(); + return view('hardware/audit')->with('asset', $asset)->with('item', $asset)->with('next_audit_date', $dt)->with('locations_list'); + } + + public function auditStore(UploadFileRequest $request, Asset $asset) + { + + $this->authorize('audit', Asset::class); + + session()->put('redirect_option', $request->get('redirect_option')); + session()->put('other_redirect', 'audit'); + + + $originalValues = $asset->getRawOriginal(); + + $asset->next_audit_date = $request->input('next_audit_date'); + $asset->last_audit_date = date('Y-m-d H:i:s'); + + // Check to see if they checked the box to update the physical location, + // not just note it in the audit notes + if ($request->input('update_location') == '1') { + $asset->location_id = $request->input('location_id'); + } + + // Update custom fields in the database + if (($asset->model) && ($asset->model->fieldset)) { + foreach ($asset->model->fieldset->fields as $field) { + if (($field->display_audit=='1') && ($request->has($field->db_column))) { + if ($field->field_encrypted == '1') { + if (Gate::allows('assets.view.encrypted_custom_fields')) { + if (is_array($request->input($field->db_column))) { + $asset->{$field->db_column} = Crypt::encrypt(implode(', ', $request->input($field->db_column))); + } else { + $asset->{$field->db_column} = Crypt::encrypt($request->input($field->db_column)); + } + } + } else { + if (is_array($request->input($field->db_column))) { + $asset->{$field->db_column} = implode(', ', $request->input($field->db_column)); + } else { + $asset->{$field->db_column} = $request->input($field->db_column); + } + } + } + } + } + + // Invoke the validation to see if the audit will complete successfully + $asset->setRules($asset->getRules() + $asset->customFieldValidationRules()); + + // Validate the rest of the data before we turn off the event dispatcher + if ($asset->isInvalid()) { + return redirect()->back()->withInput()->withErrors($asset->getErrors()); + } /** * Even though we do a save() further down, we don't want to log this as a "normal" asset update, @@ -894,18 +977,11 @@ class AssetsController extends Controller * which manually invokes Watson Validating to make sure the asset's model is valid. * * @see \App\Observers\AssetObserver::updating() + * @see \App\Models\Asset::save() */ + $asset->unsetEventDispatcher(); - $asset->next_audit_date = $request->input('next_audit_date'); - $asset->last_audit_date = date('Y-m-d H:i:s'); - - // Check to see if they checked the box to update the physical location, - // not just note it in the audit notes - if ($request->input('update_location') == '1') { - $asset->location_id = $request->input('location_id'); - } - /** * Invoke Watson Validating to check the asset itself and check to make sure it saved correctly. @@ -919,8 +995,8 @@ class AssetsController extends Controller $file_name = $request->handleFile('private_uploads/audits/', 'audit-'.$asset->id, $request->file('image')); } - $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name); - return redirect()->route('assets.audit.due')->with('success', trans('admin/hardware/message.audit.success')); + $asset->logAudit($request->input('note'), $request->input('location_id'), $file_name, $originalValues); + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets'))->with('success', trans('admin/hardware/message.audit.success')); } return redirect()->back()->withInput()->withErrors($asset->getErrors()); diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php index ffbf81944b..5ef1539392 100644 --- a/app/Http/Controllers/Assets/BulkAssetsController.php +++ b/app/Http/Controllers/Assets/BulkAssetsController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers\Assets; -use App\Models\Actionlog; use App\Helpers\Helper; use App\Http\Controllers\CheckInOutRequest; use App\Http\Controllers\Controller; @@ -11,15 +10,17 @@ use App\Models\AssetModel; use App\Models\Statuslabel; use App\Models\Setting; use App\View\Label; +use Carbon\Carbon; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Facades\Session; use App\Http\Requests\AssetCheckoutRequest; use App\Models\CustomField; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Database\Eloquent\ModelNotFoundException; class BulkAssetsController extends Controller { @@ -36,12 +37,10 @@ class BulkAssetsController extends Controller * action would make a lot more sense here and make things a lot more clear. * * @author [A. Gianotto] [] - * @return View * @internal param int $assetId * @since [v2.0] - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit(Request $request) + public function edit(Request $request) : View | RedirectResponse { $this->authorize('view', Asset::class); @@ -54,6 +53,25 @@ class BulkAssetsController extends Controller $asset_ids = $request->input('ids'); + if ($request->input('bulk_actions') === 'checkout') { + $status_check =$this->hasUndeployableStatus($asset_ids); + if($status_check && $status_check['status'] === true){ + + $asset_tags = implode(', ', array_column($status_check['tags'], 'asset_tag')); + $asset_ids = $status_check['asset_ids']; + + session()->flash('warning', trans('admin/hardware/message.undeployable', ['asset_tags' => $asset_tags])); + } + + $request->session()->flashInput(['selected_assets' => $asset_ids]); + return redirect()->route('hardware.bulkcheckout.show'); + } + + if ($request->input('bulk_actions') === 'maintenance') { + $request->session()->flashInput(['selected_assets' => $asset_ids]); + return redirect()->route('maintenances.create'); + } + // Figure out where we need to send the user after the update is complete, and store that in the session $bulk_back_url = request()->headers->get('referer'); session(['bulk_back_url' => $bulk_back_url]); @@ -94,7 +112,9 @@ class BulkAssetsController extends Controller // This handles all of the pivot sorting below (versus the assets.* fields in the allowed_columns array) $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.id'; - $assets = Asset::with('assignedTo', 'location', 'model')->whereIn('assets.id', $asset_ids); + $assets = Asset::with('assignedTo', 'location', 'model') + ->whereIn('assets.id', $asset_ids) + ->withTrashed(); $assets = $assets->get(); @@ -194,7 +214,7 @@ class BulkAssetsController extends Controller * @internal param array $assets * @since [v2.0] */ - public function update(Request $request) + public function update(Request $request) : RedirectResponse { $this->authorize('update', Asset::class); $has_errors = 0; @@ -209,6 +229,21 @@ class BulkAssetsController extends Controller $custom_field_columns = CustomField::all()->pluck('db_column')->toArray(); + // find custom field input attributes that start with 'null_' + $null_custom_fields_inputs = array_filter($request->all(), function ($key) { + // filter out all keys that start with 'null_' + return (strpos($key, 'null_') === 0); + }, ARRAY_FILTER_USE_KEY);; + // remove 'null' from the keys + $custom_fields_to_null = []; + foreach ($null_custom_fields_inputs as $key => $value) { + $custom_fields_to_null[str_replace('null', '', $key)] = $value; + } + + + + + if (! $request->filled('ids') || count($request->input('ids')) == 0) { return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.update.no_assets_selected')); @@ -227,7 +262,8 @@ class BulkAssetsController extends Controller * its checkout status. */ - if (($request->filled('purchase_date')) + if (($request->filled('name')) + || ($request->filled('purchase_date')) || ($request->filled('expected_checkin')) || ($request->filled('purchase_cost')) || ($request->filled('supplier_id')) @@ -239,10 +275,14 @@ class BulkAssetsController extends Controller || ($request->filled('status_id')) || ($request->filled('model_id')) || ($request->filled('next_audit_date')) + || ($request->filled('asset_eol_date')) + || ($request->filled('null_name')) || ($request->filled('null_purchase_date')) || ($request->filled('null_expected_checkin_date')) || ($request->filled('null_next_audit_date')) + || ($request->filled('null_asset_eol_date')) || ($request->anyFilled($custom_field_columns)) + || ($request->anyFilled(array_keys($null_custom_fields_inputs))) ) { // Let's loop through those assets and build an update array @@ -251,28 +291,58 @@ class BulkAssetsController extends Controller $this->update_array = []; /** - * Leave out model_id and status here because we do math on that later. We have to do some extra - * validation and checks on those two. + * Leave out model_id and status here because we do math on that later. We have to do some + * extra validation and checks on those two. * * It's tempting to make these match the request check above, but some of these values require * extra work to make sure the data makes sense. */ - $this->conditionallyAddItem('purchase_date') + $this->conditionallyAddItem('name') + ->conditionallyAddItem('purchase_date') ->conditionallyAddItem('expected_checkin') ->conditionallyAddItem('order_number') ->conditionallyAddItem('requestable') ->conditionallyAddItem('supplier_id') ->conditionallyAddItem('warranty_months') - ->conditionallyAddItem('next_audit_date'); + ->conditionallyAddItem('next_audit_date') + ->conditionallyAddItem('asset_eol_date'); foreach ($custom_field_columns as $key => $custom_field_column) { $this->conditionallyAddItem($custom_field_column); } + foreach ($custom_fields_to_null as $key => $custom_field_to_null) { + $this->conditionallyAddItem($key); + } + if (!($asset->eol_explicit)) { + if ($request->filled('model_id')) { + $model = AssetModel::find($request->input('model_id')); + if ($model->eol > 0) { + if ($request->filled('purchase_date')) { + $this->update_array['asset_eol_date'] = Carbon::parse($request->input('purchase_date'))->addMonths($model->eol)->format('Y-m-d'); + } else { + $this->update_array['asset_eol_date'] = Carbon::parse($asset->purchase_date)->addMonths($model->eol)->format('Y-m-d'); + } + } else { + $this->update_array['asset_eol_date'] = null; + } + } elseif (($request->filled('purchase_date')) && ($asset->model->eol > 0)) { + $this->update_array['asset_eol_date'] = Carbon::parse($request->input('purchase_date'))->addMonths($asset->model->eol)->format('Y-m-d'); + } + } + /** * Blank out fields that were requested to be blanked out via checkbox */ + if ($request->input('null_name')=='1') { + + $this->update_array['name'] = null; + } + if ($request->input('null_purchase_date')=='1') { $this->update_array['purchase_date'] = null; + if (!($asset->eol_explicit)) { + $this->update_array['asset_eol_date'] = null; + } } if ($request->input('null_expected_checkin_date')=='1') { @@ -283,6 +353,17 @@ class BulkAssetsController extends Controller $this->update_array['next_audit_date'] = null; } + if ($request->input('null_asset_eol_date')=='1') { + $this->update_array['asset_eol_date'] = null; + + // If they are nulling the EOL date to allow it to calculate, set eol explicit to 0 + if ($request->input('calc_eol')=='1') { + $this->update_array['eol_explicit'] = 0; + } + } + + + if ($request->filled('purchase_cost')) { $this->update_array['purchase_cost'] = $request->input('purchase_cost'); } @@ -311,16 +392,22 @@ class BulkAssetsController extends Controller * to someone/something. */ if ($request->filled('status_id')) { - $updated_status = Statuslabel::find($request->input('status_id')); + try { + $updated_status = Statuslabel::findOrFail($request->input('status_id')); + } catch (ModelNotFoundException $e) { + return redirect($bulk_back_url)->with('error', trans('admin/statuslabels/message.does_not_exist')); + } // We cannot assign a non-deployable status type if the asset is already assigned. // This could probably be added to a form request. // If the asset isn't assigned, we don't care what the status is. // Otherwise we need to make sure the status type is still a deployable one. - if ( - ($asset->assigned_to == '') - || ($updated_status->deployable == '1') && ($asset->assetstatus->deployable == '1') - ) { + + $unassigned = $asset->assigned_to == ''; + $deployable = $updated_status->deployable == '1' && $asset->assetstatus?->deployable == '1'; + $pending = $updated_status->pending === 1; + + if ($unassigned || $deployable || $pending) { $this->update_array['status_id'] = $updated_status->id; } @@ -372,6 +459,7 @@ class BulkAssetsController extends Controller } /** + * * Start all the custom fields shenanigans */ @@ -379,6 +467,15 @@ class BulkAssetsController extends Controller if ($asset->model->fieldset) { foreach ($asset->model->fieldset->fields as $field) { + // null custom fields + if ($custom_fields_to_null) { + foreach ($custom_fields_to_null as $key => $custom_field_to_null) { + if ($field->db_column == $key) { + $this->update_array[$field->db_column] = null; + } + } + } + if ((array_key_exists($field->db_column, $this->update_array)) && ($field->field_encrypted == '1')) { if (Gate::allows('admin')) { $decrypted_old = Helper::gracefulDecrypt($field, $asset->{$field->db_column}); @@ -455,9 +552,8 @@ class BulkAssetsController extends Controller /** * Adds parameter to update array for an item if it exists in request * @param string $field field name - * @return BulkAssetsController Model for Chaining */ - protected function conditionallyAddItem($field) + protected function conditionallyAddItem($field) : BulkAssetsController { if (request()->filled($field)) { $this->update_array[$field] = request()->input($field); @@ -471,61 +567,62 @@ class BulkAssetsController extends Controller * * @author [A. Gianotto] [] * @param Request $request - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException * @internal param array $assets * @since [v2.0] */ - public function destroy(Request $request) + public function destroy(Request $request) : RedirectResponse { $this->authorize('delete', Asset::class); $bulk_back_url = route('hardware.index'); + if ($request->session()->has('bulk_back_url')) { $bulk_back_url = $request->session()->pull('bulk_back_url'); } + $assetIds = $request->get('ids'); - if ($request->filled('ids')) { - $assets = Asset::find($request->get('ids')); - foreach ($assets as $asset) { - $update_array['deleted_at'] = date('Y-m-d H:i:s'); - $update_array['assigned_to'] = null; - - DB::table('assets') - ->where('id', $asset->id) - ->update($update_array); - } // endforeach - - return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success')); - // no values given, nothing to update + if(empty($assetIds)) { + return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated')); } - return redirect($bulk_back_url)->with('error', trans('admin/hardware/message.delete.nothing_updated')); + $assignedAssets = Asset::whereIn('id', $assetIds)->whereNotNull('assigned_to')->get(); + if($assignedAssets->isNotEmpty()) { + + //if assets are checked out, return a list of asset tags that would need to be checked in first. + $assetTags = $assignedAssets->pluck('asset_tag')->implode(', '); + return redirect($bulk_back_url)->with('error', trans_choice('admin/hardware/message.delete.assigned_to_error', $assignedAssets->count(), ['asset_tag' => $assetTags] )); + } + + foreach (Asset::wherein('id', $assetIds)->get() as $asset) { + $asset->delete(); + } + + return redirect($bulk_back_url)->with('success', trans('admin/hardware/message.delete.success')); + // no values given, nothing to update + } /** * Show Bulk Checkout Page - * @return View View to checkout multiple assets */ - public function showCheckout() + public function showCheckout() : View { $this->authorize('checkout', Asset::class); - // Filter out assets that are not deployable. - return view('hardware/bulk-checkout'); + $do_not_change = ['' => trans('general.do_not_change')]; + $status_label_list = $do_not_change + Helper::deployableStatusLabelList(); + return view('hardware/bulk-checkout')->with('statusLabel_list', $status_label_list); } /** * Process Multiple Checkout Request - * @return View */ - public function storeCheckout(AssetCheckoutRequest $request) + public function storeCheckout(AssetCheckoutRequest $request) : RedirectResponse | ModelNotFoundException { - $this->authorize('checkout', Asset::class); try { - $admin = Auth::user(); + $admin = auth()->user(); $target = $this->determineCheckoutTarget(); @@ -535,6 +632,8 @@ class BulkAssetsController extends Controller $asset_ids = array_filter($request->get('selected_assets')); + $assets = Asset::findOrFail($asset_ids); + if (request('checkout_to_type') == 'asset') { foreach ($asset_ids as $asset_id) { if ($target->id == $asset_id) { @@ -544,57 +643,87 @@ class BulkAssetsController extends Controller } $checkout_at = date('Y-m-d H:i:s'); if (($request->filled('checkout_at')) && ($request->get('checkout_at') != date('Y-m-d'))) { - $checkout_at = e($request->get('checkout_at')); + $checkout_at = $request->get('checkout_at'); } $expected_checkin = ''; if ($request->filled('expected_checkin')) { - $expected_checkin = e($request->get('expected_checkin')); + $expected_checkin = $request->get('expected_checkin'); } $errors = []; - DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, $errors, $asset_ids, $request) { - foreach ($asset_ids as $asset_id) { - $asset = Asset::findOrFail($asset_id); + DB::transaction(function () use ($target, $admin, $checkout_at, $expected_checkin, &$errors, $assets, $request) { //NOTE: $errors is passsed by reference! + foreach ($assets as $asset) { $this->authorize('checkout', $asset); - $error = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null); - - if ($target->location_id != '') { - $asset->location_id = $target->location_id; - $asset->unsetEventDispatcher(); - $asset->save(); + // See if there is a status label passed + if ($request->filled('status_id')) { + $asset->status_id = $request->get('status_id'); } - if ($error) { - array_merge_recursive($errors, $asset->getErrors()->toArray()); + $checkout_success = $asset->checkOut($target, $admin, $checkout_at, $expected_checkin, e($request->get('note')), $asset->name, null); + + //TODO - I think this logic is duplicated in the checkOut method? + if ($target->location_id != '') { + $asset->location_id = $target->location_id; + // TODO - I don't know why this is being saved without events + $asset::withoutEvents(function () use ($asset) { + $asset->save(); + }); + } + + if (!$checkout_success) { + $errors = array_merge_recursive($errors, $asset->getErrors()->toArray()); } } }); if (! $errors) { // Redirect to the new asset page - return redirect()->to('hardware')->with('success', trans('admin/hardware/message.checkout.success')); + return redirect()->to('hardware')->with('success', trans_choice('admin/hardware/message.multi-checkout.success', $asset_ids)); } // Redirect to the asset management page with error - return redirect()->route('hardware.bulkcheckout.show')->with('error', trans('admin/hardware/message.checkout.error'))->withErrors($errors); + return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $asset_ids))->withErrors($errors); } catch (ModelNotFoundException $e) { - return redirect()->route('hardware.bulkcheckout.show')->with('error', $e->getErrors()); + return redirect()->route('hardware.bulkcheckout.show')->withInput()->with('error', trans_choice('admin/hardware/message.multi-checkout.error', $request->input('selected_assets'))); } } - public function restore(Request $request) { + public function restore(Request $request) : RedirectResponse + { $this->authorize('update', Asset::class); - $assetIds = $request->get('ids'); - if (empty($assetIds)) { - return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated')); + $assetIds = $request->get('ids'); + + if (empty($assetIds)) { + return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.restore.nothing_updated')); } else { foreach ($assetIds as $key => $assetId) { - $asset = Asset::withTrashed()->find($assetId); - $asset->restore(); + $asset = Asset::withTrashed()->find($assetId); + $asset->restore(); } - return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success')); + return redirect()->route('hardware.index')->with('success', trans('admin/hardware/message.restore.success')); } } + public function hasUndeployableStatus (array $asset_ids) + { + $undeployable = Asset::whereIn('id', $asset_ids) + ->undeployable() + ->get(); + + $undeployableTags = $undeployable->map(function ($asset) { + return [ + 'id' => $asset->id, + 'asset_tag' => $asset->asset_tag, + ]; + })->toArray(); + + $undeployableIds = array_column($undeployableTags, 'id'); + $filtered_ids = array_diff($asset_ids, $undeployableIds); + + if($undeployable->isNotEmpty()) { + return ['status' => true, 'tags' => $undeployableTags, 'asset_ids' => $filtered_ids]; + } + return false; + } } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index a7322e7e59..f385c054c6 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -50,14 +50,14 @@ class ForgotPasswordController extends Controller */ public function sendResetLinkEmail(Request $request) { - /** * Let's set a max character count here to prevent potential * buffer overflow issues with attackers sending very large - * payloads through. + * payloads through. The addition of the string rule prevents attackers + * sending arrays through and causing 500s */ $request->validate([ - 'username' => ['required', 'max:255'], + 'username' => ['required', 'max:255', 'string'], ]); /** diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index ac19a57a72..0b87865455 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -127,7 +127,7 @@ class LoginController extends Controller $saml->clearData(); } - if ($user = Auth::user()) { + if ($user = auth()->user()) { $user->last_login = \Carbon::now(); $user->saveQuietly(); } @@ -206,6 +206,7 @@ class LoginController extends Controller $user->password = bcrypt($request->input('password')); } + $user->last_login = \Carbon::now(); $user->email = $ldap_attr['email']; $user->first_name = $ldap_attr['firstname']; $user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc. @@ -226,7 +227,7 @@ class LoginController extends Controller $strip_prefixes = [ // IIS/AD - // https://github.com/snipe/snipe-it/pull/5862 + // https://github.com/grokability/snipe-it/pull/5862 '\\', // Google Cloud IAP @@ -283,8 +284,11 @@ class LoginController extends Controller return redirect()->back()->withInput()->withErrors($validator); } - $this->maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts'); - $this->lockoutTime = config('auth.passwords.users.throttle.lockout_duration'); + // Set the custom lockout attempts from the env and sett the custom lockout throttle from the env. + // We divide decayMinutes by 60 here to get minutes, since Laravel changed the default from minutes + // to seconds, and we don't want to break limits on existing systems + $this->maxAttempts = config('auth.passwords.users.throttle.max_attempts'); + $this->decayMinutes = (config('auth.passwords.users.throttle.lockout_duration') / 60); if ($lockedOut = $this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); @@ -326,7 +330,7 @@ class LoginController extends Controller } } - if ($user = Auth::user()) { + if ($user = auth()->user()) { $user->last_login = \Carbon::now(); $user->activated = 1; $user->saveQuietly(); @@ -350,11 +354,11 @@ class LoginController extends Controller } $settings = Setting::getSettings(); - $user = Auth::user(); + $user = auth()->user(); // 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. + // but let's 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. @@ -398,7 +402,7 @@ class LoginController extends Controller return redirect()->route('login')->with('error', trans('auth/general.login_prompt')); } - $user = Auth::user(); + $user = auth()->user(); // Check whether there is a device enrolled. // This *should* be handled via the \App\Http\Middleware\CheckForTwoFactor middleware @@ -427,11 +431,12 @@ class LoginController extends Controller return redirect()->route('two-factor')->with('error', trans('auth/message.two_factor.code_required')); } - $user = Auth::user(); + $user = auth()->user(); $secret = $request->input('two_factor_secret'); if (Google2FA::verifyKey($user->two_factor_secret, $secret)) { $user->two_factor_enrolled = 1; + $user->last_login = \Carbon::now(); $user->saveQuietly(); $request->session()->put('2fa_authed', $user->id); @@ -479,6 +484,7 @@ class LoginController extends Controller } $request->session()->regenerate(true); + $request->session()->forget('2fa_authed'); if ($request->session()->has('password_hash_'.Auth::getDefaultDriver())){ $request->session()->remove('password_hash_'.Auth::getDefaultDriver()); @@ -508,8 +514,8 @@ class LoginController extends Controller protected function validator(array $data) { return Validator::make($data, [ - 'username' => 'required', - 'password' => 'required', + 'username' => 'required|not_array', + 'password' => 'required|not_array', ]); } @@ -519,45 +525,6 @@ class LoginController extends Controller return 'username'; } - /** - * 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( - $this->throttleKey($request) - ); - - $minutes = round($seconds / 60); - - $message = trans('auth/message.throttle', ['minutes' => $minutes]); - - 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 bool - */ - protected function hasTooManyLoginAttempts(Request $request) - { - $lockoutTime = config('auth.passwords.users.throttle.lockout_duration'); - $maxLoginAttempts = config('auth.passwords.users.throttle.max_attempts'); - - return $this->limiter()->tooManyAttempts( - $this->throttleKey($request), - $maxLoginAttempts, - $lockoutTime - ); - } public function legacyAuthRedirect() { diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index fe1b331289..805b829180 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -87,7 +87,7 @@ class ResetPasswordController extends Controller 'password.not_in' => trans('validation.disallow_same_pwd_as_user_fields'), ]; - $request->validate($this->rules(), $request->all(), $this->validationErrorMessages()); + $request->validate($this->rules()); Log::debug('Checking if '.$request->input('username').' exists'); // Check to see if the user even exists - we'll treat the response the same to prevent user sniffing @@ -103,22 +103,24 @@ class ResetPasswordController extends Controller ], $messages); } + if ($user->ldap_import != '1') { - // set the response - $response = $broker->reset( - $this->credentials($request), function ($user, $password) { - $this->resetPassword($user, $password); - }); + // set the response + $response = $broker->reset( + $this->credentials($request), function ($user, $password) { + $this->resetPassword($user, $password); + }); - // Check if the password reset above actually worked - if ($response == \Password::PASSWORD_RESET) { - Log::debug('Password reset for '.$user->username.' worked'); - return redirect()->guest('login')->with('success', trans('passwords.reset')); + // Check if the password reset above actually worked + if ($response == \Password::PASSWORD_RESET) { + Log::debug('Password reset for ' . $user->username . ' worked'); + return redirect()->guest('login')->with('success', trans('passwords.reset')); + } + + Log::debug('Password reset for ' . $user->username . ' FAILED - this user exists but the token is not valid'); + return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset')); } - Log::debug('Password reset for '.$user->username.' FAILED - this user exists but the token is not valid'); - return redirect()->back()->withInput($request->only('email'))->with('success', trans('passwords.reset')); - } diff --git a/app/Http/Controllers/Auth/SamlController.php b/app/Http/Controllers/Auth/SamlController.php index 769f903494..6a4c1f65b0 100644 --- a/app/Http/Controllers/Auth/SamlController.php +++ b/app/Http/Controllers/Auth/SamlController.php @@ -99,12 +99,18 @@ class SamlController extends Controller { $saml = $this->saml; $auth = $saml->getAuth(); - $auth->processResponse(); + $saml_exception = false; + try { + $auth->processResponse(); + } catch (\Exception $e) { + Log::warning("Exception caught in SAML login: " . $e->getMessage()); + $saml_exception = true; + } $errors = $auth->getErrors(); - if (! empty($errors)) { - Log::error('There was an error with SAML ACS: '.implode(', ', $errors)); - Log::error('Reason: '.$auth->getLastErrorReason()); + if (!empty($errors) || $saml_exception) { + Log::warning('There was an error with SAML ACS: ' . implode(', ', $errors)); + Log::warning('Reason: ' . $auth->getLastErrorReason()); return redirect()->route('login')->with('error', trans('auth/message.signin.error')); } @@ -132,12 +138,18 @@ class SamlController extends Controller { $auth = $this->saml->getAuth(); $retrieveParametersFromServer = $this->saml->getSetting('retrieveParametersFromServer', false); - $sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true); + $saml_exception = false; + try { + $sloUrl = $auth->processSLO(true, null, $retrieveParametersFromServer, null, true); + } catch (\Exception $e) { + Log::warning("Exception caught in SAML single-logout: " . $e->getMessage()); + $saml_exception = true; + } $errors = $auth->getErrors(); - if (! empty($errors)) { - Log::error('There was an error with SAML SLS: '.implode(', ', $errors)); - Log::error('Reason: '.$auth->getLastErrorReason()); + if (!empty($errors) || $saml_exception) { + Log::warning('There was an error with SAML SLS: ' . implode(', ', $errors)); + Log::warning('Reason: ' . $auth->getLastErrorReason()); return view('errors.403'); } diff --git a/app/Http/Controllers/BulkAssetModelsController.php b/app/Http/Controllers/BulkAssetModelsController.php index 5437791aa8..5f64ea0838 100644 --- a/app/Http/Controllers/BulkAssetModelsController.php +++ b/app/Http/Controllers/BulkAssetModelsController.php @@ -5,8 +5,8 @@ namespace App\Http\Controllers; use App\Helpers\Helper; use App\Models\AssetModel; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Input; -use Illuminate\Support\Facades\Redirect; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; class BulkAssetModelsController extends Controller { @@ -16,9 +16,8 @@ class BulkAssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.7] * @param Request $request - * @return \Illuminate\Contracts\View\View */ - public function edit(Request $request) + public function edit(Request $request) : View | RedirectResponse { $models_raw_array = $request->input('ids'); @@ -61,9 +60,8 @@ class BulkAssetModelsController extends Controller * @author [A. Gianotto] [] * @since [v1.7] * @param Request $request - * @return \Illuminate\Contracts\View\View */ - public function update(Request $request) + public function update(Request $request): View | RedirectResponse { $this->authorize('update', AssetModel::class); @@ -73,20 +71,28 @@ class BulkAssetModelsController extends Controller if (($request->filled('manufacturer_id') && ($request->input('manufacturer_id') != 'NC'))) { $update_array['manufacturer_id'] = $request->input('manufacturer_id'); } + if (($request->filled('category_id') && ($request->input('category_id') != 'NC'))) { $update_array['category_id'] = $request->input('category_id'); } + if ($request->input('fieldset_id') != 'NC') { $update_array['fieldset_id'] = $request->input('fieldset_id'); } + if ($request->input('depreciation_id') != 'NC') { $update_array['depreciation_id'] = $request->input('depreciation_id'); } - if ($request->filled('requestable') != '') { + if ($request->input('requestable') != '') { $update_array['requestable'] = $request->input('requestable'); } + if ($request->filled('min_amt')) { + $update_array['min_amt'] = $request->input('min_amt'); + } + + if (count($update_array) > 0) { AssetModel::whereIn('id', $models_raw_array)->update($update_array); @@ -105,9 +111,8 @@ class BulkAssetModelsController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return \Illuminate\Http\RedirectResponse */ - public function destroy(Request $request) + public function destroy(Request $request) : RedirectResponse { $this->authorize('delete', AssetModel::class); diff --git a/app/Http/Controllers/CategoriesController.php b/app/Http/Controllers/CategoriesController.php index d7e83b43d8..692e2745ac 100755 --- a/app/Http/Controllers/CategoriesController.php +++ b/app/Http/Controllers/CategoriesController.php @@ -4,10 +4,11 @@ namespace App\Http\Controllers; use App\Helpers\Helper; use App\Http\Requests\ImageUploadRequest; -use App\Models\Category as Category; +use App\Models\Category; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; -use Str; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This class controls all actions related to Categories for @@ -25,10 +26,8 @@ class CategoriesController extends Controller * @author [A. Gianotto] [] * @see CategoriesController::getDatatable() method that generates the JSON response * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View { // Show the page $this->authorize('view', Category::class); @@ -42,10 +41,8 @@ class CategoriesController extends Controller * @author [A. Gianotto] [] * @see CategoriesController::store() method that stores the data * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { // Show the page $this->authorize('create', Category::class); @@ -61,10 +58,8 @@ class CategoriesController extends Controller * @see CategoriesController::create() method that makes the form. * @since [v1.0] * @param ImageUploadRequest $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : RedirectResponse { $this->authorize('create', Category::class); $category = new Category(); @@ -74,7 +69,8 @@ class CategoriesController extends Controller $category->use_default_eula = $request->input('use_default_eula', '0'); $category->require_acceptance = $request->input('require_acceptance', '0'); $category->checkin_email = $request->input('checkin_email', '0'); - $category->user_id = Auth::id(); + $category->notes = $request->input('notes'); + $category->created_by = auth()->id(); $category = $request->handleImages($category); if ($category->save()) { @@ -91,17 +87,11 @@ class CategoriesController extends Controller * @see CategoriesController::postEdit() method saves the data * @param int $categoryId * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($categoryId = null) + public function edit(Category $category) : RedirectResponse | View { $this->authorize('update', Category::class); - if (is_null($item = Category::find($categoryId))) { - return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist')); - } - - return view('categories/edit', compact('item')) + return view('categories/edit')->with('item', $category) ->with('category_types', Helper::categoryTypeList()); } @@ -112,27 +102,27 @@ class CategoriesController extends Controller * @see CategoriesController::getEdit() method that makes the form. * @param ImageUploadRequest $request * @param int $categoryId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v1.0] */ - public function update(ImageUploadRequest $request, $categoryId = null) + public function update(ImageUploadRequest $request, Category $category) : RedirectResponse { $this->authorize('update', Category::class); - if (is_null($category = Category::find($categoryId))) { - // Redirect to the categories management page - return redirect()->to('admin/categories')->with('error', trans('admin/categories/message.does_not_exist')); - } - - // Update the category data $category->name = $request->input('name'); - // If the item count is > 0, we disable the category type in the edit. Disabled items - // don't POST, so if the category_type is blank we just set it to the default. + + // Don't allow the user to change the category_type once it's been created + if (($request->filled('category_type') && ($category->itemCount() > 0))) { + $request->validate(['category_type' => 'in:'.$category->category_type]); + } + $category->category_type = $request->input('category_type', $category->category_type); + + $category->fill($request->all()); + $category->eula_text = $request->input('eula_text'); $category->use_default_eula = $request->input('use_default_eula', '0'); $category->require_acceptance = $request->input('require_acceptance', '0'); $category->checkin_email = $request->input('checkin_email', '0'); + $category->notes = $request->input('notes'); $category = $request->handleImages($category); @@ -150,14 +140,12 @@ class CategoriesController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param int $categoryId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($categoryId) + public function destroy($categoryId) : RedirectResponse { $this->authorize('delete', Category::class); // Check if the category exists - if (is_null($category = Category::findOrFail($categoryId))) { + if (is_null($category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count')->findOrFail($categoryId))) { return redirect()->route('categories.index')->with('error', trans('admin/categories/message.not_found')); } @@ -167,7 +155,6 @@ class CategoriesController extends Controller Storage::disk('public')->delete('categories'.'/'.$category->image); $category->delete(); - // Redirect to the locations management page return redirect()->route('categories.index')->with('success', trans('admin/categories/message.delete.success')); } @@ -178,14 +165,12 @@ class CategoriesController extends Controller * @author [A. Gianotto] [] * @see CategoriesController::getDataView() method that generates the JSON response * @param $id - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v1.8] */ - public function show($id) + public function show(Category $category) : View | RedirectResponse { $this->authorize('view', Category::class); - if ($category = Category::find($id)) { + if ($category->category_type == 'asset') { $category_type = 'hardware'; $category_type_route = 'assets'; @@ -200,8 +185,5 @@ class CategoriesController extends Controller return view('categories/view', compact('category')) ->with('category_type', $category_type) ->with('category_type_route', $category_type_route); - } - - return redirect()->route('categories.index')->with('error', trans('admin/categories/message.does_not_exist')); } } diff --git a/app/Http/Controllers/CheckInOutRequest.php b/app/Http/Controllers/CheckInOutRequest.php index 6dd7e4aba3..6ab327fff8 100644 --- a/app/Http/Controllers/CheckInOutRequest.php +++ b/app/Http/Controllers/CheckInOutRequest.php @@ -11,9 +11,8 @@ trait CheckInOutRequest { /** * Find target for checkout - * @return SnipeModel Target asset is being checked out to. */ - protected function determineCheckoutTarget() + protected function determineCheckoutTarget() : ?SnipeModel { // This item is checked out to a location switch (request('checkout_to_type')) { @@ -21,7 +20,7 @@ trait CheckInOutRequest return Location::findOrFail(request('assigned_location')); case 'asset': return Asset::findOrFail(request('assigned_asset')); - case 'user': + default: return User::findOrFail(request('assigned_user')); } @@ -34,7 +33,7 @@ trait CheckInOutRequest * @param SnipeModel $target Target with location * @return Asset Asset being updated */ - protected function updateAssetLocation($asset, $target) + protected function updateAssetLocation($asset, $target) : Asset { switch (request('checkout_to_type')) { case 'location': diff --git a/app/Http/Controllers/CompaniesController.php b/app/Http/Controllers/CompaniesController.php index 63205a598d..96a80e87e6 100644 --- a/app/Http/Controllers/CompaniesController.php +++ b/app/Http/Controllers/CompaniesController.php @@ -7,6 +7,8 @@ use App\Models\Company; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Log; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to Companies for @@ -21,10 +23,8 @@ final class CompaniesController extends Controller * * @author [Abdullah Alansari] [] * @since [v1.8] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View { $this->authorize('view', Company::class); @@ -36,10 +36,8 @@ final class CompaniesController extends Controller * * @author [Abdullah Alansari] [] * @since [v1.8] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { $this->authorize('create', Company::class); @@ -52,10 +50,8 @@ final class CompaniesController extends Controller * @author [Abdullah Alansari] [] * @since [v1.8] * @param Request $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : RedirectResponse { $this->authorize('create', Company::class); @@ -64,6 +60,8 @@ final class CompaniesController extends Controller $company->phone = $request->input('phone'); $company->fax = $request->input('fax'); $company->email = $request->input('email'); + $company->notes = $request->input('notes'); + $company->created_by = auth()->id(); $company = $request->handleImages($company); @@ -81,19 +79,11 @@ final class CompaniesController extends Controller * @author [Abdullah Alansari] [] * @since [v1.8] * @param int $companyId - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($companyId) + public function edit(Company $company) : View | RedirectResponse { - if (is_null($item = Company::find($companyId))) { - return redirect()->route('companies.index') - ->with('error', trans('admin/companies/message.does_not_exist')); - } - - $this->authorize('update', $item); - - return view('companies/edit')->with('item', $item); + $this->authorize('update', $company); + return view('companies/edit')->with('item', $company); } /** @@ -103,21 +93,16 @@ final class CompaniesController extends Controller * @since [v1.8] * @param ImageUploadRequest $request * @param int $companyId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(ImageUploadRequest $request, $companyId) + public function update(ImageUploadRequest $request, Company $company) : RedirectResponse { - if (is_null($company = Company::find($companyId))) { - return redirect()->route('companies.index')->with('error', trans('admin/companies/message.does_not_exist')); - } $this->authorize('update', $company); - $company->name = $request->input('name'); $company->phone = $request->input('phone'); $company->fax = $request->input('fax'); $company->email = $request->input('email'); + $company->notes = $request->input('notes'); $company = $request->handleImages($company); @@ -135,10 +120,8 @@ final class CompaniesController extends Controller * @author [Abdullah Alansari] [] * @since [v1.8] * @param int $companyId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($companyId) + public function destroy($companyId) : RedirectResponse { if (is_null($company = Company::find($companyId))) { return redirect()->route('companies.index') @@ -165,15 +148,9 @@ final class CompaniesController extends Controller ->with('success', trans('admin/companies/message.delete.success')); } - public function show($id) + public function show(Company $company) : View | RedirectResponse { $this->authorize('view', Company::class); - - if (is_null($company = Company::find($id))) { - return redirect()->route('companies.index') - ->with('error', trans('admin/companies/message.not_found')); - } - return view('companies/view')->with('company', $company); } } diff --git a/app/Http/Controllers/Components/ComponentCheckinController.php b/app/Http/Controllers/Components/ComponentCheckinController.php index 9f4724e353..379882c3c5 100644 --- a/app/Http/Controllers/Components/ComponentCheckinController.php +++ b/app/Http/Controllers/Components/ComponentCheckinController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Components; use App\Events\CheckoutableCheckedIn; use App\Events\ComponentCheckedIn; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Asset; use App\Models\Component; @@ -95,13 +96,11 @@ class ComponentCheckinController extends Controller $asset = Asset::find($component_assets->asset_id); - event(new CheckoutableCheckedIn($component, $asset, Auth::user(), $request->input('note'), Carbon::now())); - if ($backto == 'asset'){ - return redirect()->route('hardware.show', $asset->id)->with('success', - trans('admin/components/message.checkin.success')); - } + event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now())); - return redirect()->route('components.index')->with('success', + session()->put(['redirect_option' => $request->get('redirect_option')]); + + return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkin.success')); } diff --git a/app/Http/Controllers/Components/ComponentCheckoutController.php b/app/Http/Controllers/Components/ComponentCheckoutController.php index 79ff57e7df..b40d592369 100644 --- a/app/Http/Controllers/Components/ComponentCheckoutController.php +++ b/app/Http/Controllers/Components/ComponentCheckoutController.php @@ -4,9 +4,11 @@ namespace App\Http\Controllers\Components; use App\Events\CheckoutableCheckedOut; use App\Events\ComponentCheckedOut; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\Asset; use App\Models\Component; +use App\Models\Setting; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Input; @@ -93,22 +95,31 @@ class ComponentCheckoutController extends Controller ->withInput(); } - // Check if the user exists + // Check if the asset exists $asset = Asset::find($request->input('asset_id')); + if ((Setting::getSettings()->full_multiple_companies_support) && $component->company_id !== $asset->company_id) { + return redirect()->route('components.checkout.show', $componentId)->with('error', trans('general.error_user_company')); + } + // Update the component data $component->asset_id = $request->input('asset_id'); $component->assets()->attach($component->id, [ 'component_id' => $component->id, - 'user_id' => Auth::user(), + 'created_by' => auth()->user()->id, 'created_at' => date('Y-m-d H:i:s'), 'assigned_qty' => $request->input('assigned_qty'), 'asset_id' => $request->input('asset_id'), 'note' => $request->input('note'), ]); - event(new CheckoutableCheckedOut($component, $asset, Auth::user(), $request->input('note'))); + event(new CheckoutableCheckedOut($component, $asset, auth()->user(), $request->input('note'))); - return redirect()->route('components.index')->with('success', trans('admin/components/message.checkout.success')); + $request->request->add(['checkout_to_type' => 'asset']); + $request->request->add(['assigned_asset' => $asset->id]); + + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + + return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.checkout.success')); } } diff --git a/app/Http/Controllers/Components/ComponentsController.php b/app/Http/Controllers/Components/ComponentsController.php index 33ebde6456..74594d312b 100644 --- a/app/Http/Controllers/Components/ComponentsController.php +++ b/app/Http/Controllers/Components/ComponentsController.php @@ -73,6 +73,8 @@ class ComponentsController extends Controller $component->name = $request->input('name'); $component->category_id = $request->input('category_id'); $component->supplier_id = $request->input('supplier_id'); + $component->manufacturer_id = $request->input('manufacturer_id'); + $component->model_number = $request->input('model_number'); $component->location_id = $request->input('location_id'); $component->company_id = Company::getIdForCurrentUser($request->input('company_id')); $component->order_number = $request->input('order_number', null); @@ -81,13 +83,15 @@ class ComponentsController extends Controller $component->purchase_date = $request->input('purchase_date', null); $component->purchase_cost = $request->input('purchase_cost', null); $component->qty = $request->input('qty'); - $component->user_id = Auth::id(); + $component->created_by = auth()->id(); $component->notes = $request->input('notes'); $component = $request->handleImages($component); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($component->save()) { - return redirect()->route('components.index')->with('success', trans('admin/components/message.create.success')); + return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.create.success')); } return redirect()->back()->withInput()->withErrors($component->getErrors()); @@ -103,15 +107,13 @@ class ComponentsController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($componentId = null) + public function edit(Component $component) { - if ($item = Component::find($componentId)) { - $this->authorize('update', $item); - return view('components/edit', compact('item'))->with('category_type', 'component'); - } - - return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist')); + $this->authorize('update', $component); + return view('components/edit') + ->with('item', $component) + ->with('category_type', 'component'); } @@ -126,11 +128,8 @@ class ComponentsController extends Controller * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v3.0] */ - public function update(ImageUploadRequest $request, $componentId = null) + public function update(ImageUploadRequest $request, Component $component) { - if (is_null($component = Component::find($componentId))) { - return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist')); - } $min = $component->numCheckedOut(); $validator = Validator::make($request->all(), [ 'qty' => "required|numeric|min:$min", @@ -148,6 +147,8 @@ class ComponentsController extends Controller $component->name = $request->input('name'); $component->category_id = $request->input('category_id'); $component->supplier_id = $request->input('supplier_id'); + $component->manufacturer_id = $request->input('manufacturer_id'); + $component->model_number = $request->input('model_number'); $component->location_id = $request->input('location_id'); $component->company_id = Company::getIdForCurrentUser($request->input('company_id')); $component->order_number = $request->input('order_number'); @@ -160,8 +161,10 @@ class ComponentsController extends Controller $component = $request->handleImages($component); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($component->save()) { - return redirect()->route('components.index')->with('success', trans('admin/components/message.update.success')); + return redirect()->to(Helper::getRedirectOption($request, $component->id, 'Components'))->with('success', trans('admin/components/message.update.success')); } return redirect()->back()->withInput()->withErrors($component->getErrors()); @@ -185,7 +188,7 @@ class ComponentsController extends Controller $this->authorize('delete', $component); // Remove the image if one exists - if (Storage::disk('public')->exists('components/'.$component->image)) { + if ($component->image && Storage::disk('public')->exists('components/' . $component->image)) { try { Storage::disk('public')->delete('components/'.$component->image); } catch (\Exception $e) { @@ -193,6 +196,10 @@ class ComponentsController extends Controller } } + if ($component->numCheckedOut() > 0) { + return redirect()->route('components.index')->with('error', trans('admin/components/message.delete.error_qty')); + } + $component->delete(); return redirect()->route('components.index')->with('success', trans('admin/components/message.delete.success')); @@ -208,17 +215,9 @@ class ComponentsController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($componentId = null) + public function show(Component $component) { - $component = Component::find($componentId); - - if (isset($component->id)) { $this->authorize('view', $component); - return view('components/view', compact('component')); - } - // Redirect to the user management page - return redirect()->route('components.index') - ->with('error', trans('admin/components/message.does_not_exist')); } } diff --git a/app/Http/Controllers/Components/ComponentsFilesController.php b/app/Http/Controllers/Components/ComponentsFilesController.php index a7d42bb072..b5e30aa694 100644 --- a/app/Http/Controllers/Components/ComponentsFilesController.php +++ b/app/Http/Controllers/Components/ComponentsFilesController.php @@ -50,7 +50,7 @@ class ComponentsFilesController extends Controller } - return redirect()->route('components.show', $component->id)->with('success', trans('general.file_upload_success')); + return redirect()->route('components.show', $component->id)->withFragment('files')->with('success', trans('general.file_upload_success')); } @@ -91,7 +91,7 @@ class ComponentsFilesController extends Controller $log->delete(); - return redirect()->back() + return redirect()->back()->withFragment('files') ->with('success', trans('admin/hardware/message.deletefile.success')); } @@ -112,40 +112,25 @@ class ComponentsFilesController extends Controller public function show($componentId = null, $fileId = null) { Log::debug('Private filesystem is: '.config('filesystems.default')); - $component = Component::find($componentId); + // the component is valid - if (isset($component->id)) { + if ($component = Component::find($componentId)) { $this->authorize('view', $component); $this->authorize('components.files', $component); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) { - $file = 'private_uploads/components/'.$log->filename; + $file = 'private_uploads/components/'.$log->filename; - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found')); } - - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); - } } + return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found')); + } return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php index d838ee5696..e08da41229 100644 --- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php +++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php @@ -3,13 +3,13 @@ namespace App\Http\Controllers\Consumables; use App\Events\CheckoutableCheckedOut; +use App\Helpers\Helper; use App\Http\Controllers\Controller; -use App\Models\Accessory; use App\Models\Consumable; use App\Models\User; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Input; +use \Illuminate\Contracts\View\View; +use \Illuminate\Http\RedirectResponse; class ConsumableCheckoutController extends Controller { @@ -20,13 +20,11 @@ class ConsumableCheckoutController extends Controller * @see ConsumableCheckoutController::store() method that stores the data. * @since [v1.0] * @param int $id - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create($id) + public function create($id) : View | RedirectResponse { - if ($consumable = Consumable::with('users')->find($id)) { + if ($consumable = Consumable::find($id)) { $this->authorize('checkout', $consumable); @@ -36,7 +34,7 @@ class ConsumableCheckoutController extends Controller // Make sure there is at least one available to checkout if ($consumable->numRemaining() <= 0){ return redirect()->route('consumables.index') - ->with('error', trans('admin/consumables/message.checkout.unavailable')); + ->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => 1, 'remaining' => $consumable->numRemaining()])); } // Return the checkout view @@ -72,17 +70,17 @@ class ConsumableCheckoutController extends Controller $this->authorize('checkout', $consumable); // If the quantity is not present in the request or is not a positive integer, set it to 1 - $quantity = $request->input('qty'); + $quantity = $request->input('checkout_qty'); if (!isset($quantity) || !ctype_digit((string)$quantity) || $quantity <= 0) { $quantity = 1; } // Make sure there is at least one available to checkout if ($consumable->numRemaining() <= 0 || $quantity > $consumable->numRemaining()) { - return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable')); + return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.checkout.unavailable', ['requested' => $quantity, 'remaining' => $consumable->numRemaining() ])); } - $admin_user = Auth::user(); + $admin_user = auth()->user(); $assigned_to = e($request->input('assigned_to')); // Check if the user exists @@ -94,17 +92,25 @@ class ConsumableCheckoutController extends Controller // Update the consumable data $consumable->assigned_to = e($request->input('assigned_to')); - for($i = 0; $i < $quantity; $i++){ + for ($i = 0; $i < $quantity; $i++){ $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, - 'user_id' => $admin_user->id, + 'created_by' => $admin_user->id, 'assigned_to' => e($request->input('assigned_to')), 'note' => $request->input('note'), ]); } - event(new CheckoutableCheckedOut($consumable, $user, Auth::user(), $request->input('note'))); + + $consumable->checkout_qty = $quantity; + event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note'))); + + $request->request->add(['checkout_to_type' => 'user']); + $request->request->add(['assigned_user' => $user->id]); + + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); + // Redirect to the new consumable page - return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.checkout.success')); + return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.checkout.success')); } } diff --git a/app/Http/Controllers/Consumables/ConsumablesController.php b/app/Http/Controllers/Consumables/ConsumablesController.php index b33e6e07a9..c96c2db975 100644 --- a/app/Http/Controllers/Consumables/ConsumablesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesController.php @@ -8,8 +8,10 @@ use App\Http\Requests\ImageUploadRequest; use App\Models\Company; use App\Models\Consumable; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Validator; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; +use App\Http\Requests\StoreConsumableRequest; /** * This controller handles all actions related to Consumables for @@ -48,7 +50,7 @@ class ConsumablesController extends Controller { $this->authorize('create', Consumable::class); - return view('consumables/edit')->with('category_type', 'consumable') + return view('consumables.edit')->with('category_type', 'consumable') ->with('item', new Consumable); } @@ -62,7 +64,7 @@ class ConsumablesController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(StoreConsumableRequest $request) { $this->authorize('create', Consumable::class); $consumable = new Consumable(); @@ -79,14 +81,16 @@ class ConsumablesController extends Controller $consumable->purchase_date = $request->input('purchase_date'); $consumable->purchase_cost = $request->input('purchase_cost'); $consumable->qty = $request->input('qty'); - $consumable->user_id = Auth::id(); + $consumable->created_by = auth()->id(); $consumable->notes = $request->input('notes'); $consumable = $request->handleImages($consumable); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($consumable->save()) { - return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.create.success')); + return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.create.success')); } return redirect()->back()->withInput()->withErrors($consumable->getErrors()); @@ -99,18 +103,14 @@ class ConsumablesController extends Controller * @param int $consumableId * @see ConsumablesController::postEdit() method that stores the form data. * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($consumableId = null) + public function edit(Consumable $consumable) : View | RedirectResponse { - if ($item = Consumable::find($consumableId)) { - $this->authorize($item); + $this->authorize($consumable); + return view('consumables/edit') + ->with('item', $consumable) + ->with('category_type', 'consumable'); - return view('consumables/edit', compact('item'))->with('category_type', 'consumable'); - } - - return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist')); } /** @@ -124,11 +124,8 @@ class ConsumablesController extends Controller * @see ConsumablesController::getEdit() method that stores the form data. * @since [v1.0] */ - public function update(ImageUploadRequest $request, $consumableId = null) + public function update(StoreConsumableRequest $request, Consumable $consumable) { - if (is_null($consumable = Consumable::find($consumableId))) { - return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.does_not_exist')); - } $min = $consumable->numCheckedOut(); $validator = Validator::make($request->all(), [ @@ -160,8 +157,10 @@ class ConsumablesController extends Controller $consumable = $request->handleImages($consumable); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($consumable->save()) { - return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.update.success')); + return redirect()->to(Helper::getRedirectOption($request, $consumable->id, 'Consumables'))->with('success', trans('admin/consumables/message.update.success')); } return redirect()->back()->withInput()->withErrors($consumable->getErrors()); @@ -182,6 +181,7 @@ class ConsumablesController extends Controller return redirect()->route('consumables.index')->with('error', trans('admin/consumables/message.not_found')); } $this->authorize($consumable); + $consumable->delete(); // Redirect to the locations management page return redirect()->route('consumables.index')->with('success', trans('admin/consumables/message.delete.success')); @@ -197,15 +197,22 @@ class ConsumablesController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($consumableId = null) + public function show(Consumable $consumable) { - $consumable = Consumable::find($consumableId); + $consumable = Consumable::withCount('users as users_consumables')->find($consumable->id); $this->authorize($consumable); - if (isset($consumable->id)) { - return view('consumables/view', compact('consumable')); - } + return view('consumables/view', compact('consumable')); + } - return redirect()->route('consumables.index') - ->with('error', trans('admin/consumables/message.does_not_exist')); + public function clone(Consumable $consumable) : View + { + $this->authorize('create', $consumable); + $consumable_to_close = $consumable; + $consumable = clone $consumable_to_close; + $consumable->id = null; + $consumable->image = null; + $consumable->created_by = null; + + return view('consumables/edit')->with('item', $consumable); } } diff --git a/app/Http/Controllers/Consumables/ConsumablesFilesController.php b/app/Http/Controllers/Consumables/ConsumablesFilesController.php index 35a4ae841e..545b008dc0 100644 --- a/app/Http/Controllers/Consumables/ConsumablesFilesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesFilesController.php @@ -48,7 +48,7 @@ class ConsumablesFilesController extends Controller } - return redirect()->route('consumables.show', $consumable->id)->with('success', trans('general.file_upload_success')); + return redirect()->route('consumables.show', $consumable->id)->withFragment('files')->with('success', trans('general.file_upload_success')); } @@ -89,7 +89,7 @@ class ConsumablesFilesController extends Controller $log->delete(); - return redirect()->back() + return redirect()->back()->withFragment('files') ->with('success', trans('admin/hardware/message.deletefile.success')); } @@ -104,7 +104,6 @@ class ConsumablesFilesController extends Controller * @since [v1.4] * @param int $consumableId * @param int $fileId - * @return \Symfony\Consumable\HttpFoundation\Response * @throws \Illuminate\Auth\Access\AuthorizationException */ public function show($consumableId = null, $fileId = null) @@ -116,36 +115,18 @@ class ConsumablesFilesController extends Controller $this->authorize('view', $consumable); $this->authorize('consumables.files', $consumable); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) { + $file = 'private_uploads/consumables/'.$log->filename; - $file = 'private_uploads/consumables/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); - } - - - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found')); } } + // The log record doesn't exist somehow + return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found')); + } return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index c716fff368..74fff19a37 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -35,6 +35,6 @@ abstract class Controller extends BaseController public function __construct() { view()->share('signedIn', Auth::check()); - view()->share('user', Auth::user()); + view()->share('user', auth()->user()); } } diff --git a/app/Http/Controllers/CustomFieldsController.php b/app/Http/Controllers/CustomFieldsController.php index 94b45552fd..7862719276 100644 --- a/app/Http/Controllers/CustomFieldsController.php +++ b/app/Http/Controllers/CustomFieldsController.php @@ -8,6 +8,8 @@ use App\Models\CustomField; use App\Models\CustomFieldset; use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to Custom Asset Fields for @@ -25,10 +27,8 @@ class CustomFieldsController extends Controller * * @author [Brady Wetherington] [] * @since [v1.8] - * @return \Illuminate\Support\Facades\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View { $this->authorize('view', CustomField::class); @@ -45,10 +45,8 @@ class CustomFieldsController extends Controller * @see CustomFieldsController::storeField() * @author [A. Gianotto] [] * @since [v5.1.5] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show() + public function show() : RedirectResponse { return redirect()->route('fields.index'); } @@ -60,10 +58,8 @@ class CustomFieldsController extends Controller * @see CustomFieldsController::storeField() * @author [Brady Wetherington] [] * @since [v1.8] - * @return \Illuminate\Support\Facades\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create(Request $request) + public function create(Request $request) : View { $this->authorize('create', CustomField::class); $fieldsets = CustomFieldset::get(); @@ -82,35 +78,36 @@ class CustomFieldsController extends Controller * @see CustomFieldsController::createField() * @author [Brady Wetherington] [] * @since [v1.8] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(CustomFieldRequest $request) + public function store(CustomFieldRequest $request) : RedirectResponse { $this->authorize('create', CustomField::class); - $show_in_email = $request->get("show_in_email", 0); - $display_in_user_view = $request->get("display_in_user_view", 0); + $show_in_email = $request->input("show_in_email", 0); + $display_in_user_view = $request->input("display_in_user_view", 0); // Override the display settings if the field is encrypted - if ($request->get("field_encrypted") == '1') { + if ($request->input("field_encrypted") == '1') { $show_in_email = '0'; $display_in_user_view = '0'; } - + $field = new CustomField([ - "name" => trim($request->get("name")), - "element" => $request->get("element"), - "help_text" => $request->get("help_text"), - "field_values" => $request->get("field_values"), - "field_encrypted" => $request->get("field_encrypted", 0), + "name" => trim($request->input("name")), + "element" => $request->input("element"), + "help_text" => $request->input("help_text"), + "field_values" => $request->input("field_values"), + "field_encrypted" => $request->input("field_encrypted", 0), "show_in_email" => $show_in_email, - "is_unique" => $request->get("is_unique", 0), + "is_unique" => $request->input("is_unique", 0), "display_in_user_view" => $display_in_user_view, - "auto_add_to_fieldsets" => $request->get("auto_add_to_fieldsets", 0), - "show_in_listview" => $request->get("show_in_listview", 0), - "show_in_requestable_list" => $request->get("show_in_requestable_list", 0), - "user_id" => Auth::id() + "auto_add_to_fieldsets" => $request->input("auto_add_to_fieldsets", 0), + "show_in_listview" => $request->input("show_in_listview", 0), + "show_in_requestable_list" => $request->input("show_in_requestable_list", 0), + "display_checkin" => $request->input("display_checkin", 0), + "display_checkout" => $request->input("display_checkout", 0), + "display_audit" => $request->input("display_audit", 0), + "created_by" => auth()->id() ]); @@ -144,10 +141,8 @@ class CustomFieldsController extends Controller * * @author [A. Gianotto] [] * @since [v3.0] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function deleteFieldFromFieldset($field_id, $fieldset_id) + public function deleteFieldFromFieldset($field_id, $fieldset_id) : RedirectResponse { $field = CustomField::find($field_id); @@ -176,10 +171,8 @@ class CustomFieldsController extends Controller * * @author [Brady Wetherington] [] * @since [v1.8] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($field_id) + public function destroy($field_id) : RedirectResponse { if ($field = CustomField::find($field_id)) { $this->authorize('delete', $field); @@ -202,13 +195,9 @@ class CustomFieldsController extends Controller * @author [A. Gianotto] [] * @param int $id * @since [v4.0] - * @return \Illuminate\Support\Facades\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit(Request $request, $id) + public function edit(Request $request, CustomField $field) : View | RedirectResponse { - if ($field = CustomField::find($id)) { - $this->authorize('update', $field); $fieldsets = CustomFieldset::get(); $customFormat = ''; @@ -222,11 +211,7 @@ class CustomFieldsController extends Controller 'fieldsets' => $fieldsets, 'predefinedFormats' => Helper::predefined_formats(), ]); - } - return redirect()->route("fields.index") - ->with("error", trans('admin/custom_fields/message.field.invalid')); - } @@ -241,13 +226,9 @@ class CustomFieldsController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(CustomFieldRequest $request, $id) + public function update(CustomFieldRequest $request, CustomField $field) : RedirectResponse { - $field = CustomField::find($id); - $this->authorize('update', $field); - - $show_in_email = $request->get("show_in_email", 0); $display_in_user_view = $request->get("display_in_user_view", 0); @@ -257,10 +238,10 @@ class CustomFieldsController extends Controller $display_in_user_view = '0'; } - $field->name = trim(e($request->get("name"))); - $field->element = e($request->get("element")); + $field->name = trim($request->get("name")); + $field->element = $request->get("element"); $field->field_values = $request->get("field_values"); - $field->user_id = Auth::id(); + $field->created_by = auth()->id(); $field->help_text = $request->get("help_text"); $field->show_in_email = $show_in_email; $field->is_unique = $request->get("is_unique", 0); @@ -268,11 +249,14 @@ class CustomFieldsController extends Controller $field->auto_add_to_fieldsets = $request->get("auto_add_to_fieldsets", 0); $field->show_in_listview = $request->get("show_in_listview", 0); $field->show_in_requestable_list = $request->get("show_in_requestable_list", 0); + $field->display_checkin = $request->get("display_checkin", 0); + $field->display_checkout = $request->get("display_checkout", 0); + $field->display_audit = $request->get("display_audit", 0); if ($request->get('format') == 'CUSTOM REGEX') { - $field->format = e($request->get('custom_format')); + $field->format = $request->get('custom_format'); } else { - $field->format = e($request->get('format')); + $field->format = $request->get('format'); } if ($field->element == 'checkbox' || $field->element == 'radio'){ diff --git a/app/Http/Controllers/CustomFieldsetsController.php b/app/Http/Controllers/CustomFieldsetsController.php index e4382a31ea..fde585ba6f 100644 --- a/app/Http/Controllers/CustomFieldsetsController.php +++ b/app/Http/Controllers/CustomFieldsetsController.php @@ -6,10 +6,9 @@ use App\Models\AssetModel; use App\Models\CustomField; use App\Models\CustomFieldset; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Validator; -use Redirect; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to Custom Asset Fields for @@ -23,7 +22,7 @@ use Redirect; class CustomFieldsetsController extends Controller { - public function index() + public function index() : RedirectResponse { return redirect()->route("fields.index") ->with("error", trans('admin/custom_fields/message.fieldset.does_not_exist')); @@ -34,14 +33,14 @@ class CustomFieldsetsController extends Controller * * @author [Brady Wetherington] [] * @param int $id - * @return \Illuminate\Support\Facades\View - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v1.8] */ - public function show($id) + public function show(CustomFieldset $fieldset) : View | RedirectResponse { $cfset = CustomFieldset::with('fields') - ->where('id', '=', $id)->orderBy('id', 'ASC')->first(); + ->where('id', '=', $fieldset->id) + ->orderBy('id', 'ASC') + ->first(); $this->authorize('view', $cfset); @@ -70,10 +69,8 @@ class CustomFieldsetsController extends Controller * * @author [Brady Wetherington] [] * @since [v1.8] - * @return \Illuminate\Support\Facades\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { $this->authorize('create', CustomField::class); @@ -89,13 +86,13 @@ class CustomFieldsetsController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(Request $request) + public function store(Request $request) : RedirectResponse { $this->authorize('create', CustomField::class); $fieldset = new CustomFieldset([ 'name' => $request->get('name'), - 'user_id' => Auth::user()->id, + 'created_by' => auth()->id(), ]); $validator = Validator::make($request->all(), $fieldset->rules); @@ -126,19 +123,11 @@ class CustomFieldsetsController extends Controller * @author [A. Gianotto] [] * @param int $id * @since [v6.0.14] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($id) + public function edit(CustomFieldset $fieldset) : View | RedirectResponse { $this->authorize('create', CustomField::class); - - if ($fieldset = CustomFieldset::find($id)) { - return view('custom_fields.fieldsets.edit')->with('item', $fieldset); - } - - return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id])); - + return view('custom_fields.fieldsets.edit')->with('item', $fieldset); } /** @@ -147,26 +136,19 @@ class CustomFieldsetsController extends Controller * @author [A. Gianotto] [] * @param int $id * @since [v6.0.14] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(Request $request, $id) + public function update(Request $request, CustomFieldset $fieldset) : RedirectResponse { $this->authorize('create', CustomField::class); - if ($fieldset = CustomFieldset::find($id)) { - - $fieldset->name = $request->input('name'); - - if ($fieldset->save()) { - return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated')); - } - - return redirect()->back()->withInput()->withErrors($fieldset->getErrors()); + $fieldset->name = $request->input('name'); + if ($fieldset->save()) { + return redirect()->route('fields.index')->with('success', trans('admin/custom_fields/general.fieldset_updated')); } - return redirect()->route('fields.index')->with('error', trans('admin/custom_fields/general.fieldset_does_not_exist', ['id' => $id])); + return redirect()->back()->withInput()->withErrors($fieldset->getErrors()); + } /** @@ -175,10 +157,8 @@ class CustomFieldsetsController extends Controller * @author [Brady Wetherington] [] * @param int $id * @since [v1.8] - * @return View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($id) + public function destroy($id) : RedirectResponse { $fieldset = CustomFieldset::find($id); @@ -203,9 +183,8 @@ class CustomFieldsetsController extends Controller * * @author [Brady Wetherington] [] * @since [v1.8] - * @return View */ - public function associate(Request $request, $id) + public function associate(Request $request, $id) : RedirectResponse { $set = CustomFieldset::find($id); @@ -223,7 +202,7 @@ class CustomFieldsetsController extends Controller return redirect()->route('fieldsets.show', [$id])->with('success', trans('admin/custom_fields/message.field.create.assoc_success')); } - return redirect()->route('fieldsets.show', [$id])->with('error', 'No field selected.'); + return redirect()->route('fieldsets.show', [$id])->with('error', trans('admin/custom_fields/message.field.none_selected')); } /** @@ -232,7 +211,7 @@ class CustomFieldsetsController extends Controller * @author [A. Gianotto] [] * @since [v5.0] */ - public function makeFieldRequired($fieldset_id, $field_id) + public function makeFieldRequired($fieldset_id, $field_id) : RedirectResponse { $this->authorize('update', CustomField::class); $field = CustomField::findOrFail($field_id); @@ -250,7 +229,7 @@ class CustomFieldsetsController extends Controller * @author [A. Gianotto] [] * @since [v5.0] */ - public function makeFieldOptional($fieldset_id, $field_id) + public function makeFieldOptional($fieldset_id, $field_id) : RedirectResponse { $this->authorize('update', CustomField::class); $field = CustomField::findOrFail($field_id); diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 89283a7c21..a2ccb62475 100755 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -2,8 +2,10 @@ namespace App\Http\Controllers; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Artisan; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; +use Illuminate\Support\Facades\Session; /** @@ -21,12 +23,11 @@ class DashboardController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return View */ - public function index() + public function index() : View | RedirectResponse { // Show the page - if (Auth::user()->hasAccess('admin')) { + if (auth()->user()->hasAccess('admin')) { $asset_stats = null; $counts['asset'] = \App\Models\Asset::count(); @@ -34,16 +35,18 @@ class DashboardController extends Controller $counts['license'] = \App\Models\License::assetcount(); $counts['consumable'] = \App\Models\Consumable::count(); $counts['component'] = \App\Models\Component::count(); - $counts['user'] = \App\Models\Company::scopeCompanyables(Auth::user())->count(); + $counts['user'] = \App\Models\Company::scopeCompanyables(auth()->user())->count(); $counts['grand_total'] = $counts['asset'] + $counts['accessory'] + $counts['license'] + $counts['consumable']; if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) { Artisan::call('migrate', ['--force' => true]); - \Artisan::call('passport:install'); + Artisan::call('passport:install', ['--no-interaction' => true]); } return view('dashboard')->with('asset_stats', $asset_stats)->with('counts', $counts); } else { + Session::reflash(); + // Redirect to the profile page return redirect()->intended('account/view-assets'); } diff --git a/app/Http/Controllers/DepartmentsController.php b/app/Http/Controllers/DepartmentsController.php index 62823098ba..f49242127e 100644 --- a/app/Http/Controllers/DepartmentsController.php +++ b/app/Http/Controllers/DepartmentsController.php @@ -4,8 +4,10 @@ namespace App\Http\Controllers; use App\Http\Requests\ImageUploadRequest; use App\Models\Department; +use App\Models\Company; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Log; @@ -25,10 +27,8 @@ class DepartmentsController extends Controller * @see AssetController::getDatatable() method that generates the JSON response * @since [v4.0] * @param Request $request - * @return \Illuminate\Support\Facades\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index(Request $request) + public function index(Request $request) : View { $this->authorize('index', Department::class); $company = null; @@ -45,18 +45,17 @@ class DepartmentsController extends Controller * @author [A. Gianotto] [] * @since [v4.0] * @param ImageUploadRequest $request - * @return \Illuminate\Http\Response - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : RedirectResponse { $this->authorize('create', Department::class); $department = new Department; $department->fill($request->all()); - $department->user_id = Auth::user()->id; + $department->created_by = auth()->id(); $department->manager_id = ($request->filled('manager_id') ? $request->input('manager_id') : null); $department->location_id = ($request->filled('location_id') ? $request->input('location_id') : null); $department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null); + $department->notes = $request->input('notes'); $department = $request->handleImages($department); if ($department->save()) { @@ -73,20 +72,11 @@ class DepartmentsController extends Controller * @author [A. Gianotto] [] * @param int $id * @since [v4.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($id) + public function show(Department $department) : View | RedirectResponse { - $department = Department::find($id); - $this->authorize('view', $department); - - if (isset($department->id)) { - return view('departments/view', compact('department')); - } - - return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist')); + return view('departments/view', compact('department')); } /** @@ -95,10 +85,8 @@ class DepartmentsController extends Controller * @author [A. Gianotto] [] * @see DepartmentsController::postCreate() method that validates and stores the data * @since [v4.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { $this->authorize('create', Department::class); @@ -111,10 +99,8 @@ class DepartmentsController extends Controller * @author [A. Gianotto] [] * @param int $locationId * @since [v4.0] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($id) + public function destroy($id) : RedirectResponse { if (is_null($department = Department::find($id))) { return redirect()->to(route('departments.index'))->with('error', trans('admin/departments/message.not_found')); @@ -145,25 +131,23 @@ class DepartmentsController extends Controller * @see LocationsController::postCreate() method that validates and stores * @param int $departmentId * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($departmentId = null) + public function edit(Department $department) : View | RedirectResponse { - if (is_null($item = Department::find($departmentId))) { - return redirect()->back()->with('error', trans('admin/locations/message.does_not_exist')); - } - - $this->authorize('update', $item); - - return view('departments/edit', compact('item')); + $this->authorize('update', $department); + return view('departments/edit')->with('item', $department); } - public function update(ImageUploadRequest $request, $id) + /** + * Save updated Department information. + * + * @author [A. Gianotto] [] + * @see LocationsController::postCreate() method that validates and stores + * @param int $departmentId + * @since [v1.0] + */ + public function update(ImageUploadRequest $request, Department $department) : RedirectResponse { - if (is_null($department = Department::find($id))) { - return redirect()->route('departments.index')->with('error', trans('admin/departments/message.does_not_exist')); - } $this->authorize('update', $department); @@ -173,7 +157,7 @@ class DepartmentsController extends Controller $department->company_id = ($request->filled('company_id') ? $request->input('company_id') : null); $department->phone = $request->input('phone'); $department->fax = $request->input('fax'); - + $department->notes = $request->input('notes'); $department = $request->handleImages($department); if ($department->save()) { diff --git a/app/Http/Controllers/DepreciationsController.php b/app/Http/Controllers/DepreciationsController.php index 70bfb78cb5..2c98e240b9 100755 --- a/app/Http/Controllers/DepreciationsController.php +++ b/app/Http/Controllers/DepreciationsController.php @@ -5,7 +5,8 @@ namespace App\Http\Controllers; use App\Models\Depreciation; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; - +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to Depreciations for * the Snipe-IT Asset Management application. @@ -21,14 +22,10 @@ class DepreciationsController extends Controller * @author [A. Gianotto] [authorize('view', Depreciation::class); - - // Show the page return view('depreciations/index'); } @@ -38,10 +35,8 @@ class DepreciationsController extends Controller * @author [A. Gianotto] [authorize('create', Depreciation::class); @@ -56,10 +51,8 @@ class DepreciationsController extends Controller * @see DepreciationsController::postCreate() * @since [v1.0] * @param Request $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(Request $request) + public function store(Request $request) : RedirectResponse { $this->authorize('create', Depreciation::class); @@ -68,7 +61,21 @@ class DepreciationsController extends Controller // Depreciation data $depreciation->name = $request->input('name'); $depreciation->months = $request->input('months'); - $depreciation->user_id = Auth::id(); + $depreciation->created_by = auth()->id(); + + $request->validate([ + 'depreciation_min' => [ + 'required', + 'numeric', + function ($attribute, $value, $fail) use ($request) { + if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) { + $fail(trans('validation.percent')); + } + }, + ], + 'depreciation_type' => 'required|in:amount,percent', + ]); + $depreciation->depreciation_type = $request->input('depreciation_type'); $depreciation->depreciation_min = $request->input('depreciation_min'); // Was the asset created? @@ -87,20 +94,12 @@ class DepreciationsController extends Controller * @see DepreciationsController::postEdit() * @param int $depreciationId * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($depreciationId = null) + public function edit(Depreciation $depreciation) : RedirectResponse | View { - // Check if the depreciation exists - if (is_null($item = Depreciation::find($depreciationId))) { - // Redirect to the blogs management page - return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist')); - } - $this->authorize('update', $item); - - return view('depreciations/edit', compact('item')); + $this->authorize('update', $depreciation); + return view('depreciations/edit')->with('item', $depreciation); } /** @@ -110,23 +109,28 @@ class DepreciationsController extends Controller * @see DepreciationsController::getEdit() * @param Request $request * @param int $depreciationId - * @return \Illuminate\Http\RedirectResponse * @since [v1.0] - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(Request $request, $depreciationId = null) + public function update(Request $request, Depreciation $depreciation) : RedirectResponse { - // Check if the depreciation exists - if (is_null($depreciation = Depreciation::find($depreciationId))) { - // Redirect to the blogs management page - return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist')); - } $this->authorize('update', $depreciation); - - // Depreciation data $depreciation->name = $request->input('name'); $depreciation->months = $request->input('months'); + + $request->validate([ + 'depreciation_min' => [ + 'required', + 'numeric', + function ($attribute, $value, $fail) use ($request) { + if ($request->input('depreciation_type') == 'percent' && ($value < 0 || $value > 100)) { + $fail(trans('validation.percent')); + } + }, + ], + 'depreciation_type' => 'required|in:amount,percent', + ]); + $depreciation->depreciation_type = $request->input('depreciation_type'); $depreciation->depreciation_min = $request->input('depreciation_min'); // Was the asset created? @@ -146,10 +150,8 @@ class DepreciationsController extends Controller * @author [A. Gianotto] [find($depreciationId))) { @@ -175,18 +177,23 @@ class DepreciationsController extends Controller * @see DepreciationsController::postEdit() * @param int $depreciationId * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($id) + public function show(Depreciation $depreciation) : View | RedirectResponse { - if (is_null($depreciation = Depreciation::find($id))) { - // Redirect to the blogs management page - return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist')); - } + $depreciation = Depreciation::withCount('assets as assets_count') + ->withCount('models as models_count') + ->withCount('licenses as licenses_count') + ->find($depreciation->id); $this->authorize('view', $depreciation); - return view('depreciations/view', compact('depreciation')); + if ($depreciation) { + return view('depreciations/view', compact('depreciation')); + + } + + return redirect()->route('depreciations.index')->with('error', trans('admin/depreciations/message.does_not_exist')); + + } } diff --git a/app/Http/Controllers/GoogleAuthController.php b/app/Http/Controllers/GoogleAuthController.php index 6f5d02fde9..d873491c5b 100644 --- a/app/Http/Controllers/GoogleAuthController.php +++ b/app/Http/Controllers/GoogleAuthController.php @@ -2,7 +2,7 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; +use Illuminate\Http\RedirectResponse; use App\Models\User; use Illuminate\Support\Facades\Auth; use Laravel\Socialite\Facades\Socialite; @@ -30,7 +30,7 @@ class GoogleAuthController extends Controller return Socialite::driver('google')->redirect(); } - public function handleGoogleCallback() + public function handleGoogleCallback() : RedirectResponse { try { $socialUser = Socialite::driver('google')->user(); diff --git a/app/Http/Controllers/GroupsController.php b/app/Http/Controllers/GroupsController.php index bfc8907524..577030390b 100755 --- a/app/Http/Controllers/GroupsController.php +++ b/app/Http/Controllers/GroupsController.php @@ -5,7 +5,8 @@ namespace App\Http\Controllers; use App\Helpers\Helper; use App\Models\Group; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to User Groups for @@ -22,11 +23,9 @@ class GroupsController extends Controller * @author [A. Gianotto] [name = $request->input('name'); $group->permissions = json_encode($request->input('permission')); - $group->created_by = Auth::user()->id; + $group->created_by = auth()->id(); + $group->notes = $request->input('notes'); if ($group->save()) { return redirect()->route('groups.index')->with('success', trans('admin/groups/message.success.create')); @@ -80,21 +78,17 @@ class GroupsController extends Controller * @see GroupsController::postEdit() * @param int $id * @since [v1.0] - * @return \Illuminate\Contracts\View\View */ - public function edit($id) + public function edit(Group $group) : View | RedirectResponse { - $group = Group::find($id); + $permissions = config('permissions'); + $groupPermissions = $group->decodePermissions(); - if ($group) { - $permissions = config('permissions'); - $groupPermissions = $group->decodePermissions(); - $selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions); - - return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions')); + if ((!is_array($groupPermissions)) || (!$groupPermissions)) { + $groupPermissions = []; } - - return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id])); + $selected_array = Helper::selectedPermissionsArray($permissions, $groupPermissions); + return view('groups.edit', compact('group', 'permissions', 'selected_array', 'groupPermissions')); } /** @@ -104,15 +98,12 @@ class GroupsController extends Controller * @see GroupsController::getEdit() * @param int $id * @since [v1.0] - * @return \Illuminate\Http\RedirectResponse */ - public function update(Request $request, $id = null) + public function update(Request $request, Group $group) : RedirectResponse { - if (! $group = Group::find($id)) { - return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id])); - } $group->name = $request->input('name'); $group->permissions = json_encode($request->input('permission')); + $group->notes = $request->input('notes'); if (! config('app.lock_passwords')) { if ($group->save()) { @@ -132,10 +123,8 @@ class GroupsController extends Controller * @see GroupsController::getEdit() * @param int $id * @since [v1.0] - * @return \Illuminate\Http\RedirectResponse - * @throws \Exception */ - public function destroy($id) + public function destroy($id) : RedirectResponse { if (! config('app.lock_passwords')) { if (! $group = Group::find($id)) { @@ -154,17 +143,10 @@ class GroupsController extends Controller * * @author [A. Gianotto] [] * @param $id - * @return \Illuminate\Contracts\View\View * @since [v4.0.11] */ - public function show($id) + public function show(Group $group) : View | RedirectResponse { - $group = Group::find($id); - - if ($group) { - return view('groups/view', compact('group')); - } - - return redirect()->route('groups.index')->with('error', trans('admin/groups/message.group_not_found', ['id' => $id])); + return view('groups/view', compact('group')); } } diff --git a/app/Http/Controllers/HealthController.php b/app/Http/Controllers/HealthController.php index 456f6b6f94..dac1f17bf4 100644 --- a/app/Http/Controllers/HealthController.php +++ b/app/Http/Controllers/HealthController.php @@ -3,22 +3,48 @@ namespace App\Http\Controllers; use Illuminate\Routing\Controller as BaseController; +use Illuminate\Support\Facades\DB; /** - * This controller provide the healthz route for + * This controller provide the health route for * the Snipe-IT Asset Management application. * - * @version v1.0 + * @version v1.0 + * + * @return \Illuminate\Http\JsonResponse + */ class HealthController extends BaseController { + + public function __construct() + { + $this->middleware('health'); + } + + /** * Returns a fixed JSON content ({ "status": "ok"}) which indicate the app is up and running */ public function get() { - return response()->json([ - 'status' => 'ok', - ]); + try { + + if (DB::select('select 2 + 2')) { + return response()->json([ + 'status' => 'ok', + ]); + } + + } catch (\Exception $e) { + \Log::error('Could not connect to database'); + return response()->json([ + 'status' => 'database connection failed', + ], 500); + + } + + + } } diff --git a/app/Http/Controllers/Kits/CheckoutKitController.php b/app/Http/Controllers/Kits/CheckoutKitController.php index d806f7d170..f34dbdc2c0 100644 --- a/app/Http/Controllers/Kits/CheckoutKitController.php +++ b/app/Http/Controllers/Kits/CheckoutKitController.php @@ -4,10 +4,8 @@ namespace App\Http\Controllers\Kits; use App\Http\Controllers\CheckInOutRequest; use App\Http\Controllers\Controller; -use App\Models\PredefinedKit; use App\Models\Asset; -use App\Models\PredefinedLicence; -use App\Models\PredefinedModel; +use App\Models\PredefinedKit; use App\Models\User; use App\Services\PredefinedKitCheckoutService; use Illuminate\Http\Request; @@ -33,14 +31,11 @@ class CheckoutKitController extends Controller * Show Bulk Checkout Page * * @author [D. Minaev.] [] - * @return View View to checkout + * @return \Illuminate\Contracts\View\View View to checkout */ - public function showCheckout($kit_id) + public function showCheckout(PredefinedKit $kit) { $this->authorize('checkout', Asset::class); - - $kit = PredefinedKit::findOrFail($kit_id); - return view('kits/checkout')->with('kit', $kit); } @@ -62,10 +57,10 @@ class CheckoutKitController extends Controller $checkout_result = $this->kitService->checkout($request, $kit, $user); if (Arr::has($checkout_result, 'errors') && count($checkout_result['errors']) > 0) { - return redirect()->back()->with('error', trans('general.checkout_error'))->with('error_messages', $checkout_result['errors']); + return redirect()->back()->with('error', trans('admin/kits/general.checkout_error'))->with('error_messages', $checkout_result['errors']); } - return redirect()->back()->with('success', trans('general.checkout_success')) + return redirect()->back()->with('success', trans('admin/kits/general.checkout_success')) ->with('assets', Arr::get($checkout_result, 'assets', null)) ->with('accessories', Arr::get($checkout_result, 'accessories', null)) ->with('consumables', Arr::get($checkout_result, 'consumables', null)); diff --git a/app/Http/Controllers/Kits/PredefinedKitsController.php b/app/Http/Controllers/Kits/PredefinedKitsController.php index 67d148b5ee..96e4ec4b0a 100644 --- a/app/Http/Controllers/Kits/PredefinedKitsController.php +++ b/app/Http/Controllers/Kits/PredefinedKitsController.php @@ -55,6 +55,7 @@ class PredefinedKitsController extends Controller // Create a new Predefined Kit $kit = new PredefinedKit; $kit->name = $request->input('name'); + $kit->created_by = auth()->id(); if (! $kit->save()) { return redirect()->back()->withInput()->withErrors($kit->getErrors()); @@ -73,19 +74,17 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @since [v1.0] * @param int $kit_id - * @return View + * @return \Illuminate\Contracts\View\View */ - public function edit($kit_id = null) + public function edit(PredefinedKit $kit) { $this->authorize('update', PredefinedKit::class); - if ($kit = PredefinedKit::find($kit_id)) { + return view('kits/edit') ->with('item', $kit) ->with('models', $kit->models) ->with('licenses', $kit->licenses); - } - return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none')); } /** @@ -97,15 +96,9 @@ class PredefinedKitsController extends Controller * @param int $kit_id * @return \Illuminate\Http\RedirectResponse */ - public function update(ImageUploadRequest $request, $kit_id = null) + public function update(ImageUploadRequest $request, PredefinedKit $kit) { $this->authorize('update', PredefinedKit::class); - // Check if the kit exists - if (is_null($kit = PredefinedKit::find($kit_id))) { - // Redirect to the kits management page - return redirect()->route('kits.index')->with('error', trans('admin/kits/general.kit_none')); - } - $kit->name = $request->input('name'); if ($kit->save()) { @@ -150,11 +143,11 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @since [v1.0] * @param int $modelId - * @return View + * @return \Illuminate\Contracts\View\View */ - public function show($kit_id = null) + public function show(PredefinedKit $kit) { - return $this->edit($kit_id); + return $this->edit($kit); } /** @@ -162,7 +155,7 @@ class PredefinedKitsController extends Controller * * @author [D. Minaev] [] * @param int $kit_id - * @return View + * @return \Illuminate\Contracts\View\View */ public function editModel($kit_id, $model_id) { @@ -184,7 +177,7 @@ class PredefinedKitsController extends Controller * * @author [D. Minaev] [] * @param int $modelId - * @return View + * @return \Illuminate\Contracts\View\View */ public function updateModel(Request $request, $kit_id, $model_id) { @@ -214,7 +207,7 @@ class PredefinedKitsController extends Controller * * @author [D. Minaev] [] * @param int $modelId - * @return View + * @return \Illuminate\Contracts\View\View */ public function detachModel($kit_id, $model_id) { @@ -237,7 +230,7 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @param int $kit_id * @param int $license_id - * @return View + * @return \Illuminate\Contracts\View\View */ public function editLicense($kit_id, $license_id) { @@ -262,7 +255,7 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @param int $kit_id * @param int $license_id - * @return View + * @return \Illuminate\Contracts\View\View */ public function updateLicense(Request $request, $kit_id, $license_id) { @@ -293,7 +286,7 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @param int $kit_id * @param int $license_id - * @return View + * @return \Illuminate\Contracts\View\View */ public function detachLicense($kit_id, $license_id) { @@ -316,7 +309,7 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @param int $kit_id * @param int $accessoryId - * @return View + * @return \Illuminate\Contracts\View\View */ public function editAccessory($kit_id, $accessory_id) { @@ -341,7 +334,7 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @param int $kit_id * @param int $accessory_id - * @return View + * @return \Illuminate\Contracts\View\View */ public function updateAccessory(Request $request, $kit_id, $accessory_id) { @@ -371,7 +364,7 @@ class PredefinedKitsController extends Controller * * @author [D. Minaev] [] * @param int $accessory_id - * @return View + * @return \Illuminate\Contracts\View\View */ public function detachAccessory($kit_id, $accessory_id) { @@ -394,7 +387,7 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @param int $kit_id * @param int $consumable_id - * @return View + * @return \Illuminate\Contracts\View\View */ public function editConsumable($kit_id, $consumable_id) { @@ -419,7 +412,7 @@ class PredefinedKitsController extends Controller * @author [D. Minaev] [] * @param int $kit_id * @param int $consumableId - * @return View + * @return \Illuminate\Contracts\View\View */ public function updateConsumable(Request $request, $kit_id, $consumable_id) { @@ -449,7 +442,7 @@ class PredefinedKitsController extends Controller * * @author [D. Minaev] [] * @param int $consumable_id - * @return View + * @return \Illuminate\Contracts\View\View */ public function detachConsumable($kit_id, $consumable_id) { diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php index 1c5715b35f..ce5bf7d249 100755 --- a/app/Http/Controllers/LabelsController.php +++ b/app/Http/Controllers/LabelsController.php @@ -14,8 +14,6 @@ use App\Models\Setting; use App\Models\Supplier; use App\Models\User; use App\View\Label as LabelView; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Storage; class LabelsController extends Controller { @@ -23,7 +21,6 @@ class LabelsController extends Controller * Returns the Label view with test data * * @param string $labelName - * @return \Illuminate\Contracts\View\View * @author Grant Le Roux */ public function show(string $labelName) @@ -41,6 +38,7 @@ class LabelsController extends Controller $exampleAsset->order_number = '12345'; $exampleAsset->purchase_date = '2023-01-01'; $exampleAsset->status_id = 1; + $exampleAsset->location_id = 1; $exampleAsset->company = new Company([ 'name' => trans('admin/labels/table.example_company'), @@ -96,6 +94,5 @@ class LabelsController extends Controller ->with('bulkedit', false) ->with('count', 0); - return redirect()->route('home')->with('error', trans('admin/labels/message.does_not_exist')); } } diff --git a/app/Http/Controllers/Licenses/LicenseCheckinController.php b/app/Http/Controllers/Licenses/LicenseCheckinController.php index be71dfc4d2..72a3d43d51 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckinController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckinController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Licenses; use App\Events\CheckoutableCheckedIn; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Models\License; use App\Models\LicenseSeat; @@ -27,16 +28,11 @@ class LicenseCheckinController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create($seatId = null, $backTo = null) + public function create(LicenseSeat $licenseSeat, $backTo = null) { // Check if the asset exists - if (is_null($licenseSeat = LicenseSeat::find($seatId)) || is_null($license = License::find($licenseSeat->license_id))) { - // Redirect to the asset management page with error - return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found')); - } - + $license = License::find($licenseSeat->license_id); $this->authorize('checkout', $license); - return view('licenses/checkin', compact('licenseSeat'))->with('backto', $backTo); } @@ -70,7 +66,7 @@ class LicenseCheckinController extends Controller if (! $license->reassignable) { // Not allowed to checkin - Session::flash('error', 'License not reassignable.'); + Session::flash('error', trans('admin/licenses/message.checkin.not_reassignable') . '.'); return redirect()->back()->withInput(); } @@ -91,6 +87,7 @@ class LicenseCheckinController extends Controller if($licenseSeat->assigned_to != null){ $return_to = User::find($licenseSeat->assigned_to); + session()->put('checkedInFrom', $return_to->id); } else { $return_to = Asset::find($licenseSeat->asset_id); } @@ -100,15 +97,15 @@ class LicenseCheckinController extends Controller $licenseSeat->asset_id = null; $licenseSeat->notes = $request->input('notes'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + + // Was the asset updated? if ($licenseSeat->save()) { - event(new CheckoutableCheckedIn($licenseSeat, $return_to, Auth::user(), $request->input('notes'))); + event(new CheckoutableCheckedIn($licenseSeat, $return_to, auth()->user(), $request->input('notes'))); - if ($backTo == 'user') { - return redirect()->route('users.show', $return_to->id)->with('success', trans('admin/licenses/message.checkin.success')); - } - return redirect()->route('licenses.show', $licenseSeat->license_id)->with('success', trans('admin/licenses/message.checkin.success')); + return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkin.success')); } // Redirect to the license page with error diff --git a/app/Http/Controllers/Licenses/LicenseCheckoutController.php b/app/Http/Controllers/Licenses/LicenseCheckoutController.php index d3887d365c..564ce97a89 100644 --- a/app/Http/Controllers/Licenses/LicenseCheckoutController.php +++ b/app/Http/Controllers/Licenses/LicenseCheckoutController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Licenses; use App\Events\CheckoutableCheckedOut; +use App\Helpers\Helper; use App\Http\Controllers\Controller; use App\Http\Requests\LicenseCheckoutRequest; use App\Models\Accessory; @@ -27,33 +28,24 @@ class LicenseCheckoutController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create($id) + public function create(License $license) { + $this->authorize('checkout', $license); - if ($license = License::find($id)) { + if ($license->category) { - $this->authorize('checkout', $license); - - if ($license->category) { - - // Make sure there is at least one available to checkout - if ($license->availCount()->count() < 1){ - return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')); - } - - // Return the checkout view - return view('licenses/checkout', compact('license')); + // Make sure there is at least one available to checkout + if ($license->availCount()->count() < 1) { + return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats')); } - // Invalid category - return redirect()->route('licenses.edit', ['license' => $license->id]) - ->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')])); - + // Return the checkout view + return view('licenses/checkout', compact('license')); } - // Not found - return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found')); - + // Invalid category + return redirect()->route('licenses.edit', ['license' => $license->id]) + ->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.license')])); } @@ -76,15 +68,32 @@ class LicenseCheckoutController extends Controller $this->authorize('checkout', $license); $licenseSeat = $this->findLicenseSeatToCheckout($license, $seatId); - $licenseSeat->user_id = Auth::id(); + $licenseSeat->created_by = auth()->id(); $licenseSeat->notes = $request->input('notes'); $checkoutMethod = 'checkoutTo'.ucwords(request('checkout_to_type')); - if ($this->$checkoutMethod($licenseSeat)) { - return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.checkout.success')); + + if ($request->filled('asset_id')) { + + $checkoutTarget = $this->checkoutToAsset($licenseSeat); + $request->request->add(['assigned_asset' => $checkoutTarget->id]); + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'asset']); + + } elseif ($request->filled('assigned_to')) { + $checkoutTarget = $this->checkoutToUser($licenseSeat); + $request->request->add(['assigned_user' => $checkoutTarget->id]); + session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => 'user']); } + + + if ($checkoutTarget) { + return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.checkout.success')); + } + + + return redirect()->route('licenses.index')->with('error', trans('Something went wrong handling this checkout.')); } @@ -94,14 +103,14 @@ class LicenseCheckoutController extends Controller if (! $licenseSeat) { if ($seatId) { - throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'This Seat is not available for checkout.')); + throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.unavailable'))); } - throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'There are no available seats for this license.')); + throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.not_enough_seats'))); } if (! $licenseSeat->license->is($license)) { - throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', 'The license seat provided does not match the license.')); + throw new \Illuminate\Http\Exceptions\HttpResponseException(redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.checkout.mismatch'))); } return $licenseSeat; @@ -119,9 +128,8 @@ class LicenseCheckoutController extends Controller $licenseSeat->assigned_to = $target->assigned_to; } if ($licenseSeat->save()) { - event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes'))); - - return true; + event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes'))); + return $target; } return false; @@ -136,9 +144,8 @@ class LicenseCheckoutController extends Controller $licenseSeat->assigned_to = request('assigned_to'); if ($licenseSeat->save()) { - event(new CheckoutableCheckedOut($licenseSeat, $target, Auth::user(), request('notes'))); - - return true; + event(new CheckoutableCheckedOut($licenseSeat, $target, auth()->user(), request('notes'))); + return $target; } return false; diff --git a/app/Http/Controllers/Licenses/LicenseFilesController.php b/app/Http/Controllers/Licenses/LicenseFilesController.php index fa18e8cf48..6ab3cb7703 100644 --- a/app/Http/Controllers/Licenses/LicenseFilesController.php +++ b/app/Http/Controllers/Licenses/LicenseFilesController.php @@ -112,37 +112,19 @@ class LicenseFilesController extends Controller $this->authorize('view', $license); $this->authorize('licenses.files', $license); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } - - $file = 'private_uploads/licenses/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('NOT EXISTS for '.$file); - Log::debug('NOT EXISTS URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - if (request('inline') == 'true') { - - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); - } - - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) { + $file = 'private_uploads/licenses/'.$log->filename; + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found')); } } + + // The log record doesn't exist somehow + return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found')); + } return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/Licenses/LicensesController.php b/app/Http/Controllers/Licenses/LicensesController.php index 01de4b4d46..ab1e3868d2 100755 --- a/app/Http/Controllers/Licenses/LicensesController.php +++ b/app/Http/Controllers/Licenses/LicensesController.php @@ -99,11 +99,13 @@ class LicensesController extends Controller $license->supplier_id = $request->input('supplier_id'); $license->category_id = $request->input('category_id'); $license->termination_date = $request->input('termination_date'); - $license->user_id = Auth::id(); + $license->created_by = auth()->id(); $license->min_amt = $request->input('min_amt'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($license->save()) { - return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success')); + return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.create.success')); } return redirect()->back()->withInput()->withErrors($license->getErrors()); @@ -119,13 +121,10 @@ class LicensesController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($licenseId = null) + public function edit(License $license) { - if (is_null($item = License::find($licenseId))) { - return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist')); - } - $this->authorize('update', $item); + $this->authorize('update', $license); $maintained_list = [ '' => 'Maintained', @@ -133,7 +132,8 @@ class LicensesController extends Controller '0' => 'No', ]; - return view('licenses/edit', compact('item')) + return view('licenses/edit') + ->with('item', $license) ->with('depreciation_list', Helper::depreciationList()) ->with('maintained_list', $maintained_list); } @@ -151,11 +151,9 @@ class LicensesController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(Request $request, $licenseId = null) + public function update(Request $request, License $license) { - if (is_null($license = License::find($licenseId))) { - return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist')); - } + $this->authorize('update', $license); @@ -180,8 +178,10 @@ class LicensesController extends Controller $license->category_id = $request->input('category_id'); $license->min_amt = $request->input('min_amt'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($license->save()) { - return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success')); + return redirect()->to(Helper::getRedirectOption($request, $license->id, 'Licenses'))->with('success', trans('admin/licenses/message.update.success')); } // If we can't adjust the number of seats, the error is flashed to the session by the event handler in License.php return redirect()->back()->withInput()->withErrors($license->getErrors()); @@ -197,10 +197,10 @@ class LicensesController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($licenseId) + public function destroy(License $license) { // Check if the license exists - if (is_null($license = License::find($licenseId))) { + if (is_null($license = License::find($license->id))) { // Redirect to the license management page return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.not_found')); } @@ -234,14 +234,9 @@ class LicensesController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($licenseId = null) + public function show(License $license) { - $license = License::with('assignedusers')->find($licenseId); - - if (!$license) { - return redirect()->route('licenses.index') - ->with('error', trans('admin/licenses/message.does_not_exist')); - } + $license = License::with('assignedusers')->find($license->id); $users_count = User::where('autoassign_licenses', '1')->count(); $total_seats_count = $license->totalSeatsByLicenseID(); @@ -263,10 +258,10 @@ class LicensesController extends Controller * * @author [A. Gianotto] [] * @param int $licenseId - * @return \Illuminate\Http\RedirectResponse + * @return \Illuminate\Http\RedirectResponse | \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function getClone($licenseId = null) + public function getClone($licenseId = null) : \Illuminate\Contracts\View\View | \Illuminate\Http\RedirectResponse { if (is_null($license_to_clone = License::find($licenseId))) { return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist')); diff --git a/app/Http/Controllers/LocationsController.php b/app/Http/Controllers/LocationsController.php index 938c76fe12..da4e6a7e44 100755 --- a/app/Http/Controllers/LocationsController.php +++ b/app/Http/Controllers/LocationsController.php @@ -2,15 +2,19 @@ namespace App\Http\Controllers; +use App\Helpers\Helper; use App\Http\Requests\ImageUploadRequest; +use App\Models\Actionlog; use App\Models\Asset; +use App\Models\Company; use App\Models\Location; +use App\Models\Setting; use App\Models\User; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; - +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to Locations for * the Snipe-IT Asset Management application. @@ -26,10 +30,8 @@ class LocationsController extends Controller * @author [A. Gianotto] [] * @see LocationsController::getDatatable() method that generates the JSON response * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View { // Grab all the locations $this->authorize('view', Location::class); @@ -43,10 +45,8 @@ class LocationsController extends Controller * @author [A. Gianotto] [] * @see LocationsController::postCreate() method that validates and stores the data * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { $this->authorize('create', Location::class); @@ -62,12 +62,11 @@ class LocationsController extends Controller * @see LocationsController::getCreate() method that makes the form * @since [v1.0] * @param ImageUploadRequest $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : RedirectResponse { $this->authorize('create', Location::class); + $location = new Location(); $location->name = $request->input('name'); $location->parent_id = $request->input('parent_id', null); @@ -80,9 +79,22 @@ class LocationsController extends Controller $location->zip = $request->input('zip'); $location->ldap_ou = $request->input('ldap_ou'); $location->manager_id = $request->input('manager_id'); - $location->user_id = Auth::id(); + $location->created_by = auth()->id(); $location->phone = request('phone'); $location->fax = request('fax'); + $location->notes = $request->input('notes'); + $location->company_id = Company::getIdForCurrentUser($request->input('company_id')); + + // Only scope the location if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $location->company_id = Company::getIdForCurrentUser($request->input('company_id')); + // check if parent is set and has a different company + if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) { + return redirect()->back()->withInput()->withInput()->with('error', 'different company than parent'); + } + } else { + $location->company_id = $request->input('company_id'); + } $location = $request->handleImages($location); @@ -100,18 +112,11 @@ class LocationsController extends Controller * @see LocationsController::postCreate() method that validates and stores * @param int $locationId * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($locationId = null) + public function edit(Location $location) : View | RedirectResponse { $this->authorize('update', Location::class); - // Check if the location exists - if (is_null($item = Location::find($locationId))) { - return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist')); - } - - return view('locations/edit', compact('item')); + return view('locations/edit')->with('item', $location); } /** @@ -121,19 +126,12 @@ class LocationsController extends Controller * @see LocationsController::getEdit() method that makes the form view * @param ImageUploadRequest $request * @param int $locationId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException * @since [v1.0] */ - public function update(ImageUploadRequest $request, $locationId = null) + public function update(ImageUploadRequest $request, Location $location) : RedirectResponse { $this->authorize('update', Location::class); - // Check if the location exists - if (is_null($location = Location::find($locationId))) { - return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist')); - } - // Update the location data $location->name = $request->input('name'); $location->parent_id = $request->input('parent_id', null); $location->currency = $request->input('currency', '$'); @@ -147,6 +145,18 @@ class LocationsController extends Controller $location->fax = request('fax'); $location->ldap_ou = $request->input('ldap_ou'); $location->manager_id = $request->input('manager_id'); + $location->notes = $request->input('notes'); + + // Only scope the location if the setting is enabled + if (Setting::getSettings()->scope_locations_fmcs) { + $location->company_id = Company::getIdForCurrentUser($request->input('company_id')); + // check if there are related objects with different company + if (Helper::test_locations_fmcs(false, $location->id, $location->company_id)) { + return redirect()->back()->withInput()->withInput()->with('error', 'error scoped locations'); + } + } else { + $location->company_id = $request->input('company_id'); + } $location = $request->handleImages($location); @@ -163,12 +173,11 @@ class LocationsController extends Controller * @author [A. Gianotto] [] * @param int $locationId * @since [v1.0] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($locationId) + public function destroy($locationId) : RedirectResponse { $this->authorize('delete', Location::class); + if (is_null($location = Location::find($locationId))) { return redirect()->to(route('locations.index'))->with('error', trans('admin/locations/message.does_not_exist')); } @@ -202,11 +211,18 @@ class LocationsController extends Controller * @author [A. Gianotto] [] * @param int $id * @since [v1.0] - * @return \Illuminate\Contracts\View\View */ - public function show($id = null) + public function show(Location $location) : View | RedirectResponse { - $location = Location::find($id); + $this->authorize('view', Location::class); + + $location = Location::withCount('assignedAssets as assigned_assets_count') + ->withCount('assets as assets_count') + ->withCount('rtd_assets as rtd_assets_count') + ->withCount('children as children_count') + ->withCount('users as users_count') + ->withTrashed() + ->find($location->id); if (isset($location->id)) { return view('locations/view', compact('location')); @@ -215,22 +231,26 @@ class LocationsController extends Controller return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist')); } - public function print_assigned($id) + public function print_assigned($id) : View | RedirectResponse { + $this->authorize('view', Location::class); if ($location = Location::where('id', $id)->first()) { $parent = Location::where('id', $location->parent_id)->first(); $manager = User::where('id', $location->manager_id)->first(); + $company = Company::where('id', $location->company_id)->first(); $users = User::where('location_id', $id)->with('company', 'department', 'location')->get(); $assets = Asset::where('assigned_to', $id)->where('assigned_type', Location::class)->with('model', 'model.category')->get(); - return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager); - + return view('locations/print') + ->with('assets', $assets) + ->with('users',$users) + ->with('location', $location) + ->with('parent', $parent) + ->with('manager', $manager) + ->with('company', $company); } return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist')); - - - } @@ -240,9 +260,8 @@ class LocationsController extends Controller * @author [A. Gianotto] [] * @param int $locationId * @since [v6.0.14] - * @return \Illuminate\Contracts\View\View */ - public function getClone($locationId = null) + public function getClone($locationId = null) : View | RedirectResponse { $this->authorize('create', Location::class); @@ -263,15 +282,57 @@ class LocationsController extends Controller } - public function print_all_assigned($id) + /** + * Restore a given Asset Model (mark as un-deleted) + * + * @author [A. Gianotto] [] + * @since [v1.0] + * @param int $id + */ + public function postRestore($id) : RedirectResponse { + $this->authorize('create', Location::class); + + if ($location = Location::withTrashed()->find($id)) { + + if ($location->deleted_at == '') { + return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.location')])); + } + + if ($location->restore()) { + $logaction = new Actionlog(); + $logaction->item_type = Location::class; + $logaction->item_id = $location->id; + $logaction->created_at = date('Y-m-d H:i:s'); + $logaction->created_by = auth()->id(); + $logaction->logaction('restore'); + + return redirect()->route('locations.index')->with('success', trans('admin/locations/message.restore.success')); + } + + // Check validation + return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.location'), 'error' => $location->getErrors()->first()])); + } + + return redirect()->back()->with('error', trans('admin/models/message.does_not_exist')); + + } + public function print_all_assigned($id) : View | RedirectResponse + { + $this->authorize('view', Location::class); if ($location = Location::where('id', $id)->first()) { $parent = Location::where('id', $location->parent_id)->first(); $manager = User::where('id', $location->manager_id)->first(); + $company = Company::where('id', $location->company_id)->first(); $users = User::where('location_id', $id)->with('company', 'department', 'location')->get(); $assets = Asset::where('location_id', $id)->with('model', 'model.category')->get(); - return view('locations/print')->with('assets', $assets)->with('users', $users)->with('location', $location)->with('parent', $parent)->with('manager', $manager); - + return view('locations/print') + ->with('assets', $assets) + ->with('users',$users) + ->with('location', $location) + ->with('parent', $parent) + ->with('manager', $manager) + ->with('company', $company); } return redirect()->route('locations.index')->with('error', trans('admin/locations/message.does_not_exist')); } @@ -282,10 +343,11 @@ class LocationsController extends Controller * * @author [A. Gianotto] [] * @since [v6.3.1] - * @return \Illuminate\Contracts\View\View */ - public function postBulkDelete(Request $request) + public function postBulkDelete(Request $request) : View | RedirectResponse { + $this->authorize('update', Location::class); + $locations_raw_array = $request->input('ids'); // Make sure some IDs have been selected @@ -315,9 +377,12 @@ class LocationsController extends Controller * * @author [A. Gianotto] [] * @since [v6.3.1] - * @return \Illuminate\Http\RedirectResponse + */ - public function postBulkDeleteStore(Request $request) { + public function postBulkDeleteStore(Request $request) : RedirectResponse + { + $this->authorize('delete', Location::class); + $locations_raw_array = $request->input('ids'); if ((is_array($locations_raw_array)) && (count($locations_raw_array) > 0)) { diff --git a/app/Http/Controllers/LocationsFilesController.php b/app/Http/Controllers/LocationsFilesController.php new file mode 100644 index 0000000000..3aaec0e089 --- /dev/null +++ b/app/Http/Controllers/LocationsFilesController.php @@ -0,0 +1,111 @@ +] + */ + public function store(UploadFileRequest $request, Location $location) : RedirectResponse + { + $this->authorize('update', $location); + + if ($request->hasFile('file')) { + + if (! Storage::exists('private_uploads/locations')) { + Storage::makeDirectory('private_uploads/locations', 775); + } + + foreach ($request->file('file') as $file) { + $file_name = $request->handleFile('private_uploads/locations/','location-'.$location->id, $file); + $location->logUpload($file_name, $request->get('notes')); + } + + return redirect()->back()->withFragment('files')->with('success', trans('general.file_upload_success')); + } + + return redirect()->back()->withFragment('files')->with('error', trans('admin/hardware/message.upload.nofiles')); + } + + /** + * Check for permissions and display the file. + * + * @author [A. Gianotto] [] + * @param int $modelId + * @param int $fileId + * @since [v1.0] + */ + public function show(Location $location, $fileId = null) : StreamedResponse | Response | RedirectResponse | BinaryFileResponse + { + + $this->authorize('view', $location); + + if (! $log = Actionlog::find($fileId)) { + return redirect()->back()->withFragment('files')->with('error', 'No matching file record'); + } + + $file = 'private_uploads/locations/'.$log->filename; + + if (! Storage::exists($file)) { + return redirect()->back()->withFragment('files')->with('error', 'No matching file on server'); + } + + if (request('inline') == 'true') { + + $headers = [ + 'Content-Disposition' => 'inline', + ]; + + return Storage::download($file, $log->filename, $headers); + } + + return StorageHelper::downloader($file); + } + + /** + * Delete the associated file + * + * @author [A. Gianotto] [] + * @param int $modelId + * @param int $fileId + * @since [v1.0] + */ + public function destroy(Location $location, $fileId = null) : RedirectResponse + { + $rel_path = 'private_uploads/locations'; + $this->authorize('update', $location); + $log = Actionlog::find($fileId); + + if ($log) { + + // This should be moved to purge +// if (Storage::exists($rel_path.'/'.$log->filename)) { +// Storage::delete($rel_path.'/'.$log->filename); +// } + $log->delete(); + + return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); + } + + return redirect()->back()->withFragment('files')->with('success', trans('admin/hardware/message.deletefile.success')); + + } +} diff --git a/app/Http/Controllers/ManufacturersController.php b/app/Http/Controllers/ManufacturersController.php index 833c5b6977..cdc5fc1bb8 100755 --- a/app/Http/Controllers/ManufacturersController.php +++ b/app/Http/Controllers/ManufacturersController.php @@ -2,17 +2,16 @@ namespace App\Http\Controllers; -use App\Helpers\Helper; use App\Http\Requests\ImageUploadRequest; use App\Models\Actionlog; -use App\Models\Asset; use App\Models\Manufacturer; -use App\Models\User; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; -use Redirect; use Illuminate\Support\Facades\Log; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to Manufacturers for @@ -29,14 +28,34 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @see Api\ManufacturersController::index() method that generates the JSON response * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View + { + $this->authorize('index', Manufacturer::class); + $manufacturer_count = Manufacturer::withTrashed()->count(); + return view('manufacturers/index')->with('manufacturer_count', $manufacturer_count); + } + + /** + * Returns a view that invokes the ajax tables which actually contains + * the content for the manufacturers listing, which is generated in getDatatable. + * + * @author [A. Gianotto] [] + * @see Api\ManufacturersController::index() method that generates the JSON response + * @since [v1.0] + */ + public function seed() : RedirectResponse { $this->authorize('index', Manufacturer::class); - return view('manufacturers/index'); + $manufacturers_count = Manufacturer::withTrashed()->count(); + + if ($manufacturers_count == 0) { + Artisan::call('db:seed', ['--class' => 'ManufacturerSeeder']); + return redirect()->route('manufacturers.index')->with('success', trans('general.seeding.manufacturers.success')); + } + + return redirect()->route('manufacturers.index')->with('error', trans_choice('general.seeding.manufacturers.error', ['count' => $manufacturers_count])); } /** @@ -45,10 +64,8 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @see ManufacturersController::store() * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { $this->authorize('create', Manufacturer::class); @@ -62,20 +79,19 @@ class ManufacturersController extends Controller * @see ManufacturersController::create() * @since [v1.0] * @param ImageUploadRequest $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : RedirectResponse { $this->authorize('create', Manufacturer::class); $manufacturer = new Manufacturer; $manufacturer->name = $request->input('name'); - $manufacturer->user_id = Auth::id(); + $manufacturer->created_by = auth()->id(); $manufacturer->url = $request->input('url'); $manufacturer->support_url = $request->input('support_url'); $manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url'); $manufacturer->support_phone = $request->input('support_phone'); $manufacturer->support_email = $request->input('support_email'); + $manufacturer->notes = $request->input('notes'); $manufacturer = $request->handleImages($manufacturer); if ($manufacturer->save()) { @@ -92,21 +108,11 @@ class ManufacturersController extends Controller * @see ManufacturersController::update() * @param int $manufacturerId * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($manufacturerId = null) + public function edit(Manufacturer $manufacturer) : View | RedirectResponse { - // Handles manufacturer checks and permissions. $this->authorize('update', Manufacturer::class); - - // Check if the manufacturer exists - if (! $item = Manufacturer::find($manufacturerId)) { - return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist')); - } - - // Show the page - return view('manufacturers/edit', compact('item')); + return view('manufacturers/edit')->with('item', $manufacturer); } /** @@ -116,26 +122,19 @@ class ManufacturersController extends Controller * @see ManufacturersController::getEdit() * @param Request $request * @param int $manufacturerId - * @return \Illuminate\Http\RedirectResponse * @since [v1.0] - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(ImageUploadRequest $request, $manufacturerId = null) + public function update(ImageUploadRequest $request, Manufacturer $manufacturer) : RedirectResponse { $this->authorize('update', Manufacturer::class); - // Check if the manufacturer exists - if (is_null($manufacturer = Manufacturer::find($manufacturerId))) { - // Redirect to the manufacturer page - return redirect()->route('manufacturers.index')->with('error', trans('admin/manufacturers/message.does_not_exist')); - } - // Save the data $manufacturer->name = $request->input('name'); $manufacturer->url = $request->input('url'); $manufacturer->support_url = $request->input('support_url'); $manufacturer->warranty_lookup_url = $request->input('warranty_lookup_url'); $manufacturer->support_phone = $request->input('support_phone'); $manufacturer->support_email = $request->input('support_email'); + $manufacturer->notes = $request->input('notes'); // Set the model's image property to null if the image is being deleted if ($request->input('image_delete') == 1) { @@ -157,10 +156,8 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @param int $manufacturerId * @since [v1.0] - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($manufacturerId) + public function destroy($manufacturerId) : RedirectResponse { $this->authorize('delete', Manufacturer::class); if (is_null($manufacturer = Manufacturer::withTrashed()->withCount('models as models_count')->find($manufacturerId))) { @@ -197,21 +194,11 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @param int $manufacturerId * @since [v1.0] - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($manufacturerId = null) + public function show(Manufacturer $manufacturer) : View | RedirectResponse { $this->authorize('view', Manufacturer::class); - $manufacturer = Manufacturer::find($manufacturerId); - - if (isset($manufacturer->id)) { - return view('manufacturers/view', compact('manufacturer')); - } - - $error = trans('admin/manufacturers/message.does_not_exist'); - // Redirect to the user management page - return redirect()->route('manufacturers.index')->with('error', $error); + return view('manufacturers/view', compact('manufacturer')); } /** @@ -220,10 +207,8 @@ class ManufacturersController extends Controller * @author [A. Gianotto] [] * @since [v4.1.15] * @param int $manufacturers_id - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function restore($id) + public function restore($id) : RedirectResponse { $this->authorize('delete', Manufacturer::class); @@ -238,7 +223,7 @@ class ManufacturersController extends Controller $logaction->item_type = Manufacturer::class; $logaction->item_id = $manufacturer->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = Auth::user()->id; + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); // Redirect them to the deleted page if there are more, otherwise the section index diff --git a/app/Http/Controllers/ModalController.php b/app/Http/Controllers/ModalController.php index 6f6b39dd12..10b17ec8e2 100644 --- a/app/Http/Controllers/ModalController.php +++ b/app/Http/Controllers/ModalController.php @@ -15,7 +15,7 @@ class ModalController extends Controller * @version v5.3.7-pre * @author [Brady Wetherington] [] * @author [A. Gianotto] [with('statuslabel_types', Helper::statusTypeList()); - } - if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) { - $view->with('kitId', $itemId); + $view->with('statuslabel_types', Helper::statusTypeList()); + } + + if (in_array($type, ['kit-model', 'kit-license', 'kit-consumable', 'kit-accessory'])) { + $view->with('kitId', $itemId); } return $view; } diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php new file mode 100644 index 0000000000..c5de66526e --- /dev/null +++ b/app/Http/Controllers/NotesController.php @@ -0,0 +1,42 @@ +authorize('update', Asset::class); + + $validated = $request->validate([ + 'id' => 'required', + 'note' => 'required|string|max:50000', + 'type' => [ + 'required', + Rule::in(['asset']), + ], + ]); + + $item = Asset::findOrFail($validated['id']); + + $this->authorize('update', $item); + + $logaction = new Actionlog; + $logaction->item_id = $item->id; + $logaction->item_type = get_class($item); + $logaction->note = $validated['note']; + $logaction->created_by = Auth::id(); + $logaction->logaction('note added'); + + return redirect() + ->route('hardware.show', $validated['id']) + ->withFragment('history') + ->with('success', trans('general.note_added')); + } +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 72179161f1..bbbc46da6f 100755 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -10,7 +10,8 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; - +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to User Profiles for * the Snipe-IT Asset Management application. @@ -24,12 +25,11 @@ class ProfileController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return \Illuminate\Contracts\View\View */ - public function getIndex() + public function getIndex() : View { $this->authorize('self.profile'); - $user = Auth::user(); + $user = auth()->user(); return view('account/profile', compact('user')); } @@ -38,21 +38,22 @@ class ProfileController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return \Illuminate\Http\RedirectResponse */ - public function postIndex(ImageUploadRequest $request) + public function postIndex(ImageUploadRequest $request) : RedirectResponse { $this->authorize('self.profile'); - $user = Auth::user(); + $user = auth()->user(); $user->first_name = $request->input('first_name'); $user->last_name = $request->input('last_name'); $user->website = $request->input('website'); $user->gravatar = $request->input('gravatar'); $user->skin = $request->input('skin'); $user->phone = $request->input('phone'); + $user->enable_sounds = $request->input('enable_sounds', false); + $user->enable_confetti = $request->input('enable_confetti', false); if (! config('app.lock_passwords')) { - $user->locale = $request->input('locale', 'en-US'); + $user->locale = $request->input('locale'); } if ((Gate::allows('self.two_factor')) && ((Setting::getSettings()->two_factor_enabled == '1') && (! config('app.lock_passwords')))) { @@ -84,7 +85,7 @@ class ProfileController extends Controller * @author [A. Gianotto] [] * @since [v4.0] */ - public function api(): \Illuminate\Contracts\View\View + public function api(): View { // Make sure the self.api permission has been granted if (!Gate::allows('self.api')) { @@ -97,29 +98,29 @@ class ProfileController extends Controller /** * User change email page. * - * @return View */ - public function password() + public function password() : View | RedirectResponse { - $user = Auth::user(); - + + $user = auth()->user(); + if ($user->ldap_import=='1') { + return redirect()->route('account')->with('error', trans('admin/users/message.error.password_ldap')); + } return view('account/change-password', compact('user')); } /** * Users change password form processing page. - * - * @return \Illuminate\Http\RedirectResponse */ - public function passwordSave(Request $request) + public function passwordSave(Request $request) : RedirectResponse { if (config('app.lock_passwords')) { return redirect()->route('account.password.index')->with('error', trans('admin/users/table.lock_passwords')); } - $user = Auth::user(); + $user = auth()->user(); if ($user->ldap_import == '1') { - return redirect()->route('account.password.index')->with('error', trans('admin/users/message.error.password_ldap')); + return redirect()->route('account')->with('error', trans('admin/users/message.error.password_ldap')); } $rules = [ @@ -135,7 +136,7 @@ class ProfileController extends Controller } // This checks to make sure that the user's password isn't the same as their username, - // email address, first name or last name (see https://github.com/snipe/snipe-it/issues/8661) + // email address, first name or last name (see https://github.com/grokability/snipe-it/issues/8661) // While this is handled via SaveUserRequest form request in other places, we have to do this manually // here because we don't have the username, etc form fields available in the profile password change // form. @@ -178,9 +179,8 @@ class ProfileController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return View */ - public function getMenuState(Request $request) + public function getMenuState(Request $request) : void { if ($request->input('state') == 'open') { $request->session()->put('menu_state', 'open'); @@ -195,18 +195,17 @@ class ProfileController extends Controller * * @author A. Gianotto * @since [v6.0.12] - * @return Illuminate\View\View */ - public function printInventory() + public function printInventory() : View { - $show_user = Auth::user(); + $show_users = User::where('id',auth()->user()->id)->get(); return view('users/print') - ->with('assets', Auth::user()->assets) - ->with('licenses', $show_user->licenses()->get()) - ->with('accessories', $show_user->accessories()->get()) - ->with('consumables', $show_user->consumables()->get()) - ->with('show_user', $show_user) + ->with('assets', auth()->user()->assets()) + ->with('licenses', auth()->user()->licenses()->get()) + ->with('accessories', auth()->user()->accessories()->get()) + ->with('consumables', auth()->user()->consumables()->get()) + ->with('users', $show_users) ->with('settings', Setting::getSettings()); } @@ -215,12 +214,11 @@ class ProfileController extends Controller * * @author A. Gianotto * @since [v6.0.12] - * @return \Illuminate\Http\RedirectResponse */ - public function emailAssetList() + public function emailAssetList() : RedirectResponse { - if (!$user = User::find(Auth::user()->id)) { + if (!$user = User::find(auth()->id())) { return redirect()->back() ->with('error', trans('admin/users/message.user_not_found', ['id' => $id])); } @@ -228,7 +226,12 @@ class ProfileController extends Controller return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email')); } - $user->notify((new CurrentInventory($user))); + try { + $user->notify((new CurrentInventory($user))); + } catch (\Exception $e) { + \Log::error($e); + } + return redirect()->back()->with('success', trans('admin/users/general.user_notified')); } } diff --git a/app/Http/Controllers/ReportTemplatesController.php b/app/Http/Controllers/ReportTemplatesController.php new file mode 100644 index 0000000000..2be1da99e0 --- /dev/null +++ b/app/Http/Controllers/ReportTemplatesController.php @@ -0,0 +1,80 @@ +authorize('reports.view'); + + // Ignore "options" rules since data does not come in under that key... + $validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options')); + + $report = $request->user()->reportTemplates()->create([ + 'name' => $validated['name'], + 'options' => $request->except(['_token', 'name']), + ]); + + session()->flash('success', trans('admin/reports/message.create.success')); + + return redirect()->route('report-templates.show', $report->id); + } + + public function show(ReportTemplate $reportTemplate) + { + $this->authorize('reports.view'); + + $customfields = CustomField::get(); + $report_templates = ReportTemplate::orderBy('name')->get(); + + return view('reports/custom', [ + 'customfields' => $customfields, + 'report_templates' => $report_templates, + 'template' => $reportTemplate, + ]); + } + + public function edit(ReportTemplate $reportTemplate) + { + $this->authorize('reports.view'); + + return view('reports/custom', [ + 'customfields' => CustomField::get(), + 'template' => $reportTemplate, + ]); + } + + public function update(Request $request, ReportTemplate $reportTemplate): RedirectResponse + { + $this->authorize('reports.view'); + + // Ignore "options" rules since data does not come in under that key... + $validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options')); + + $reportTemplate->update([ + 'name' => $validated['name'], + 'options' => $request->except(['_token', 'name']), + ]); + + session()->flash('success', trans('admin/reports/message.update.success')); + + return redirect()->route('report-templates.show', $reportTemplate->id); + } + + public function destroy(ReportTemplate $reportTemplate): RedirectResponse + { + $this->authorize('reports.view'); + + $reportTemplate->delete(); + + return redirect()->route('reports/custom') + ->with('success', trans('admin/reports/message.delete.success')); + } +} diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 67a73d0dc0..031e0bbdb5 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -3,28 +3,34 @@ namespace App\Http\Controllers; use App\Helpers\Helper; +use App\Mail\CheckoutAssetMail; use App\Models\Accessory; use App\Models\Actionlog; use App\Models\Asset; +use App\Models\AssetModel; +use App\Models\Category; use App\Models\AssetMaintenance; use App\Models\CheckoutAcceptance; +use App\Models\Company; use App\Models\CustomField; use App\Models\Depreciation; use App\Models\License; +use App\Models\ReportTemplate; use App\Models\Setting; use App\Notifications\CheckoutAssetNotification; use Carbon\Carbon; -use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Notification; -use Illuminate\Support\Facades\Response; -use Illuminate\Support\Facades\View; +use \Illuminate\Contracts\View\View; use League\Csv\Reader; use Symfony\Component\HttpFoundation\StreamedResponse; use League\Csv\EscapeFormula; use App\Http\Requests\CustomAssetReportRequest; use Illuminate\Support\Facades\Log; - +use Illuminate\Http\RedirectResponse; /** * This controller handles all actions related to Reports for @@ -47,9 +53,9 @@ class ReportsController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return View + * @return \Illuminate\Contracts\View\View */ - public function getAccessoryReport() + public function getAccessoryReport() : View { $this->authorize('reports.view'); @@ -65,7 +71,7 @@ class ReportsController extends Controller * @since [v1.0] * @return \Illuminate\Http\Response */ - public function exportAccessoryReport() + public function exportAccessoryReport() : Response { $this->authorize('reports.view'); $accessories = Accessory::orderBy('created_at', 'DESC')->get(); @@ -92,7 +98,7 @@ class ReportsController extends Controller } $csv = implode("\n", $rows); - $response = Response::make($csv, 200); + $response = response()->make($csv, 200); $response->header('Content-Type', 'text/csv'); $response->header('Content-disposition', 'attachment;filename=report.csv'); @@ -104,9 +110,8 @@ class ReportsController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return View */ - public function getDeprecationReport() + public function getDeprecationReport() : View { $this->authorize('reports.view'); $depreciations = Depreciation::get(); @@ -119,9 +124,8 @@ class ReportsController extends Controller * @deprecated Server-side exports have been replaced by datatables export since v2. * @author [A. Gianotto] [] * @since [v1.0] - * @return \Illuminate\Http\Response */ - public function exportDeprecationReport() + public function exportDeprecationReport() : Response { $this->authorize('reports.view'); // Grab all the assets @@ -180,7 +184,7 @@ class ReportsController extends Controller $currency = e(Setting::getSettings()->default_currency); } - $row[] = $asset->purchase_date; + $row[] = Helper::getFormattedDateObject($asset->purchase_date, 'date', false); $row[] = $currency.Helper::formatCurrencyOutput($asset->purchase_cost); $row[] = $currency.Helper::formatCurrencyOutput($asset->getDepreciatedValue()); $row[] = $currency.Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue())); @@ -197,12 +201,10 @@ class ReportsController extends Controller * * @author [A. Gianotto] [] * @since [v4.0] - * @return View */ - public function audit() + public function audit() : View { $this->authorize('reports.view'); - return view('reports/audit'); } @@ -212,9 +214,8 @@ class ReportsController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return View */ - public function getActivityReport() + public function getActivityReport() : View { $this->authorize('reports.view'); @@ -226,9 +227,8 @@ class ReportsController extends Controller * * @author [A. Gianotto] [] * @since [v5.0.7] - * @return \Illuminate\Http\Response */ - public function postActivityReport(Request $request) + public function postActivityReport(Request $request) : StreamedResponse { ini_set('max_execution_time', 12000); $this->authorize('reports.view'); @@ -243,7 +243,7 @@ class ReportsController extends Controller $header = [ trans('general.date'), - trans('general.admin'), + trans('general.created_by'), trans('general.action'), trans('general.type'), trans('general.item'), @@ -264,9 +264,9 @@ class ReportsController extends Controller $executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']; Log::debug('Added headers: '.$executionTime); - $actionlogs = Actionlog::with('item', 'user', 'target', 'location') + $actionlogs = Actionlog::with('item', 'user', 'target', 'location', 'adminuser') ->orderBy('created_at', 'DESC') - ->chunk(20, function ($actionlogs) use ($handle) { + ->chunk(500, function ($actionlogs) use ($handle) { $executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']; Log::debug('Walking results: '.$executionTime); $count = 0; @@ -291,7 +291,7 @@ class ReportsController extends Controller $row = [ $actionlog->created_at, - ($actionlog->admin) ? e($actionlog->admin->getFullNameAttribute()) : '', + ($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '', $actionlog->present()->actionType(), e($actionlog->itemType()), ($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name, @@ -328,9 +328,8 @@ class ReportsController extends Controller * * @author [A. Gianotto] [] * @since [v1.0] - * @return View */ - public function getLicenseReport() + public function getLicenseReport() : View { $this->authorize('reports.view'); $licenses = License::with('depreciation')->orderBy('created_at', 'DESC') @@ -346,9 +345,8 @@ class ReportsController extends Controller * @deprecated Server-side exports have been replaced by datatables export since v2. * @author [A. Gianotto] [] * @since [v1.0] - * @return \Illuminate\Http\Response */ - public function exportLicenseReport() + public function exportLicenseReport() : Response { $this->authorize('reports.view'); $licenses = License::orderBy('created_at', 'DESC')->get(); @@ -385,7 +383,7 @@ class ReportsController extends Controller $csv = implode("\n", $rows); - $response = Response::make($csv, 200); + $response = response()->make($csv, 200); $response->header('Content-Type', 'text/csv'); $response->header('Content-disposition', 'attachment;filename=report.csv'); @@ -398,14 +396,28 @@ class ReportsController extends Controller * @author [A. Gianotto] [] * @see ReportsController::postCustomReport() method that generates the CSV * @since [v1.0] - * @return \Illuminate\Http\Response */ - public function getCustomReport() + public function getCustomReport(Request $request) : View { $this->authorize('reports.view'); $customfields = CustomField::get(); + $report_templates = ReportTemplate::orderBy('name')->get(); - return view('reports/custom')->with('customfields', $customfields); + // The view needs a template to render correctly, even if it is empty... + $template = new ReportTemplate; + + // Set the report's input values in the cases we were redirected back + // with validation errors so the report is populated as expected. + if ($request->old()) { + $template->name = $request->old('name'); + $template->options = $request->old(); + } + + return view('reports/custom', [ + 'customfields' => $customfields, + 'report_templates' => $report_templates, + 'template' => $template, + ]); } /** @@ -414,9 +426,8 @@ class ReportsController extends Controller * @author [A. Gianotto] [] * @see ReportsController::getCustomReport() method that generates form view * @since [v1.0] - * @return \Illuminate\Http\Response */ - public function postCustom(CustomAssetReportRequest $request) + public function postCustom(CustomAssetReportRequest $request) : StreamedResponse { ini_set('max_execution_time', env('REPORT_TIME_LIMIT', 12000)); //12000 seconds = 200 minutes $this->authorize('reports.view'); @@ -474,7 +485,7 @@ class ReportsController extends Controller $header[] = trans('admin/hardware/table.purchase_date'); } - if (($request->filled('purchase_cost')) || ($request->filled('depreciation'))) { + if ($request->filled('purchase_cost')) { $header[] = trans('admin/hardware/table.purchase_cost'); } @@ -482,6 +493,17 @@ class ReportsController extends Controller $header[] = trans('admin/hardware/table.eol'); } + if ($request->filled('warranty')) { + $header[] = trans('admin/hardware/form.warranty'); + $header[] = trans('admin/hardware/form.warranty_expires'); + } + + if ($request->filled('depreciation')) { + $header[] = trans('admin/hardware/table.book_value'); + $header[] = trans('admin/hardware/table.diff'); + $header[] = trans('admin/hardware/form.fully_depreciated'); + } + if ($request->filled('order')) { $header[] = trans('admin/hardware/form.order'); } @@ -568,17 +590,6 @@ class ReportsController extends Controller $header[] = trans('general.status'); } - if ($request->filled('warranty')) { - $header[] = trans('admin/hardware/form.warranty'); - $header[] = trans('admin/hardware/form.warranty_expires'); - } - - if ($request->filled('depreciation')) { - $header[] = trans('admin/hardware/table.book_value'); - $header[] = trans('admin/hardware/table.diff'); - $header[] = trans('admin/hardware/form.fully_depreciated'); - } - if ($request->filled('checkout_date')) { $header[] = trans('admin/hardware/table.checkout_date'); } @@ -681,15 +692,15 @@ class ReportsController extends Controller } if (($request->filled('created_start')) && ($request->filled('created_end'))) { - $created_start = \Carbon::parse($request->input('created_start'))->startOfDay(); - $created_end = \Carbon::parse($request->input('created_end'))->endOfDay(); + $created_start = Carbon::parse($request->input('created_start'))->startOfDay(); + $created_end = Carbon::parse($request->input('created_end'))->endOfDay(); $assets->whereBetween('assets.created_at', [$created_start, $created_end]); } if (($request->filled('checkout_date_start')) && ($request->filled('checkout_date_end'))) { - $checkout_start = \Carbon::parse($request->input('checkout_date_start'))->startOfDay(); - $checkout_end = \Carbon::parse($request->input('checkout_date_end',now()))->endOfDay(); + $checkout_start = Carbon::parse($request->input('checkout_date_start'))->startOfDay(); + $checkout_end = Carbon::parse($request->input('checkout_date_end',now()))->endOfDay(); $actionlogassets = Actionlog::where('action_type','=', 'checkout') ->where('item_type', 'LIKE', '%Asset%',) @@ -700,9 +711,9 @@ class ReportsController extends Controller } if (($request->filled('checkin_date_start'))) { - $checkin_start = \Carbon::parse($request->input('checkin_date_start'))->startOfDay(); + $checkin_start = Carbon::parse($request->input('checkin_date_start'))->startOfDay(); // use today's date is `checkin_date_end` is not provided - $checkin_end = \Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(); + $checkin_end = Carbon::parse($request->input('checkin_date_end', now()))->endOfDay(); $assets->whereBetween('assets.last_checkin', [$checkin_start, $checkin_end ]); } @@ -712,9 +723,13 @@ class ReportsController extends Controller $assets->whereBetween('assets.expected_checkin', [$request->input('expected_checkin_start'), $request->input('expected_checkin_end')]); } + if (($request->filled('asset_eol_date_start')) && ($request->filled('asset_eol_date_end'))) { + $assets->whereBetween('assets.asset_eol_date', [$request->input('asset_eol_date_start'), $request->input('asset_eol_date_end')]); + } + if (($request->filled('last_audit_start')) && ($request->filled('last_audit_end'))) { - $last_audit_start = \Carbon::parse($request->input('last_audit_start'))->startOfDay(); - $last_audit_end = \Carbon::parse($request->input('last_audit_end'))->endOfDay(); + $last_audit_start = Carbon::parse($request->input('last_audit_start'))->startOfDay(); + $last_audit_end = Carbon::parse($request->input('last_audit_end'))->endOfDay(); $assets->whereBetween('assets.last_audit_date', [$last_audit_start, $last_audit_end]); } @@ -722,6 +737,11 @@ class ReportsController extends Controller if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) { $assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]); } + + if (($request->filled('last_updated_start')) && ($request->filled('last_updated_end'))) { + $assets->whereBetween('assets.updated_at', [$request->input('last_updated_start'), $request->input('last_updated_end')]); + } + if ($request->filled('exclude_archived')) { $assets->notArchived(); } @@ -733,7 +753,7 @@ class ReportsController extends Controller } Log::debug($assets->toSql()); - $assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) { + $assets->orderBy('assets.id', 'ASC')->chunk(500, function ($assets) use ($handle, $customfields, $request) { $executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']; Log::debug('Walking results: '.$executionTime); @@ -787,7 +807,20 @@ class ReportsController extends Controller } if ($request->filled('eol')) { - $row[] = ($asset->asset_eol_date) ? $asset->asset_eol_date : ''; + $row[] = ($asset->purchase_date != '') ? $asset->asset_eol_date : ''; + } + + if ($request->filled('warranty')) { + $row[] = ($asset->warranty_months) ? $asset->warranty_months : ''; + $row[] = $asset->present()->warranty_expires(); + } + + if ($request->filled('depreciation')) { + $depreciation = $asset->getDepreciatedValue(); + $diff = ($asset->purchase_cost - $depreciation); + $row[] = Helper::formatCurrencyOutput($depreciation); + $row[] = Helper::formatCurrencyOutput($diff); + $row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : ''; } if ($request->filled('order')) { @@ -923,19 +956,6 @@ class ReportsController extends Controller $row[] = ($asset->assetstatus) ? $asset->assetstatus->name.' ('.$asset->present()->statusMeta.')' : ''; } - if ($request->filled('warranty')) { - $row[] = ($asset->warranty_months) ? $asset->warranty_months : ''; - $row[] = $asset->present()->warranty_expires(); - } - - if ($request->filled('depreciation')) { - $depreciation = $asset->getDepreciatedValue(); - $diff = ($asset->purchase_cost - $depreciation); - $row[] = Helper::formatCurrencyOutput($depreciation); - $row[] = Helper::formatCurrencyOutput($diff); - $row[] = (($asset->depreciation) && ($asset->depreciated_date())) ? $asset->depreciated_date()->format('Y-m-d') : ''; - } - if ($request->filled('checkout_date')) { $row[] = ($asset->last_checkout) ? $asset->last_checkout : ''; } @@ -1015,11 +1035,10 @@ class ReportsController extends Controller /** * getImprovementsReport * - * @return View * @author Vincent Sposato * @version v1.0 */ - public function getAssetMaintenancesReport() + public function getAssetMaintenancesReport() : View { $this->authorize('reports.view'); @@ -1029,11 +1048,10 @@ class ReportsController extends Controller /** * exportImprovementsReport * - * @return \Illuminate\Http\Response * @author Vincent Sposato * @version v1.0 */ - public function exportAssetMaintenancesReport() + public function exportAssetMaintenancesReport() : Response { $this->authorize('reports.view'); // Grab all the improvements @@ -1068,10 +1086,10 @@ class ReportsController extends Controller $row[] = e($assetMaintenance->start_date); $row[] = e($assetMaintenance->completion_date); if (is_null($assetMaintenance->asset_maintenance_time)) { - $improvementTime = intval(Carbon::now() - ->diffInDays(Carbon::parse($assetMaintenance->start_date))); + $improvementTime = (int) Carbon::now() + ->diffInDays(Carbon::parse($assetMaintenance->start_date), true); } else { - $improvementTime = intval($assetMaintenance->asset_maintenance_time); + $improvementTime = (int) $assetMaintenance->asset_maintenance_time; } $row[] = $improvementTime; $row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost); @@ -1080,7 +1098,7 @@ class ReportsController extends Controller // spit out a csv $csv = implode("\n", $rows); - $response = Response::make($csv, 200); + $response = response()->make($csv, 200); $response->header('Content-Type', 'text/csv'); $response->header('Content-disposition', 'attachment;filename=report.csv'); @@ -1090,39 +1108,39 @@ class ReportsController extends Controller /** * getAssetAcceptanceReport * - * @return mixed - * @throws \Illuminate\Auth\Access\AuthorizationException - * @author Vincent Sposato * @version v1.0 */ - public function getAssetAcceptanceReport($deleted = false) + public function getAssetAcceptanceReport($deleted = false) : View { $this->authorize('reports.view'); $showDeleted = $deleted == 'deleted'; - /** - * Get all assets with pending checkout acceptances - */ - if($showDeleted) { - $acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->withTrashed()->with(['assignedTo' , 'checkoutable.assignedTo', 'checkoutable.model'])->get(); - } else { - $acceptances = CheckoutAcceptance::pending()->where('checkoutable_type', 'App\Models\Asset')->with(['assignedTo' => function ($query) { - $query->withTrashed(); - }, 'checkoutable.assignedTo', 'checkoutable.model'])->get(); + $query = CheckoutAcceptance::pending() + ->where('checkoutable_type', 'App\Models\Asset') + ->with([ + 'checkoutable' => function (MorphTo $query) { + $query->morphWith([ + AssetModel::class => ['model'], + Company::class => ['company'], + Asset::class => ['assignedTo'], + ])->with('model.category'); + }, + 'assignedTo' => function($query){ + $query->withTrashed(); + } + ]); + + if ($showDeleted) { + $query->withTrashed(); } - $assetsForReport = $acceptances - ->filter(function ($acceptance) { - $acceptance_checkoutable_flag = false; - if ($acceptance->checkoutable){ - $acceptance_checkoutable_flag = $acceptance->checkoutable->checkedOutToUser(); - } - - return $acceptance->checkoutable_type == 'App\Models\Asset' && $acceptance_checkoutable_flag; - }) - ->map(function($acceptance) { - return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance]; + $assetsForReport = $query->get() + ->map(function ($acceptance) { + return [ + 'assetItem' => $acceptance->checkoutable, + 'acceptance' => $acceptance, + ]; }); return view('reports/unaccepted_assets', compact('assetsForReport','showDeleted' )); @@ -1132,11 +1150,9 @@ class ReportsController extends Controller * sentAssetAcceptanceReminder * * @param integer|null $acceptanceId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException * @version v1.0 */ - public function sentAssetAcceptanceReminder(Request $request) + public function sentAssetAcceptanceReminder(Request $request) : RedirectResponse { $this->authorize('reports.view'); @@ -1162,27 +1178,15 @@ class ReportsController extends Controller } $logItem = $logItem_res[0]; } + $email = $assetItem->assignedTo?->email; + $locale = $assetItem->assignedTo?->locale; - // Only send notification if assigned - if ($assetItem->assignedTo) { - - if (!$assetItem->assignedTo->locale) { - Notification::locale(Setting::getSettings()->locale)->send( - $assetItem->assignedTo, - new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note) - ); - } else { - Notification::send( - $assetItem->assignedTo, - new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note) - ); - } - } - - if ($assetItem->assignedTo->email == ''){ + if (is_null($email) || $email === '') { return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email')); } + Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale)); + return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent')); } @@ -1190,11 +1194,9 @@ class ReportsController extends Controller * sentAssetAcceptanceReminder * * @param integer|null $acceptanceId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException * @version v1.0 */ - public function deleteAssetAcceptance($acceptanceId = null) + public function deleteAssetAcceptance($acceptanceId = null) : RedirectResponse { $this->authorize('reports.view'); @@ -1213,11 +1215,10 @@ class ReportsController extends Controller /** * Exports the AssetAcceptance report to CSV * - * @return \Illuminate\Http\Response * @author Vincent Sposato * @version v1.0 */ - public function postAssetAcceptanceReport($deleted = false) + public function postAssetAcceptanceReport($deleted = false) : Response { $this->authorize('reports.view'); $showDeleted = $deleted == 'deleted'; @@ -1268,7 +1269,7 @@ class ReportsController extends Controller // spit out a csv $csv = implode("\n", $rows); - $response = Response::make($csv, 200); + $response = response()->make($csv, 200); $response->header('Content-Type', 'text/csv'); $response->header('Content-disposition', 'attachment;filename=report.csv'); @@ -1284,7 +1285,7 @@ class ReportsController extends Controller * @author Vincent Sposato * @version v1.0 */ - protected function getCheckedOutAssetsRequiringAcceptance($modelsInCategoriesThatRequireAcceptance) + protected function getCheckedOutAssetsRequiringAcceptance($modelsInCategoriesThatRequireAcceptance) : View { $this->authorize('reports.view'); $assets = Asset::deployed() @@ -1304,11 +1305,11 @@ class ReportsController extends Controller * @author Vincent Sposato * @version v1.0 */ - protected function getModelsInCategoriesThatRequireAcceptance($assetCategoriesRequiringAcceptance) + protected function getModelsInCategoriesThatRequireAcceptance($assetCategoriesRequiringAcceptance) : array { $this->authorize('reports.view'); - return array_pluck(Model::inCategory($assetCategoriesRequiringAcceptance) + return array_pluck(AssetModel::inCategory($assetCategoriesRequiringAcceptance) ->select('id') ->get() ->toArray(), 'id'); @@ -1321,7 +1322,7 @@ class ReportsController extends Controller * @author Vincent Sposato * @version v1.0 */ - protected function getCategoriesThatRequireAcceptance() + protected function getCategoriesThatRequireAcceptance() : array { $this->authorize('reports.view'); @@ -1334,11 +1335,10 @@ class ReportsController extends Controller /** * getAssetsCheckedOutRequiringAcceptance * - * @return array * @author Vincent Sposato * @version v1.0 */ - protected function getAssetsCheckedOutRequiringAcceptance() + protected function getAssetsCheckedOutRequiringAcceptance() : array { $this->authorize('reports.view'); diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php old mode 100755 new mode 100644 index a3c3cab8f7..b652455a57 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -7,19 +7,26 @@ use App\Helpers\StorageHelper; use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\SettingsSamlRequest; use App\Http\Requests\SetupUserRequest; +use App\Http\Requests\StoreLdapSettings; +use App\Http\Requests\StoreLocalizationSettings; +use App\Http\Requests\StoreNotificationSettings; +use App\Http\Requests\StoreLabelSettings; +use App\Http\Requests\StoreSecuritySettings; use App\Models\CustomField; use App\Models\Group; +use App\Models\Labels\Label as LabelModel; use App\Models\Setting; use App\Models\Asset; use App\Models\User; use App\Notifications\FirstAdminNotification; use App\Notifications\MailTest; -use Illuminate\Http\Client\HttpClientException; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Storage; use Illuminate\Validation\Rule; -use Redirect; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\JsonResponse; +use \Illuminate\Contracts\View\View; use Illuminate\Support\Str; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Auth; @@ -30,6 +37,7 @@ use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\Validator; +use Symfony\Component\HttpFoundation\BinaryFileResponse; /** * This controller handles all actions related to Settings for @@ -47,9 +55,9 @@ class SettingsController extends Controller * * @since [v3.0] * - * @return View + * @return \Illuminate\Contracts\View\View | \Illuminate\Http\Response */ - public function getSetupIndex() + public function getSetupIndex() : View { $start_settings['php_version_min'] = false; @@ -123,14 +131,14 @@ class SettingsController extends Controller * @return bool This method will return true when exceptions (such as curl exception) is thrown. * Check the log files to see more details about the exception. */ - protected function dotEnvFileIsExposed() + protected function dotEnvFileIsExposed() : bool { try { - return Http::timeout(10) + return Http::withoutVerifying()->timeout(10) ->accept('*/*') ->get(URL::to('.env')) ->successful(); - } catch (HttpClientException $e) { + } catch (\Exception $e) { Log::debug($e->getMessage()); return true; } @@ -153,13 +161,12 @@ class SettingsController extends Controller * Save the first admin user from Setup. * * @author [A. Gianotto] [] - * * @since [v3.0] * - * @return \Illuminate\Http\RedirectResponse */ - public function postSaveFirstAdmin(SetupUserRequest $request) + public function postSaveFirstAdmin(SetupUserRequest $request) : RedirectResponse { + $user = new User(); $user->first_name = $data['first_name'] = $request->input('first_name'); $user->last_name = $request->input('last_name'); @@ -180,12 +187,13 @@ class SettingsController extends Controller $settings->brand = 1; $settings->locale = $request->input('locale', 'en-US'); $settings->default_currency = $request->input('default_currency', 'USD'); - $settings->user_id = 1; + $settings->created_by = 1; $settings->email_domain = $request->input('email_domain'); $settings->email_format = $request->input('email_format'); $settings->next_auto_tag_base = 1; $settings->auto_increment_assets = $request->input('auto_increment_assets', 0); $settings->auto_increment_prefix = $request->input('auto_increment_prefix'); + $settings->zerofill_count = $request->input('zerofill_count') ?: 0; if ((! $user->isValid()) || (! $settings->isValid())) { return redirect()->back()->withInput()->withErrors($user->getErrors())->withErrors($settings->getErrors()); @@ -214,10 +222,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v3.0] - * - * @return View */ - public function getSetupUser() + public function getSetupUser() : View { return view('setup/user') ->with('step', 3) @@ -230,10 +236,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v3.0] - * - * @return View */ - public function getSetupDone() + public function getSetupDone() : View { return view('setup/done') ->with('step', 4) @@ -247,15 +251,13 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v3.0] - * - * @return View */ - public function getSetupMigrate() + public function getSetupMigrate() : View { Artisan::call('migrate', ['--force' => true]); if ((! file_exists(storage_path().'/oauth-private.key')) || (! file_exists(storage_path().'/oauth-public.key'))) { Artisan::call('migrate', ['--path' => 'vendor/laravel/passport/database/migrations', '--force' => true]); - Artisan::call('passport:install'); + Artisan::call('passport:install', ['--no-interaction' => true]); } return view('setup/migrate') @@ -270,29 +272,25 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function index() + public function index() : View { $settings = Setting::getSettings(); return view('settings/index', compact('settings')); } + /** - * Return the admin settings page. + * Return a form to allow a super admin to update settings. * * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function getEdit() + public function getSettings() : View { $setting = Setting::getSettings(); - return view('settings/general', compact('setting')); } @@ -302,26 +300,9 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function getSettings() - { - $setting = Setting::getSettings(); + public function postSettings(Request $request) : RedirectResponse - return view('settings/general', compact('setting')); - } - - /** - * Return a form to allow a super admin to update settings. - * - * @author [A. Gianotto] [] - * - * @since [v1.0] - * - * @return View - */ - public function postSettings(Request $request) { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -333,8 +314,25 @@ class SettingsController extends Controller $setting->modellist_displays = implode(',', $request->input('show_in_model_list')); } + $old_locations_fmcs = $setting->scope_locations_fmcs; $setting->full_multiple_companies_support = $request->input('full_multiple_companies_support', '0'); + $setting->scope_locations_fmcs = $request->input('scope_locations_fmcs', '0'); + + // Backward compatibility for locations makes no sense without FullMultipleCompanySupport + if (!$setting->full_multiple_companies_support) { + $setting->scope_locations_fmcs = '0'; + } + + // check for inconsistencies when activating scoped locations + if ($old_locations_fmcs == '0' && $setting->scope_locations_fmcs == '1') { + $mismatched = Helper::test_locations_fmcs(false); + if (count($mismatched) != 0) { + return redirect()->back()->withInput()->with('error', trans_choice('admin/settings/message.location_scoping.mismatch', count($mismatched)).' '.trans('admin/settings/message.location_scoping.not_saved')); + } + } + $setting->unique_serial = $request->input('unique_serial', '0'); + $setting->shortcuts_enabled = $request->input('shortcuts_enabled', '0'); $setting->show_images_in_email = $request->input('show_images_in_email', '0'); $setting->show_archived_in_list = $request->input('show_archived_in_list', '0'); $setting->dashboard_message = $request->input('dashboard_message'); @@ -348,12 +346,13 @@ class SettingsController extends Controller } $setting->default_eula_text = $request->input('default_eula_text'); - $setting->load_remote = $request->input('load_remote', 0); $setting->thumbnail_max_h = $request->input('thumbnail_max_h'); $setting->privacy_policy_link = $request->input('privacy_policy_link'); $setting->depreciation_method = $request->input('depreciation_method'); $setting->dash_chart_type = $request->input('dash_chart_type'); $setting->profile_edit = $request->input('profile_edit', 0); + $setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0); + if ($request->input('per_page') != '') { $setting->per_page = $request->input('per_page'); @@ -376,10 +375,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function getBranding() + public function getBranding() : View { $setting = Setting::getSettings(); @@ -392,11 +389,10 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function postBranding(ImageUploadRequest $request) + public function postBranding(ImageUploadRequest $request) : RedirectResponse { + // Something has gone horribly wrong - no settings record exists! if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); } @@ -407,51 +403,75 @@ class SettingsController extends Controller $setting->version_footer = $request->input('version_footer'); $setting->footer_text = $request->input('footer_text'); $setting->skin = $request->input('skin'); - $setting->allow_user_skin = $request->input('allow_user_skin'); + $setting->allow_user_skin = $request->input('allow_user_skin', '0'); $setting->show_url_in_emails = $request->input('show_url_in_emails', '0'); $setting->logo_print_assets = $request->input('logo_print_assets', '0'); + $setting->load_remote = $request->input('load_remote', 0); - // Only allow the site name and CSS to be changed if lock_passwords is false + // Only allow the site name, images, and CSS to be changed if lock_passwords is false // Because public demos make people act like dicks - if (! config('app.lock_passwords')) { - $request->validate(['site_name' => 'required']); - $setting->site_name = $request->input('site_name'); + if (!config('app.lock_passwords')) { + + if ($request->has('site_name')) { + $request->validate(['site_name' => 'required']); + } + + $setting->site_name = $request->input('site_name', 'Snipe-IT'); $setting->custom_css = $request->input('custom_css'); + + // Logo upload $setting = $request->handleImages($setting, 600, 'logo', '', 'logo'); - if ('1' == $request->input('clear_logo')) { - Storage::disk('public')->delete($setting->logo); + if ($request->input('clear_logo') == '1') { + $setting = $request->deleteExistingImage($setting, '', 'logo'); $setting->logo = null; $setting->brand = 1; } - + // Email logo upload $setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo'); - - - if ('1' == $request->input('clear_email_logo')) { - Storage::disk('public')->delete($setting->email_logo); + if ($request->input('clear_email_logo') == '1') { + $setting = $request->deleteExistingImage($setting, '', 'email_logo'); $setting->email_logo = null; - // If they are uploading an image, validate it and upload it } - + // Label logo upload $setting = $request->handleImages($setting, 600, 'label_logo', '', 'label_logo'); - if ('1' == $request->input('clear_label_logo')) { - Storage::disk('public')->delete($setting->label_logo); + if ($request->input('clear_label_logo') == '1') { + $setting = $request->deleteExistingImage($setting, '', 'label_logo'); $setting->label_logo = null; } + // Acceptance PDF upload + $setting = $request->handleImages($setting, 600, 'acceptance_pdf_logo', '', 'acceptance_pdf_logo'); + if ('1' == $request->input('clear_acceptance_pdf_logo')) { + $setting = $request->deleteExistingImage($setting, '', 'acceptance_pdf_logo'); + $setting->acceptance_pdf_logo = null; + } - $setting = $request->handleImages($setting, 600, 'favicon', '', 'favicon'); - - // If the user wants to clear the favicon... + // Favicon upload + $setting = $request->handleImages($setting, 100, 'favicon', '', 'favicon'); if ('1' == $request->input('clear_favicon')) { - Storage::disk('public')->delete($setting->favicon); + $setting = $request->deleteExistingImage($setting, '', 'favicon'); $setting->favicon = null; } + + + // Default avatar upload + $setting = $request->handleImages($setting, 500, 'default_avatar', 'avatars', 'default_avatar'); + if ($request->input('clear_default_avatar') == '1') { + // Don't delete the file, just update the field if this is the default + if ($setting->default_avatar!='default.png') { + $setting = $request->deleteExistingImage($setting, 'avatars', 'default_avatar'); + } + $setting->default_avatar = null; + } + + if ($request->input('restore_default_avatar') == '1') { + $setting->default_avatar = 'default.png'; + } } if ($setting->save()) { @@ -469,10 +489,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function getSecurity() + public function getSecurity() : View { $setting = Setting::getSettings(); @@ -485,10 +503,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function postSecurity(Request $request) + public function postSecurity(StoreSecuritySettings $request) : RedirectResponse { $this->validate($request, [ 'pwd_secure_complexity' => 'array', @@ -543,10 +559,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function getLocalization() + public function getLocalization() : View { $setting = Setting::getSettings(); @@ -559,10 +573,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function postLocalization(Request $request) + public function postLocalization(StoreLocalizationSettings $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -591,10 +603,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function getAlerts() + public function getAlerts() : View { $setting = Setting::getSettings(); @@ -605,12 +615,9 @@ class SettingsController extends Controller * Return a form to allow a super admin to update settings. * * @author [A. Gianotto] [] - * * @since [v1.0] - * - * @return View */ - public function postAlerts(Request $request) + public function postAlerts(StoreNotificationSettings $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -643,11 +650,13 @@ class SettingsController extends Controller $setting->alert_email = $alert_email; $setting->admin_cc_email = $admin_cc_email; + $setting->admin_cc_always = $request->validated('admin_cc_always'); $setting->alerts_enabled = $request->input('alerts_enabled', '0'); $setting->alert_interval = $request->input('alert_interval'); $setting->alert_threshold = $request->input('alert_threshold'); $setting->audit_interval = $request->input('audit_interval'); $setting->audit_warning_days = $request->input('audit_warning_days'); + $setting->due_checkin_days = $request->input('due_checkin_days'); $setting->show_alerts_in_menu = $request->input('show_alerts_in_menu', '0'); if ($setting->save()) { @@ -664,10 +673,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function getSlack() + public function getSlack() : View { $setting = Setting::getSettings(); @@ -680,10 +687,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function getAssetTags() + public function getAssetTags() : View { $setting = Setting::getSettings(); @@ -696,10 +701,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.0] - * - * @return View */ - public function postAssetTags(Request $request) + public function postAssetTags(Request $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -718,62 +721,14 @@ class SettingsController extends Controller return redirect()->back()->withInput()->withErrors($setting->getErrors()); } - /** - * Return a form to allow a super admin to update settings. - * - * @author [A. Gianotto] [] - * - * @since [v1.0] - * - * @return View - */ - public function getBarcodes() - { - $setting = Setting::getSettings(); - $is_gd_installed = extension_loaded('gd'); - - return view('settings.barcodes', compact('setting'))->with('is_gd_installed', $is_gd_installed); - } - - /** - * Saves settings from form. - * - * @author [A. Gianotto] [] - * - * @since [v1.0] - * - * @return View - */ - public function postBarcodes(Request $request) - { - if (is_null($setting = Setting::getSettings())) { - return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); - } - - $setting->qr_code = $request->input('qr_code', '0'); - $setting->alt_barcode = $request->input('alt_barcode'); - $setting->alt_barcode_enabled = $request->input('alt_barcode_enabled', '0'); - $setting->barcode_type = $request->input('barcode_type'); - $setting->qr_text = $request->input('qr_text'); - - if ($setting->save()) { - return redirect()->route('settings.index') - ->with('success', trans('admin/settings/message.update.success')); - } - - return redirect()->back()->withInput()->withErrors($setting->getErrors()); - } - /** * Return a form to allow a super admin to update settings. * * @author [A. Gianotto] [] * * @since [v4.0] - * - * @return View */ - public function getPhpInfo() + public function getPhpInfo() : View | RedirectResponse { if (config('app.debug') === true) { return view('settings.phpinfo'); @@ -787,15 +742,15 @@ class SettingsController extends Controller * Return a form to allow a super admin to update settings. * * @author [A. Gianotto] [] - * * @since [v4.0] - * - * @return View */ - public function getLabels() + public function getLabels() : View { + $is_gd_installed = extension_loaded('gd'); + return view('settings.labels') ->with('setting', Setting::getSettings()) + ->with('is_gd_installed', $is_gd_installed) ->with('customFields', CustomField::where('field_encrypted', '=', 0)->get()); } @@ -803,12 +758,9 @@ class SettingsController extends Controller * Saves settings from form. * * @author [A. Gianotto] [] - * * @since [v4.0] - * - * @return View */ - public function postLabels(Request $request) + public function postLabels(StoreLabelSettings $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -821,6 +773,7 @@ class SettingsController extends Controller $setting->label2_2d_type = $request->input('label2_2d_type'); $setting->label2_2d_target = $request->input('label2_2d_target'); $setting->label2_fields = $request->input('label2_fields'); + $setting->label2_empty_row_count = $request->input('label2_empty_row_count'); $setting->labels_per_page = $request->input('labels_per_page'); $setting->labels_width = $request->input('labels_width'); $setting->labels_height = $request->input('labels_height'); @@ -834,9 +787,13 @@ class SettingsController extends Controller $setting->labels_pagewidth = $request->input('labels_pagewidth'); $setting->labels_pageheight = $request->input('labels_pageheight'); $setting->labels_display_company_name = $request->input('labels_display_company_name', '0'); - $setting->labels_display_company_name = $request->input('labels_display_company_name', '0'); - + //Barcodes + $setting->qr_code = $request->input('qr_code', '0'); + //1D-Barcode + $setting->alt_barcode_enabled = $request->input('alt_barcode_enabled', '0'); + //QR-Code + $setting->qr_text = $request->input('qr_text'); if ($request->filled('labels_display_name')) { $setting->labels_display_name = 1; @@ -869,6 +826,7 @@ class SettingsController extends Controller } if ($setting->save()) { + return redirect()->route('settings.labels.index') ->with('success', trans('admin/settings/message.update.success')); } @@ -882,45 +840,21 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v4.0] - * - * @return View */ - public function getLdapSettings() + public function getLdapSettings() : View { $setting = Setting::getSettings(); $groups = Group::pluck('name', 'id'); - - - /** - * This validator is only temporary (famous last words.) - @snipe - */ - $messages = [ - 'ldap_username_field.not_in' => 'sAMAccountName (mixed case) will likely not work. You should use samaccountname (lowercase) instead. ', - 'ldap_auth_filter_query.not_in' => 'uid=samaccountname is probably not a valid auth filter. You probably want uid= ', - 'ldap_filter.regex' => 'This value should probably not be wrapped in parentheses.', - ]; - - $validator = Validator::make($setting->toArray(), [ - 'ldap_username_field' => 'not_in:sAMAccountName', - 'ldap_auth_filter_query' => 'not_in:uid=samaccountname|required_if:ldap_enabled,1', - 'ldap_filter' => 'nullable|regex:"^[^(]"|required_if:ldap_enabled,1', - ], $messages); - - - - return view('settings.ldap', compact('setting', 'groups'))->withErrors($validator); + return view('settings.ldap', compact('setting', 'groups')); } /** * Saves settings from form. * * @author [A. Gianotto] [] - * * @since [v4.0] - * - * @return View */ - public function postLdapSettings(Request $request) + public function postLdapSettings(StoreLdapSettings $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -942,7 +876,8 @@ class SettingsController extends Controller $setting->ldap_fname_field = $request->input('ldap_fname_field'); $setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query'); $setting->ldap_version = $request->input('ldap_version', 3); - $setting->ldap_active_flag = $request->input('ldap_active_flag'); + $setting->ldap_active_flag = $request->input('ldap_active_flag', 0); + $setting->ldap_invert_active_flag = $request->input('ldap_invert_active_flag', 0); $setting->ldap_emp_num = $request->input('ldap_emp_num'); $setting->ldap_email = $request->input('ldap_email'); $setting->ldap_manager = $request->input('ldap_manager'); @@ -962,7 +897,6 @@ class SettingsController extends Controller } if ($setting->save()) { - $setting->update_client_side_cert_files(); return redirect()->route('settings.ldap.index') ->with('success', trans('admin/settings/message.update.success')); } @@ -974,15 +908,11 @@ class SettingsController extends Controller * Return a form to allow a super admin to update settings. * * @author Johnson Yi - * * @since v5.0.0 - * - * @return View */ - public function getSamlSettings() + public function getSamlSettings() : View { $setting = Setting::getSettings(); - return view('settings.saml', compact('setting')); } @@ -990,12 +920,9 @@ class SettingsController extends Controller * Saves settings from form. * * @author Johnson Yi - * * @since v5.0.0 - * - * @return View */ - public function postSamlSettings(SettingsSamlRequest $request) + public function postSamlSettings(SettingsSamlRequest $request) : RedirectResponse { if (is_null($setting = Setting::getSettings())) { return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error')); @@ -1024,10 +951,13 @@ class SettingsController extends Controller return redirect()->back()->withInput()->withErrors($setting->getErrors()); } - public static function getPDFBranding() - { - $pdf_branding= Setting::getSettings(); + /** + * Do we need this? Can we not just call getSettings() directly? + */ + public static function getPDFBranding() : Setting + { + $pdf_branding = Setting::getSettings(); return $pdf_branding; } @@ -1037,9 +967,8 @@ class SettingsController extends Controller * * @author [A. Gianotto] [] * @since [v6.1.1] - * @return View */ - public function getGoogleLoginSettings() + public function getGoogleLoginSettings() : View { $setting = Setting::getSettings(); return view('settings.google', compact('setting')); @@ -1050,9 +979,8 @@ class SettingsController extends Controller * * @author [A. Gianotto] [] * @since [v6.1.1] - * @return View */ - public function postGoogleLoginSettings(Request $request) + public function postGoogleLoginSettings(Request $request) : RedirectResponse { if (!config('app.lock_passwords')) { $setting = Setting::getSettings(); @@ -1079,10 +1007,8 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v1.8] - * - * @return View */ - public function getBackups() + public function getBackups() : View { $settings = Setting::getSettings(); $path = 'app/backups'; @@ -1118,12 +1044,9 @@ class SettingsController extends Controller * Process the backup. * * @author [A. Gianotto] [] - * * @since [v1.8] - * - * @return \Illuminate\Http\RedirectResponse */ - public function postBackups() + public function postBackups() : RedirectResponse { if (! config('app.lock_passwords')) { Artisan::call('snipeit:backup', ['--filename' => 'manual-backup-'.date('Y-m-d-H-i-s')]); @@ -1152,12 +1075,9 @@ class SettingsController extends Controller * Download the backup file. * * @author [A. Gianotto] [] - * * @since [v1.8] - * - * @return Storage */ - public function downloadFile($filename = null) + public function downloadFile($filename = null) : RedirectResponse | BinaryFileResponse { $path = 'app/backups'; @@ -1178,12 +1098,9 @@ class SettingsController extends Controller * Delete the backup file. * * @author [A. Gianotto] [] - * * @since [v1.8] - * - * @return View */ - public function deleteFile($filename = null) + public function deleteFile($filename = null) : RedirectResponse { if (config('app.allow_backup_delete')=='true') { @@ -1207,7 +1124,7 @@ class SettingsController extends Controller } // Hell to the no - Log::warning('User ID '.Auth::user()->id.' is attempting to delete backup file '.$filename.' and is not authorized to.'); + Log::warning('User ID '.auth()->id().' is attempting to delete backup file '.$filename.' and is not authorized to.'); return redirect()->route('settings.backups.index')->with('error', trans('general.backup_delete_not_allowed')); } @@ -1216,13 +1133,11 @@ class SettingsController extends Controller * Uploads a backup file * * @author [A. Gianotto] [] - * * @since [v6.0] - * - * @return \Illuminate\Http\RedirectResponse */ - public function postUploadBackup(Request $request) { + public function postUploadBackup(Request $request) : RedirectResponse + { if (! config('app.lock_passwords')) { if (!$request->hasFile('file')) { @@ -1254,12 +1169,9 @@ class SettingsController extends Controller * Restore the backup file. * * @author [A. Gianotto] [] - * * @since [v6.0] - * - * @return View */ - public function postRestore($filename = null) + public function postRestore(Request $request, $filename = null): RedirectResponse { if (! config('app.lock_passwords')) { @@ -1268,7 +1180,7 @@ class SettingsController extends Controller if (Storage::exists($path.'/'.$filename)) { // grab the user's info so we can make sure they exist in the system - $user = User::find(Auth::user()->id); + $user = User::find(auth()->id()); // TODO: run a backup @@ -1279,13 +1191,29 @@ class SettingsController extends Controller Log::debug('Attempting to restore from: '. storage_path($path).'/'.$filename); - // run the restore command - Artisan::call('snipeit:restore', - [ + $restore_params = [ '--force' => true, '--no-progress' => true, - 'filename' => storage_path($path).'/'.$filename - ]); + 'filename' => storage_path($path) . '/' . $filename + ]; + + if ($request->input('clean')) { + Log::debug("Attempting 'clean' - first, guessing prefix..."); + Artisan::call('snipeit:restore', [ + '--sanitize-guess-prefix' => true, + 'filename' => storage_path($path) . '/' . $filename + ]); + $guess_prefix_output = Artisan::output(); + Log::debug("Sanitize output is: $guess_prefix_output"); + list($prefix, $_output) = explode("\n", $guess_prefix_output); + Log::debug("prefix is: '$prefix'"); + $restore_params['--sanitize-with-prefix'] = $prefix; + } + + // run the restore command + Artisan::call('snipeit:restore', + $restore_params + ); // If it's greater than 300, it probably worked $output = Artisan::output(); @@ -1312,7 +1240,7 @@ class SettingsController extends Controller DB::table('users')->update(['remember_token' => null]); Auth::logout(); - return redirect()->route('login')->with('success', 'Your system has been restored. Please login again.'); + return redirect()->route('login')->with('success', trans('admin/settings/message.restore.success')); } else { return redirect()->route('settings.backups.index')->with('error', trans('admin/settings/message.backup.file_not_found')); } @@ -1327,13 +1255,11 @@ class SettingsController extends Controller * @author [A. Gianotto] [] * * @since [v4.0] - * - * @return View */ - public function getPurge() + public function getPurge() : View | RedirectResponse { - Log::warning('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting a PURGE'); + Log::warning('User '.auth()->user()->username.' (ID: '.auth()->id().') is attempting a PURGE'); if (config('app.allow_purge')=='true') { return view('settings.purge-form'); @@ -1346,14 +1272,11 @@ class SettingsController extends Controller * Purges soft-deletes. * * @author [A. Gianotto] [] - * * @since [v3.0] - * - * @return View */ - public function postPurge(Request $request) + public function postPurge(Request $request) : RedirectResponse { - Log::warning('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting a PURGE'); + Log::warning('User '.auth()->user()->username.' (ID'.auth()->id().') is attempting a PURGE'); if (config('app.allow_purge')=='true') { Log::debug('Purging is not allowed via the .env'); @@ -1362,7 +1285,7 @@ class SettingsController extends Controller if ($request->input('confirm_purge')=='DELETE') { - Log::warning('User ID ' . Auth::user()->id . ' initiated a PURGE!'); + Log::warning('User ID ' . auth()->id() . ' initiated a PURGE!'); // Run a backup immediately before processing Artisan::call('backup:run'); Artisan::call('snipeit:purge', ['--force' => 'true', '--no-interaction' => true]); @@ -1380,7 +1303,7 @@ class SettingsController extends Controller } } - Log::error('User '.Auth::user()->username.' (ID'.Auth::user()->id.') is attempting to purge deleted data and is not authorized to.'); + Log::error('User '.auth()->user()->username.' (ID'.auth()->id().') is attempting to purge deleted data and is not authorized to.'); // Nope. @@ -1395,12 +1318,9 @@ class SettingsController extends Controller * in the routes file if you want to be able to cache the routes. * * @author [A. Gianotto] [] - * * @since [v4.0] - * - * @return View */ - public function api() + public function api() : View { return view('settings.api'); } @@ -1409,12 +1329,9 @@ class SettingsController extends Controller * Test the email configuration. * * @author [A. Gianotto] [] - * * @since [v3.0] - * - * @return \Illuminate\Http\RedirectResponse */ - public function ajaxTestEmail() + public function ajaxTestEmail() : JsonResponse { try { (new User())->forceFill([ @@ -1422,13 +1339,20 @@ class SettingsController extends Controller 'email' => config('mail.from.address'), ])->notify(new MailTest()); - return response()->json(Helper::formatStandardApiResponse('success', null, 'Maiol sent!')); + return response()->json(Helper::formatStandardApiResponse('success', null, trans('mail_sent.mail_sent'))); } catch (\Exception $e) { return response()->json(Helper::formatStandardApiResponse('success', null, $e->getMessage())); } } - public function getLoginAttempts() + + + /** + * Get login attempts view + * + * @author [A. Gianotto] [] + */ + public function getLoginAttempts() : View { return view('settings.logins'); } diff --git a/app/Http/Controllers/StatuslabelsController.php b/app/Http/Controllers/StatuslabelsController.php index 0c559560e0..6fd261206c 100755 --- a/app/Http/Controllers/StatuslabelsController.php +++ b/app/Http/Controllers/StatuslabelsController.php @@ -6,6 +6,8 @@ use App\Helpers\Helper; use App\Models\Statuslabel; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to Status Labels for @@ -17,34 +19,24 @@ class StatuslabelsController extends Controller { /** * Show a list of all the statuslabels. - * - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View { $this->authorize('view', Statuslabel::class); - return view('statuslabels.index'); } - public function show($id) + public function show(Statuslabel $statuslabel) : View | RedirectResponse { $this->authorize('view', Statuslabel::class); - if ($statuslabel = Statuslabel::find($id)) { - return view('statuslabels.view')->with('statuslabel', $statuslabel); - } - - return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist')); + return view('statuslabels.view')->with('statuslabel', $statuslabel); } /** * Statuslabel create. * - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { // Show the page $this->authorize('create', Statuslabel::class); @@ -58,10 +50,8 @@ class StatuslabelsController extends Controller * Statuslabel create form processing. * * @param Request $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(Request $request) + public function store(Request $request) : RedirectResponse { $this->authorize('create', Statuslabel::class); // create a new model instance @@ -75,7 +65,7 @@ class StatuslabelsController extends Controller // Save the Statuslabel data $statusLabel->name = $request->input('name'); - $statusLabel->user_id = Auth::id(); + $statusLabel->created_by = auth()->id(); $statusLabel->notes = $request->input('notes'); $statusLabel->deployable = $statusType['deployable']; $statusLabel->pending = $statusType['pending']; @@ -96,40 +86,26 @@ class StatuslabelsController extends Controller * Statuslabel update. * * @param int $statuslabelId - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($statuslabelId = null) + public function edit(Statuslabel $statuslabel) : View | RedirectResponse { $this->authorize('update', Statuslabel::class); - // Check if the Statuslabel exists - if (is_null($item = Statuslabel::find($statuslabelId))) { - // Redirect to the blogs management page - return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist')); - } - - $use_statuslabel_type = $item->getStatuslabelType(); $statuslabel_types = ['' => trans('admin/hardware/form.select_statustype')] + ['undeployable' => trans('admin/hardware/general.undeployable')] + ['pending' => trans('admin/hardware/general.pending')] + ['archived' => trans('admin/hardware/general.archived')] + ['deployable' => trans('admin/hardware/general.deployable')]; - return view('statuslabels/edit', compact('item', 'statuslabel_types'))->with('use_statuslabel_type', $use_statuslabel_type); + return view('statuslabels/edit', compact('statuslabel_types')) + ->with('item', $statuslabel) + ->with('use_statuslabel_type', $statuslabel); } /** * Statuslabel update form processing page. * * @param int $statuslabelId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(Request $request, $statuslabelId = null) + public function update(Request $request, Statuslabel $statuslabel) : RedirectResponse { $this->authorize('update', Statuslabel::class); - // Check if the Statuslabel exists - if (is_null($statuslabel = Statuslabel::find($statuslabelId))) { - // Redirect to the blogs management page - return redirect()->route('statuslabels.index')->with('error', trans('admin/statuslabels/message.does_not_exist')); - } if (! $request->filled('statuslabel_types')) { return redirect()->back()->withInput()->withErrors(['statuslabel_types' => trans('validation.statuslabel_type')]); @@ -159,10 +135,8 @@ class StatuslabelsController extends Controller * Delete the given Statuslabel. * * @param int $statuslabelId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($statuslabelId) + public function destroy($statuslabelId) : RedirectResponse { $this->authorize('delete', Statuslabel::class); // Check if the Statuslabel exists diff --git a/app/Http/Controllers/SuppliersController.php b/app/Http/Controllers/SuppliersController.php index b901224714..d96031a9aa 100755 --- a/app/Http/Controllers/SuppliersController.php +++ b/app/Http/Controllers/SuppliersController.php @@ -5,6 +5,8 @@ namespace App\Http\Controllers; use App\Http\Requests\ImageUploadRequest; use App\Models\Supplier; use Illuminate\Support\Facades\Auth; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; /** * This controller handles all actions related to Suppliers for @@ -20,25 +22,19 @@ class SuppliersController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function index() + public function index() : View { - // Grab all the suppliers $this->authorize('view', Supplier::class); - - // Show the page return view('suppliers/index'); } /** * Supplier create. * - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function create() + public function create() : View { $this->authorize('create', Supplier::class); - return view('suppliers/edit')->with('item', new Supplier); } @@ -46,10 +42,8 @@ class SuppliersController extends Controller * Supplier create form processing. * * @param ImageUploadRequest $request - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function store(ImageUploadRequest $request) + public function store(ImageUploadRequest $request) : RedirectResponse { $this->authorize('create', Supplier::class); // Create a new supplier @@ -68,7 +62,7 @@ class SuppliersController extends Controller $supplier->email = request('email'); $supplier->notes = request('notes'); $supplier->url = $supplier->addhttp(request('url')); - $supplier->user_id = Auth::id(); + $supplier->created_by = auth()->id(); $supplier = $request->handleImages($supplier); if ($supplier->save()) { @@ -82,38 +76,21 @@ class SuppliersController extends Controller * Supplier update. * * @param int $supplierId - * @return \Illuminate\Contracts\View\View - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($supplierId = null) + public function edit(Supplier $supplier) : View | RedirectResponse { $this->authorize('update', Supplier::class); - // Check if the supplier exists - if (is_null($item = Supplier::find($supplierId))) { - // Redirect to the supplier page - return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist')); - } - - // Show the page - return view('suppliers/edit', compact('item')); + return view('suppliers/edit')->with('item', $supplier); } /** * Supplier update form processing page. * * @param int $supplierId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update($supplierId, ImageUploadRequest $request) + public function update(ImageUploadRequest $request, Supplier $supplier) : RedirectResponse { $this->authorize('update', Supplier::class); - // Check if the supplier exists - if (is_null($supplier = Supplier::find($supplierId))) { - // Redirect to the supplier page - return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist')); - } - // Save the data $supplier->name = request('name'); $supplier->address = request('address'); @@ -141,10 +118,8 @@ class SuppliersController extends Controller * Delete the given supplier. * * @param int $supplierId - * @return \Illuminate\Http\RedirectResponse - * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy($supplierId) + public function destroy($supplierId) : RedirectResponse { $this->authorize('delete', Supplier::class); if (is_null($supplier = Supplier::with('asset_maintenances', 'assets', 'licenses')->withCount('asset_maintenances as asset_maintenances_count', 'assets as assets_count', 'licenses as licenses_count')->find($supplierId))) { @@ -174,18 +149,12 @@ class SuppliersController extends Controller * Get the asset information to present to the supplier view page * * @param null $supplierId - * @return \Illuminate\Contracts\View\View * @internal param int $assetId */ - public function show($supplierId = null) + public function show(Supplier $supplier) : View | RedirectResponse { $this->authorize('view', Supplier::class); - $supplier = Supplier::find($supplierId); + return view('suppliers/view', compact('supplier')); - if (isset($supplier->id)) { - return view('suppliers/view', compact('supplier')); - } - - return redirect()->route('suppliers.index')->with('error', trans('admin/suppliers/message.does_not_exist')); } } diff --git a/app/Http/Controllers/Users/BulkUsersController.php b/app/Http/Controllers/Users/BulkUsersController.php index d028560607..0269f3fd0c 100644 --- a/app/Http/Controllers/Users/BulkUsersController.php +++ b/app/Http/Controllers/Users/BulkUsersController.php @@ -13,9 +13,12 @@ use App\Models\Group; use App\Models\LicenseSeat; use App\Models\ConsumableAssignment; use App\Models\Consumable; +use App\Models\Setting; use App\Models\User; +use App\Notifications\CurrentInventory; use Carbon\Carbon; use Illuminate\Http\Request; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Password; @@ -29,12 +32,12 @@ class BulkUsersController extends Controller * @author [A. Gianotto] [] * @since [v1.7] * @param Request $request - * @return \Illuminate\Contracts\View\View + * @return \Illuminate\Contracts\View\View | \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ public function edit(Request $request) { - $this->authorize('update', User::class); + $this->authorize('view', User::class); // Make sure there were users selected if (($request->filled('ids')) && (count($request->input('ids')) > 0)) { @@ -42,20 +45,44 @@ class BulkUsersController extends Controller // Get the list of affected users $user_raw_array = request('ids'); $users = User::whereIn('id', $user_raw_array) - ->with('groups', 'assets', 'licenses', 'accessories')->get(); + ->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations','uploads', 'acceptances')->get(); // bulk edit, display the bulk edit form if ($request->input('bulk_actions') == 'edit') { + $this->authorize('update', User::class); return view('users/bulk-edit', compact('users')) ->with('groups', Group::pluck('name', 'id')); + // bulk send assigned inventory + } elseif ($request->input('bulk_actions') == 'send_assigned') { + $this->authorize('update', User::class); + + $users_without_email = 0; + foreach ($users as $user) { + if (empty($user->email)) { + $users_without_email++; + } else { + $user->notify((new CurrentInventory($user))); + } + } + + if ($users_without_email == 0) { + return redirect()->back()->with('success', trans_choice('admin/users/general.users_notified', $users->count())); + } else { + return redirect()->back()->with('warning', trans_choice('admin/users/general.users_notified_warning', $users->count(), ['no_email' => $users_without_email])); + } + + + + // bulk delete, display the bulk delete confirmation form } elseif ($request->input('bulk_actions') == 'delete') { + $this->authorize('delete', User::class); return view('users/confirm-bulk-delete')->with('users', $users)->with('statuslabel_list', Helper::statusLabelList()); // merge, confirm they have at least 2 users selected and display the merge screen } elseif ($request->input('bulk_actions') == 'merge') { - + $this->authorize('delete', User::class); if (($request->filled('ids')) && (count($request->input('ids')) > 1)) { return view('users/confirm-merge')->with('users', $users); // Not enough users selected, send them back @@ -66,7 +93,7 @@ class BulkUsersController extends Controller // bulk password reset, just do the thing } elseif ($request->input('bulk_actions') == 'bulkpasswordreset') { foreach ($users as $user) { - if (($user->activated == '1') && ($user->email != '')) { + if (($user->activated == '1') && ($user->email != '') && ($user->ldap_import != '1')) { $credentials = ['email' => $user->email]; Password::sendResetLink($credentials/* , function (Message $message) { $message->subject($this->getEmailSubject()); // TODO - I'm not sure if we still need this, but this second parameter is no longer accepted in later Laravel versions. @@ -75,6 +102,33 @@ class BulkUsersController extends Controller } return redirect()->back()->with('success', trans('admin/users/message.password_resets_sent')); + } elseif ($request->input('bulk_actions') == 'print') { + $users = User::query() + ->with([ + 'assets.assetlog', + 'assets.assignedAssets.assetlog', + 'assets.assignedAssets.defaultLoc', + 'assets.assignedAssets.location', + 'assets.assignedAssets.model.category', + 'assets.defaultLoc', + 'assets.location', + 'assets.model.category', + 'accessories.assetlog', + 'accessories.category', + 'accessories.manufacturer', + 'consumables.assetlog', + 'consumables.category', + 'consumables.manufacturer', + 'licenses.category', + ]) + ->withTrashed() + ->findMany($request->input('ids')); + + $users->each(fn($user) => $this->authorize('view', $user)); + + return view('users.print') + ->with('users', $users) + ->with('settings', Setting::getSettings()); } } @@ -100,9 +154,9 @@ class BulkUsersController extends Controller $user_raw_array = $request->input('ids'); // Remove the user from any updates. - $user_raw_array = array_diff($user_raw_array, [Auth::id()]); + $user_raw_array = array_diff($user_raw_array, [auth()->id()]); $manager_conflict = false; - $users = User::whereIn('id', $user_raw_array)->where('id', '!=', Auth::user()->id)->get(); + $users = User::whereIn('id', $user_raw_array)->where('id', '!=', auth()->id())->get(); $return_array = [ 'success' => trans('admin/users/message.success.update_bulk'), @@ -115,6 +169,9 @@ class BulkUsersController extends Controller ->conditionallyAddItem('remote') ->conditionallyAddItem('ldap_import') ->conditionallyAddItem('activated') + ->conditionallyAddItem('start_date') + ->conditionallyAddItem('end_date') + ->conditionallyAddItem('city') ->conditionallyAddItem('autoassign_licenses'); @@ -145,13 +202,24 @@ class BulkUsersController extends Controller $this->update_array['company_id'] = null; } - + if ($request->input('null_start_date')=='1') { + $this->update_array['start_date'] = null; + } + + if ($request->input('null_end_date')=='1') { + $this->update_array['end_date'] = null; + } + + if ($request->input('null_locale')=='1') { + $this->update_array['locale'] = null; + } + if (! $manager_conflict) { $this->conditionallyAddItem('manager_id'); } // Save the updated info User::whereIn('id', $user_raw_array) - ->where('id', '!=', Auth::id())->update($this->update_array); + ->where('id', '!=', auth()->id())->update($this->update_array); if (array_key_exists('location_id', $this->update_array)){ Asset::where('assigned_type', User::class) @@ -213,26 +281,23 @@ class BulkUsersController extends Controller $user_raw_array = request('ids'); - if (($key = array_search(Auth::id(), $user_raw_array)) !== false) { + if (($key = array_search(auth()->id(), $user_raw_array)) !== false) { unset($user_raw_array[$key]); } $users = User::whereIn('id', $user_raw_array)->get(); - $assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', \App\Models\User::class)->get(); - $accessories = DB::table('accessories_users')->whereIn('assigned_to', $user_raw_array)->get(); + $assets = Asset::whereIn('assigned_to', $user_raw_array)->where('assigned_type', User::class)->get(); + $accessoryUserRows = DB::table('accessories_checkout')->where('assigned_type', User::class)->whereIn('assigned_to', $user_raw_array)->get(); $licenses = DB::table('license_seats')->whereIn('assigned_to', $user_raw_array)->get(); - $consumables = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get(); + $consumableUserRows = DB::table('consumables_users')->whereIn('assigned_to', $user_raw_array)->get(); if ((($assets->count() > 0) && ((!$request->filled('status_id')) || ($request->input('status_id') == '')))) { return redirect()->route('users.index')->with('error', 'No status selected'); } - $this->logItemCheckinAndDelete($assets, Asset::class); - $this->logItemCheckinAndDelete($accessories, Accessory::class); + $this->logAccessoriesCheckin($accessoryUserRows); $this->logItemCheckinAndDelete($licenses, License::class); - $this->logItemCheckinAndDelete($consumables, Consumable::class); - Asset::whereIn('id', $assets->pluck('id'))->update([ 'status_id' => e(request('status_id')), @@ -241,19 +306,14 @@ class BulkUsersController extends Controller 'expected_checkin' => null, ]); - LicenseSeat::whereIn('id', $licenses->pluck('id'))->update(['assigned_to' => null]); - ConsumableAssignment::whereIn('id', $consumables->pluck('id'))->delete(); - + ConsumableAssignment::whereIn('id', $consumableUserRows->pluck('id'))->delete(); foreach ($users as $user) { - - $user->consumables()->sync([]); $user->accessories()->sync([]); if ($request->input('delete_user')=='1') { $user->delete(); } - } $msg = trans('general.bulk_checkin_success'); @@ -279,13 +339,27 @@ class BulkUsersController extends Controller if ($itemType == License::class){ $item_id = $item->license_id; } - + $logAction->item_id = $item_id; // We can't rely on get_class here because the licenses/accessories fetched above are not eloquent models, but simply arrays. $logAction->item_type = $itemType; $logAction->target_id = $item->assigned_to; $logAction->target_type = User::class; - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); + $logAction->note = 'Bulk checkin items'; + $logAction->logaction('checkin from'); + } + } + + private function logAccessoriesCheckin(Collection $accessoryUserRows): void + { + foreach ($accessoryUserRows as $accessoryUserRow) { + $logAction = new Actionlog(); + $logAction->item_id = $accessoryUserRow->accessory_id; + $logAction->item_type = Accessory::class; + $logAction->target_id = $accessoryUserRow->assigned_to; + $logAction->target_type = User::class; + $logAction->created_by = auth()->id(); $logAction->note = 'Bulk checkin items'; $logAction->logaction('checkin from'); } @@ -317,8 +391,8 @@ class BulkUsersController extends Controller // Get the users $merge_into_user = User::find($request->input('merge_into_id')); - $users_to_merge = User::whereIn('id', $user_ids_to_merge)->with('assets', 'licenses', 'consumables','accessories')->get(); - $admin = User::find(Auth::user()->id); + $users_to_merge = User::whereIn('id', $user_ids_to_merge)->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations','uploads', 'acceptances')->get(); + $admin = User::find(auth()->id()); // Walk users foreach ($users_to_merge as $user_to_merge) { @@ -344,10 +418,20 @@ class BulkUsersController extends Controller } foreach ($user_to_merge->userlog as $log) { - $log->target_id = $user_to_merge->id; + $log->target_id = $merge_into_user->id; $log->save(); } + foreach ($user_to_merge->uploads as $upload) { + $upload->item_id = $merge_into_user->id; + $upload->save(); + } + + foreach ($user_to_merge->acceptances as $acceptance) { + $acceptance->item_id = $merge_into_user->id; + $acceptance->save(); + } + User::where('manager_id', '=', $user_to_merge->id)->update(['manager_id' => $merge_into_user->id]); foreach ($user_to_merge->managedLocations as $managedLocation) { @@ -356,7 +440,6 @@ class BulkUsersController extends Controller } $user_to_merge->delete(); - //$user_to_merge->save(); event(new UserMerged($user_to_merge, $merge_into_user, $admin)); diff --git a/app/Http/Controllers/Users/UserFilesController.php b/app/Http/Controllers/Users/UserFilesController.php index ded44f35f6..45bd0c6329 100644 --- a/app/Http/Controllers/Users/UserFilesController.php +++ b/app/Http/Controllers/Users/UserFilesController.php @@ -7,9 +7,6 @@ use App\Http\Controllers\Controller; use App\Http\Requests\UploadFileRequest; use App\Models\Actionlog; use App\Models\User; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Input; -use Illuminate\Support\Facades\Response; use Symfony\Component\HttpFoundation\JsonResponse; use Illuminate\Support\Facades\Storage; @@ -25,43 +22,34 @@ class UserFilesController extends Controller *@author [A. Gianotto] [] * @since [v1.6] */ - public function store(UploadFileRequest $request, $userId = null) + public function store(UploadFileRequest $request, User $user) { - $user = User::find($userId); - $destinationPath = config('app.private_uploads').'/users'; + $this->authorize('update', $user); + $files = $request->file('file'); - if (isset($user->id)) { - $this->authorize('update', $user); - - $logActions = []; - $files = $request->file('file'); - - if (is_null($files)) { - return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles')); - } - foreach ($files as $file) { - $file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file); - - //Log the uploaded file to the log - $logAction = new Actionlog(); - $logAction->item_id = $user->id; - $logAction->item_type = User::class; - $logAction->user_id = Auth::id(); - $logAction->note = $request->input('notes'); - $logAction->target_id = null; - $logAction->created_at = date("Y-m-d H:i:s"); - $logAction->filename = $file_name; - $logAction->action_type = 'uploaded'; - - if (! $logAction->save()) { - return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500); - } - $logActions[] = $logAction; - } - // dd($logActions); - return redirect()->back()->with('success', trans('admin/users/message.upload.success')); + if (is_null($files)) { + return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles')); + } + foreach ($files as $file) { + $file_name = $request->handleFile('private_uploads/users/', 'user-'.$user->id, $file); + + //Log the uploaded file to the log + $logAction = new Actionlog(); + $logAction->item_id = $user->id; + $logAction->item_type = User::class; + $logAction->created_by = auth()->id(); + $logAction->note = $request->input('notes'); + $logAction->target_id = null; + $logAction->created_at = date("Y-m-d H:i:s"); + $logAction->filename = $file_name; + $logAction->action_type = 'uploaded'; + + if (! $logAction->save()) { + return JsonResponse::create(['error' => 'Failed validation: '.print_r($logAction->getErrors(), true)], 500); + } + + return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.upload.success')); } - return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles')); } @@ -90,7 +78,7 @@ class UserFilesController extends Controller if (Storage::exists($rel_path.'/'.$filename)) { Storage::delete($rel_path.'/'.$filename); - return redirect()->back()->with('success', trans('admin/users/message.deletefile.success')); + return redirect()->back()->withFragment('files')->with('success', trans('admin/users/message.deletefile.success')); } } @@ -113,38 +101,29 @@ class UserFilesController extends Controller * @return mixed * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($userId = null, $fileId = null) + public function show(User $user, $fileId = null) { + if (empty($fileId)) { return redirect()->route('users.show')->with('error', 'Invalid file request'); } - $user = User::find($userId); - - // the license is valid - if (isset($user->id)) { - $this->authorize('view', $user); - if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) { + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) { + $file = 'private_uploads/users/'.$log->filename; - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers); - } - - return Storage::download('private_uploads/users/'.$log->filename); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found')); } - - return redirect()->route('users.index')->with('error', trans('admin/users/message.log_record_not_found')); } - // Redirect to the user management page if the user doesn't exist - return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId])); + // The log record doesn't exist somehow + return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found')); + } } diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 0b16dd0a95..b18a7b6fff 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -117,7 +117,7 @@ class UsersController extends Controller $user->zip = $request->input('zip', null); $user->remote = $request->input('remote', 0); $user->website = $request->input('website', null); - $user->created_by = Auth::user()->id; + $user->created_by = auth()->id(); $user->start_date = $request->input('start_date', null); $user->end_date = $request->input('end_date', null); $user->autoassign_licenses = $request->input('autoassign_licenses', 0); @@ -125,7 +125,7 @@ class UsersController extends Controller // Strip out the superuser permission if the user isn't a superadmin $permissions_array = $request->input('permission'); - if (! Auth::user()->isSuperUser()) { + if (! auth()->user()->isSuperUser()) { unset($permissions_array['superuser']); } $user->permissions = json_encode($permissions_array); @@ -133,6 +133,8 @@ class UsersController extends Controller // we have to invoke the app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + if ($user->save()) { if ($request->filled('groups')) { $user->groups()->sync($request->input('groups')); @@ -152,7 +154,7 @@ class UsersController extends Controller $user->notify(new WelcomeNotification($data)); } - return redirect()->route('users.index')->with('success', trans('admin/users/message.success.create')); + return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users'))->with('success', trans('admin/users/message.success.create')); } return redirect()->back()->withInput()->withErrors($user->getErrors()); @@ -176,18 +178,22 @@ class UsersController extends Controller * @author [A. Gianotto] [] * @since [v1.0] * @param $permissions - * @return View + * @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse * @internal param int $id * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function edit($id) + public function edit(User $user) { $this->authorize('update', User::class); - $user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id); + $user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($user->id); if ($user) { + if ($user->trashed()) { + return redirect()->route('users.show', $user->id); + } + $permissions = config('permissions'); $groups = Group::pluck('name', 'id'); @@ -199,7 +205,6 @@ class UsersController extends Controller return view('users/edit', compact('user', 'groups', 'userGroups', 'permissions', 'userPermissions'))->with('item', $user); } - return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id'))); } /** @@ -212,116 +217,105 @@ class UsersController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function update(SaveUserRequest $request, $id = null) + public function update(SaveUserRequest $request, User $user) { $this->authorize('update', User::class); // This is a janky hack to prevent people from changing admin demo user data on the public demo. // The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder. // Thanks, jerks. You are why we can't have nice things. - snipe - - if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) { + if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) { return redirect()->route('users.index')->with('error', trans('general.permission_denied_superuser_demo')); } - // We need to reverse the UI specific logic for our // permissions here before we update the user. $permissions = $request->input('permissions', []); app('request')->request->set('permissions', $permissions); - $user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id); + $user->load(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed(); - // User is valid - continue... - if ($user) { - $this->authorize('update', $user); + $this->authorize('update', $user); - // Figure out of this user was an admin before this edit - $orig_permissions_array = $user->decodePermissions(); - $orig_superuser = '0'; - if (is_array($orig_permissions_array)) { - if (array_key_exists('superuser', $orig_permissions_array)) { - $orig_superuser = $orig_permissions_array['superuser']; - } + // Figure out of this user was an admin before this edit + $orig_permissions_array = $user->decodePermissions(); + $orig_superuser = '0'; + if (is_array($orig_permissions_array)) { + if (array_key_exists('superuser', $orig_permissions_array)) { + $orig_superuser = $orig_permissions_array['superuser']; } + } - // Only save groups if the user is a superuser - if (Auth::user()->isSuperUser()) { - $user->groups()->sync($request->input('groups')); - } + // Only save groups if the user is a superuser + if (auth()->user()->isSuperUser()) { + $user->groups()->sync($request->input('groups')); + } - // Update the user fields - $user->username = trim($request->input('username')); - $user->email = trim($request->input('email')); - $user->first_name = $request->input('first_name'); - $user->last_name = $request->input('last_name'); - $user->two_factor_optin = $request->input('two_factor_optin') ?: 0; - $user->locale = $request->input('locale'); - $user->employee_num = $request->input('employee_num'); - $user->activated = $request->input('activated', 0); - $user->jobtitle = $request->input('jobtitle', null); - $user->phone = $request->input('phone'); - $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); - $user->notes = $request->input('notes'); - $user->department_id = $request->input('department_id', null); - $user->address = $request->input('address', null); - $user->city = $request->input('city', null); - $user->state = $request->input('state', null); - $user->country = $request->input('country', null); - // if a user is editing themselves we should always keep activated true - $user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0); - $user->zip = $request->input('zip', null); - $user->remote = $request->input('remote', 0); - $user->vip = $request->input('vip', 0); - $user->website = $request->input('website', null); - $user->start_date = $request->input('start_date', null); - $user->end_date = $request->input('end_date', null); - $user->autoassign_licenses = $request->input('autoassign_licenses', 0); + // Update the user fields + $user->username = trim($request->input('username')); + $user->email = trim($request->input('email')); + $user->first_name = $request->input('first_name'); + $user->last_name = $request->input('last_name'); + $user->two_factor_optin = $request->input('two_factor_optin') ?: 0; + $user->locale = $request->input('locale'); + $user->employee_num = $request->input('employee_num'); + $user->activated = $request->input('activated', 0); + $user->jobtitle = $request->input('jobtitle', null); + $user->phone = $request->input('phone'); + $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); + $user->notes = $request->input('notes'); + $user->department_id = $request->input('department_id', null); + $user->address = $request->input('address', null); + $user->city = $request->input('city', null); + $user->state = $request->input('state', null); + $user->country = $request->input('country', null); + // if a user is editing themselves we should always keep activated true + $user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0); + $user->zip = $request->input('zip', null); + $user->remote = $request->input('remote', 0); + $user->vip = $request->input('vip', 0); + $user->website = $request->input('website', null); + $user->start_date = $request->input('start_date', null); + $user->end_date = $request->input('end_date', null); + $user->autoassign_licenses = $request->input('autoassign_licenses', 0); - // Update the location of any assets checked out to this user - Asset::where('assigned_type', User::class) - ->where('assigned_to', $user->id) - ->update(['location_id' => $request->input('location_id', null)]); - - // Do we want to update the user password? - if ($request->filled('password')) { - $user->password = bcrypt($request->input('password')); - } + // Update the location of any assets checked out to this user + Asset::where('assigned_type', User::class) + ->where('assigned_to', $user->id) + ->update(['location_id' => $request->input('location_id', null)]); + // Do we want to update the user password? + if ($request->filled('password')) { + $user->password = bcrypt($request->input('password')); + } // Update the location of any assets checked out to this user Asset::where('assigned_type', User::class) ->where('assigned_to', $user->id) ->update(['location_id' => $user->location_id]); - $permissions_array = $request->input('permission'); - - - // Strip out the superuser permission if the user isn't a superadmin - if (! Auth::user()->isSuperUser()) { - unset($permissions_array['superuser']); - $permissions_array['superuser'] = $orig_superuser; - } - - $user->permissions = json_encode($permissions_array); - - // Handle uploaded avatar - app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); - - if ($user->save()) { - // Redirect to the user page - return redirect()->route('users.index') - ->with('success', trans('admin/users/message.success.update')); - } - - return redirect()->back()->withInput()->withErrors($user->getErrors()); - + $permissions_array = $request->input('permission'); + // Strip out the superuser permission if the user isn't a superadmin + if (! auth()->user()->isSuperUser()) { + unset($permissions_array['superuser']); + $permissions_array['superuser'] = $orig_superuser; } - return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id'))); + $user->permissions = json_encode($permissions_array); + + // Handle uploaded avatar + app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); + session()->put(['redirect_option' => $request->get('redirect_option')]); + + if ($user->save()) { + // Redirect to the user page + return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users')) + ->with('success', trans('admin/users/message.success.update')); + } + return redirect()->back()->withInput()->withErrors($user->getErrors()); } /** @@ -333,7 +327,7 @@ class UsersController extends Controller * @return \Illuminate\Http\RedirectResponse * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function destroy(DeleteUserRequest $request, $id = null) + public function destroy(DeleteUserRequest $request, $id) { $this->authorize('delete', User::class); @@ -342,13 +336,6 @@ class UsersController extends Controller $this->authorize('delete', $user); if ($user->delete()) { - if (Storage::disk('public')->exists('avatars/' . $user->avatar)) { - try { - Storage::disk('public')->delete('avatars/' . $user->avatar); - } catch (\Exception $e) { - Log::debug($e); - } - } return redirect()->route('users.index')->with('success', trans('admin/users/message.success.delete')); } } @@ -379,7 +366,7 @@ class UsersController extends Controller $logaction->item_type = User::class; $logaction->item_id = $user->id; $logaction->created_at = date('Y-m-d H:i:s'); - $logaction->user_id = Auth::user()->id; + $logaction->created_by = auth()->id(); $logaction->logaction('restore'); // Redirect them to the deleted page if there are more, otherwise the section index @@ -407,23 +394,27 @@ class UsersController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function show($userId = null) + public function show(User $user) { // Make sure the user can view users at all $this->authorize('view', User::class); - $user = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($userId); + $user = User::with([ + 'consumables', + 'accessories', + 'licenses', + 'userloc', + ]) + ->withTrashed() + ->find($user->id); // Make sure they can view this particular user $this->authorize('view', $user); - if ($user) { - $userlog = $user->userlog->load('item'); - return view('users/view', compact('user', 'userlog'))->with('settings', Setting::getSettings()); - } - - return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', ['id' => $userId])); - + return view('users/view', [ + 'user' => $user, + 'settings' => Setting::getSettings(), + ]); } @@ -437,7 +428,7 @@ class UsersController extends Controller * @return \Illuminate\Contracts\View\View * @throws \Illuminate\Auth\Access\AuthorizationException */ - public function getClone(Request $request, $id = null) + public function getClone(Request $request, User $user) { $this->authorize('create', User::class); @@ -447,7 +438,7 @@ class UsersController extends Controller app('request')->request->set('permissions', $permissions); - $user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($id); + $user_to_clone = User::with('assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc')->withTrashed()->find($user->id); // Make sure they can view this particular user $this->authorize('view', $user_to_clone); @@ -477,10 +468,10 @@ class UsersController extends Controller ->with('user', $user) ->with('groups', Group::pluck('name', 'id')) ->with('userGroups', $userGroups) - ->with('clone_user', $user_to_clone); + ->with('clone_user', $user_to_clone) + ->with('item', $user); } - return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id'))); } @@ -598,29 +589,43 @@ class UsersController extends Controller /** * Print inventory * - * @author Aladin Alaily * @since [v1.8] - * @return \Illuminate\Http\RedirectResponse + * @author Aladin Alaily */ public function printInventory($id) { $this->authorize('view', User::class); - $user = User::where('id', $id)->withTrashed()->first(); - - // Make sure they can view this particular user - $this->authorize('view', $user); + $user = User::where('id', $id) + ->with([ + 'assets.log' => fn($query) => $query->withTrashed()->where('target_type', User::class)->where('target_id', $id)->where('action_type', 'accepted'), + 'assets.assignedAssets.log' => fn($query) => $query->withTrashed()->where('target_type', User::class)->where('target_id', $id)->where('action_type', 'accepted'), + 'assets.assignedAssets.defaultLoc', + 'assets.assignedAssets.location', + 'assets.assignedAssets.model.category', + 'assets.defaultLoc', + 'assets.location', + 'assets.model.category', + 'accessories.log' => fn($query) => $query->withTrashed()->where('target_type', User::class)->where('target_id', $id)->where('action_type', 'accepted'), + 'accessories.category', + 'accessories.manufacturer', + 'consumables.log' => fn($query) => $query->withTrashed()->where('target_type', User::class)->where('target_id', $id)->where('action_type', 'accepted'), + 'consumables.category', + 'consumables.manufacturer', + 'licenses.category', + ]) + ->withTrashed() + ->first(); - $assets = Asset::where('assigned_to', $id)->where('assigned_type', User::class)->with('model', 'model.category')->get(); - $accessories = $user->accessories()->get(); - $consumables = $user->consumables()->get(); + if ($user) { + $this->authorize('view', $user); - return view('users/print')->with('assets', $assets) - ->with('licenses', $user->licenses()->get()) - ->with('accessories', $accessories) - ->with('consumables', $consumables) - ->with('show_user', $user) - ->with('settings', Setting::getSettings()); + return view('users.print') + ->with('users', [$user]) + ->with('settings', Setting::getSettings()); + } + + return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id'))); } /** diff --git a/app/Http/Controllers/ViewAssetsController.php b/app/Http/Controllers/ViewAssetsController.php index cbff99937d..bbff6ba4f7 100755 --- a/app/Http/Controllers/ViewAssetsController.php +++ b/app/Http/Controllers/ViewAssetsController.php @@ -2,17 +2,21 @@ namespace App\Http\Controllers; +use App\Actions\CheckoutRequests\CancelCheckoutRequestAction; +use App\Actions\CheckoutRequests\CreateCheckoutRequestAction; +use App\Exceptions\AssetNotRequestable; use App\Models\Actionlog; use App\Models\Asset; use App\Models\AssetModel; -use App\Models\Company; use App\Models\Setting; use App\Models\User; -use App\Models\CustomField; use App\Notifications\RequestAssetCancelation; use App\Notifications\RequestAssetNotification; +use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; +use Illuminate\Http\RedirectResponse; +use \Illuminate\Contracts\View\View; +use Exception; /** * This controller handles all actions related to the ability for users @@ -25,9 +29,8 @@ class ViewAssetsController extends Controller /** * Redirect to the profile page. * - * @return \Illuminate\Http\RedirectResponse */ - public function getIndex() + public function getIndex() : View | RedirectResponse { $user = User::with( 'assets', @@ -36,7 +39,7 @@ class ViewAssetsController extends Controller 'consumables', 'accessories', 'licenses', - )->find(Auth::user()->id); + )->find(auth()->id()); $field_array = array(); @@ -72,9 +75,8 @@ class ViewAssetsController extends Controller /** * Returns view of requestable items for a user. - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function getRequestableIndex() + public function getRequestableIndex() : View { $assets = Asset::with('model', 'defaultLoc', 'location', 'assignedTo', 'requests')->Hardware()->RequestableAssets(); $models = AssetModel::with('category', 'requests', 'assets')->RequestableModels()->get(); @@ -82,7 +84,7 @@ class ViewAssetsController extends Controller return view('account/requestable-assets', compact('assets', 'models')); } - public function getRequestItem(Request $request, $itemType, $itemId = null, $cancel_by_admin = false, $requestingUser = null) + public function getRequestItem(Request $request, $itemType, $itemId = null, $cancel_by_admin = false, $requestingUser = null): RedirectResponse { $item = null; $fullItemType = 'App\\Models\\'.studly_case($itemType); @@ -92,7 +94,7 @@ class ViewAssetsController extends Controller } $item = call_user_func([$fullItemType, 'find'], $itemId); - $user = Auth::user(); + $user = auth()->user(); $logaction = new Actionlog(); $logaction->item_id = $data['asset_id'] = $item->id; @@ -102,14 +104,15 @@ class ViewAssetsController extends Controller if ($user->location_id) { $logaction->location_id = $user->location_id; } - $logaction->target_id = $data['user_id'] = Auth::user()->id; + + $logaction->target_id = $data['user_id'] = auth()->id(); $logaction->target_type = User::class; $data['item_quantity'] = $request->has('request-quantity') ? e($request->input('request-quantity')) : 1; $data['requested_by'] = $user->present()->fullName(); $data['item'] = $item; $data['item_type'] = $itemType; - $data['target'] = Auth::user(); + $data['target'] = auth()->user(); if ($fullItemType == Asset::class) { $data['item_url'] = route('hardware.show', $item->id); @@ -143,59 +146,35 @@ class ViewAssetsController extends Controller /** * Process a specific requested asset * @param null $assetId - * @return \Illuminate\Http\RedirectResponse */ - public function getRequestAsset($assetId = null) + public function store(Asset $asset): RedirectResponse { - $user = Auth::user(); - - // Check if the asset exists and is requestable - if (is_null($asset = Asset::RequestableAssets()->find($assetId))) { - return redirect()->route('requestable-assets') - ->with('error', trans('admin/hardware/message.does_not_exist_or_not_requestable')); + try { + CreateCheckoutRequestAction::run($asset, auth()->user()); + return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success')); + } catch (AssetNotRequestable $e) { + return redirect()->back()->with('error', 'Asset is not requestable'); + } catch (AuthorizationException $e) { + return redirect()->back()->with('error', trans('admin/hardware/message.requests.error')); + } catch (Exception $e) { + report($e); + return redirect()->back()->with('error', trans('general.something_went_wrong')); } - if (! Company::isCurrentUserHasAccess($asset)) { - return redirect()->route('requestable-assets') - ->with('error', trans('general.insufficient_permissions')); - } - - $data['item'] = $asset; - $data['target'] = Auth::user(); - $data['item_quantity'] = 1; - $settings = Setting::getSettings(); - - $logaction = new Actionlog(); - $logaction->item_id = $data['asset_id'] = $asset->id; - $logaction->item_type = $data['item_type'] = Asset::class; - $logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s'); - - if ($user->location_id) { - $logaction->location_id = $user->location_id; - } - $logaction->target_id = $data['user_id'] = Auth::user()->id; - $logaction->target_type = User::class; - - // If it's already requested, cancel the request. - if ($asset->isRequestedBy(Auth::user())) { - $asset->cancelRequest(); - $asset->decrement('requests_counter', 1); - - $logaction->logaction('request canceled'); - $settings->notify(new RequestAssetCancelation($data)); - - return redirect()->route('requestable-assets') - ->with('success')->with('success', trans('admin/hardware/message.requests.canceled')); - } - - $logaction->logaction('requested'); - $asset->request(); - $asset->increment('requests_counter', 1); - $settings->notify(new RequestAssetNotification($data)); - - return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.success')); } - public function getRequestedAssets() + public function destroy(Asset $asset): RedirectResponse + { + try { + CancelCheckoutRequestAction::run($asset, auth()->user()); + return redirect()->route('requestable-assets')->with('success')->with('success', trans('admin/hardware/message.requests.canceled')); + } catch (Exception $e) { + report($e); + return redirect()->back()->with('error', trans('general.something_went_wrong')); + } + } + + + public function getRequestedAssets() : View { return view('account/requested'); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index e3419f247c..729fe75175 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -14,6 +14,7 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ + \App\Http\Middleware\TrustProxies::class, \App\Http\Middleware\NoSessionStore::class, \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Session\Middleware\StartSession::class, @@ -21,6 +22,7 @@ class Kernel extends HttpKernel \App\Http\Middleware\CheckForSetup::class, \App\Http\Middleware\CheckForDebug::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + \App\Http\Middleware\TrimStrings::class, \App\Http\Middleware\SecurityHeaders::class, \App\Http\Middleware\PreventBackHistory::class, \Illuminate\Http\Middleware\HandleCors::class, @@ -43,10 +45,17 @@ class Kernel extends HttpKernel \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class, \App\Http\Middleware\AssetCountForSidebar::class, \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ 'auth:api', + \App\Http\Middleware\CheckLocale::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'health' => [ + ], ]; @@ -64,5 +73,7 @@ class Kernel extends HttpKernel 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'api-throttle' => \App\Http\Middleware\SetAPIResponseHeaders::class, + 'health' => null, ]; } diff --git a/app/Http/Middleware/CheckForDebug.php b/app/Http/Middleware/CheckForDebug.php index 85311cf539..3bf919f1f2 100644 --- a/app/Http/Middleware/CheckForDebug.php +++ b/app/Http/Middleware/CheckForDebug.php @@ -18,7 +18,7 @@ class CheckForDebug { view()->share('debug_in_production', false); - if (((Auth::check() && (Auth::user()->isSuperUser()))) && (app()->environment() == 'production') && (config('app.warn_debug') === true) && (config('app.debug') === true)) { + if (((Auth::check() && (auth()->user()->isSuperUser()))) && (app()->environment() == 'production') && (config('app.warn_debug') === true) && (config('app.debug') === true)) { view()->share('debug_in_production', true); } diff --git a/app/Http/Middleware/CheckForSetup.php b/app/Http/Middleware/CheckForSetup.php index 4e399ffcff..6cb593b5ff 100644 --- a/app/Http/Middleware/CheckForSetup.php +++ b/app/Http/Middleware/CheckForSetup.php @@ -7,14 +7,19 @@ use Closure; class CheckForSetup { + + protected $except = [ + '_debugbar*', + 'health' + ]; + public function handle($request, Closure $next, $guard = null) { /** - * This is dumb - * @todo Check on removing this, not sure if it's still needed + * Skip this middleware for the debugbar and health check */ - if ($request->is('_debugbar*')) { + if ($request->is($this->except)) { return $next($request); } @@ -25,7 +30,7 @@ class CheckForSetup return $next($request); } } else { - if (! ($request->is('setup*')) && ! ($request->is('.env')) && ! ($request->is('health'))) { + if (! ($request->is('setup*')) && ! ($request->is('.env'))) { return redirect(config('app.url').'/setup'); } diff --git a/app/Http/Middleware/CheckForTwoFactor.php b/app/Http/Middleware/CheckForTwoFactor.php index 7846d00ade..b94423d205 100644 --- a/app/Http/Middleware/CheckForTwoFactor.php +++ b/app/Http/Middleware/CheckForTwoFactor.php @@ -38,17 +38,17 @@ class CheckForTwoFactor if ($settings = Setting::getSettings()) { if (Auth::check() && ($settings->two_factor_enabled != '')) { // This user is already 2fa-authed - if ($request->session()->get('2fa_authed')==Auth::user()->id) { + if ($request->session()->get('2fa_authed')==auth()->id()) { return $next($request); } // Two-factor is optional and the user has NOT opted in, let them through - if (($settings->two_factor_enabled == '1') && (Auth::user()->two_factor_optin != '1')) { + if (($settings->two_factor_enabled == '1') && (auth()->user()->two_factor_optin != '1')) { return $next($request); } // Otherwise make sure they're enrolled and show them the 2FA code screen - if ((Auth::user()->two_factor_secret != '') && (Auth::user()->two_factor_enrolled == '1')) { + if ((auth()->user()->two_factor_secret != '') && (auth()->user()->two_factor_enrolled == '1')) { return redirect()->route('two-factor')->with('info', trans('auth/message.two_factor.enter_two_factor_code')); } diff --git a/app/Http/Middleware/CheckLocale.php b/app/Http/Middleware/CheckLocale.php index 412a423f22..68a9c48c34 100644 --- a/app/Http/Middleware/CheckLocale.php +++ b/app/Http/Middleware/CheckLocale.php @@ -45,7 +45,7 @@ class CheckLocale } - \App::setLocale(Helper::mapLegacyLocale($language)); + app()->setLocale(Helper::mapLegacyLocale($language)); return $next($request); } } diff --git a/app/Http/Middleware/CheckPermissions.php b/app/Http/Middleware/CheckPermissions.php index a51484f283..3eda3e3144 100644 --- a/app/Http/Middleware/CheckPermissions.php +++ b/app/Http/Middleware/CheckPermissions.php @@ -26,6 +26,6 @@ class CheckPermissions return response()->view('layouts/basic', [ 'content' => view('errors/403'), - ]); + ], 403); } } diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 5f4c62723a..0d5b37de77 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -20,5 +20,5 @@ class EncryptCookies extends BaseEncrypter * * @var bool */ - protected static $serialize = true; + protected static $serialize = false; } diff --git a/app/Http/Middleware/SetAPIResponseHeaders.php b/app/Http/Middleware/SetAPIResponseHeaders.php new file mode 100644 index 0000000000..ac277e785c --- /dev/null +++ b/app/Http/Middleware/SetAPIResponseHeaders.php @@ -0,0 +1,82 @@ +headers->get('X-RateLimit-Remaining')) && + (int) $response->headers->get('X-RateLimit-Remaining') <= (int) $remainingAttempts) { + $headers = []; + $headers['Retry-After'] = $retryAfter; // this is the only line we changed + $headers['X-RateLimit-Reset'] = $retryAfter; // this is the only line we changed + $headers['X-RateLimit-Reset-Timestamp'] = $this->availableAt($retryAfter); // this is the only line we changed + return $headers; + } + + $headers = [ + 'X-RateLimit-Limit' => $maxAttempts, + 'X-RateLimit-Remaining' => $remainingAttempts, + ]; + + if (! is_null($retryAfter)) { + $headers['Retry-After'] = $retryAfter; + $headers['X-RateLimit-Reset'] = $retryAfter; // this is the only line we changed + $headers['X-RateLimit-Reset-Timestamp'] = $this->availableAt($retryAfter); // this is the only line we changed + } + + return $headers; + } + + + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + protected function handleRequest($request, Closure $next, array $limits) + { + foreach ($limits as $limit) { + if ($this->limiter->tooManyAttempts($limit->key, $limit->maxAttempts)) { + throw $this->buildException($request, $limit->key, $limit->maxAttempts, $limit->responseCallback); + } + + $this->limiter->hit($limit->key, $limit->decaySeconds); + } + + $response = $next($request); + + foreach ($limits as $limit) { + $response = $this->addHeaders( + $response, + $limit->maxAttempts, + $this->calculateRemainingAttempts($limit->key, $limit->maxAttempts), + $this->getTimeUntilNextRetry($limit->key) // this is the only line we changed + ); + } + + return $response; + } + +} \ No newline at end of file diff --git a/app/Http/Requests/AccessoryCheckoutRequest.php b/app/Http/Requests/AccessoryCheckoutRequest.php new file mode 100644 index 0000000000..deda07f8ff --- /dev/null +++ b/app/Http/Requests/AccessoryCheckoutRequest.php @@ -0,0 +1,76 @@ +accessory) { + + $this->diff = ($this->accessory->numRemaining() - $this->checkout_qty); + $this->merge([ + 'checkout_qty' => $this->checkout_qty ?? 1, + 'number_remaining_after_checkout' => (int) ($this->accessory->numRemaining() - $this->checkout_qty), + 'number_currently_remaining' => (int) $this->accessory->numRemaining(), + 'checkout_difference' => (int) $this->diff, + ]); + + \Log::debug('---------------------------------------------'); + } + + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + + return array_merge( + [ + 'assigned_user' => 'required_without_all:assigned_asset,assigned_location', + 'assigned_asset' => 'required_without_all:assigned_user,assigned_location', + 'assigned_location' => 'required_without_all:assigned_user,assigned_asset', + + 'number_remaining_after_checkout' => [ + 'min:0', + 'required', + 'integer', + ], + + 'checkout_qty' => [ + 'integer', + 'lte:number_currently_remaining', + 'min:1', + ], + ], + ); + } + + public function messages(): array + { + $messages = [ + 'checkout_qty.lte' => trans_choice('admin/accessories/message.checkout.checkout_qty.lte', $this->number_currently_remaining, [ + 'number_currently_remaining' => $this->number_currently_remaining, + 'checkout_qty' => $this->checkout_qty, + ]), + ]; + return $messages; + } +} diff --git a/app/Http/Requests/AssetCheckinRequest.php b/app/Http/Requests/AssetCheckinRequest.php index 116b8b39f2..8980cea095 100644 --- a/app/Http/Requests/AssetCheckinRequest.php +++ b/app/Http/Requests/AssetCheckinRequest.php @@ -21,9 +21,14 @@ class AssetCheckinRequest extends Request */ public function rules() { - return [ + $settings = \App\Models\Setting::getSettings(); - ]; + $rules = []; + + if($settings->require_checkinout_notes) { + $rules['note'] = 'string|required'; + } + return $rules; } public function response(array $errors) diff --git a/app/Http/Requests/AssetCheckoutRequest.php b/app/Http/Requests/AssetCheckoutRequest.php index f48a7d5e53..004813c118 100644 --- a/app/Http/Requests/AssetCheckoutRequest.php +++ b/app/Http/Requests/AssetCheckoutRequest.php @@ -21,10 +21,12 @@ class AssetCheckoutRequest extends Request */ public function rules() { + $settings = \App\Models\Setting::getSettings(); + $rules = [ - 'assigned_user' => 'required_without_all:assigned_asset,assigned_location', - 'assigned_asset' => 'required_without_all:assigned_user,assigned_location', - 'assigned_location' => 'required_without_all:assigned_user,assigned_asset', + 'assigned_user' => 'numeric|nullable|required_without_all:assigned_asset,assigned_location', + 'assigned_asset' => 'numeric|nullable|required_without_all:assigned_user,assigned_location', + 'assigned_location' => 'numeric|nullable|required_without_all:assigned_user,assigned_asset', 'status_id' => 'exists:status_labels,id,deployable,1', 'checkout_to_type' => 'required|in:asset,location,user', 'checkout_at' => [ @@ -35,7 +37,11 @@ class AssetCheckoutRequest extends Request 'nullable', 'date' ], - ]; + ]; + + if($settings->require_checkinout_notes) { + $rules['note'] = 'required|string'; + } return $rules; } diff --git a/app/Http/Requests/DeleteUserRequest.php b/app/Http/Requests/DeleteUserRequest.php index 282919d7ae..6d387917c6 100644 --- a/app/Http/Requests/DeleteUserRequest.php +++ b/app/Http/Requests/DeleteUserRequest.php @@ -24,12 +24,12 @@ class DeleteUserRequest extends FormRequest public function prepareForValidation(): void { - $user_to_delete = User::withTrashed()->find(request()->route('user')); + $user_to_delete = User::withTrashed()->with('managesUsers')->find(request()->route('user')); if ($user_to_delete) { $this->merge([ 'user' => request()->route('user'), - 'admin_id' => Auth::user()->id, + 'admin_id' => auth()->id(), 'managed_users' => $user_to_delete->managesUsers()->count(), 'managed_locations' => $user_to_delete->managedLocations()->count(), 'assigned_assets' => $user_to_delete->assets()->count(), @@ -48,7 +48,7 @@ class DeleteUserRequest extends FormRequest public function rules(): array { return [ - 'user' => Rule::notIn([auth()->user()->id]), + 'user' => Rule::notIn([auth()->id()]), 'managed_users' => Rule::in([0]), 'managed_locations' => Rule::in([0]), 'assigned_assets' => Rule::in([0]), @@ -61,7 +61,8 @@ class DeleteUserRequest extends FormRequest public function messages(): array { - $user_to_delete = User::withTrashed()->find(request()->route('user')); + $user_to_delete = User::withTrashed()->with('managesUsers')->find(request()->route('user')); + $messages = []; if ($user_to_delete) { diff --git a/app/Http/Requests/ImageUploadRequest.php b/app/Http/Requests/ImageUploadRequest.php index c75454d0f3..abb0cee5f7 100644 --- a/app/Http/Requests/ImageUploadRequest.php +++ b/app/Http/Requests/ImageUploadRequest.php @@ -10,6 +10,7 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Intervention\Image\Exception\NotReadableException; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Validator; class ImageUploadRequest extends Request { @@ -96,10 +97,7 @@ class ImageUploadRequest extends Request $ext = $image->guessExtension(); $file_name = $type.'-'.$form_fieldname.'-'.$item->id.'-'.str_random(10).'.'.$ext; - - Log::info('File name will be: '.$file_name); - Log::debug('File extension is: '.$ext); - + if (($image->getMimeType() == 'image/vnd.microsoft.icon') || ($image->getMimeType() == 'image/x-icon') || ($image->getMimeType() == 'image/avif') || ($image->getMimeType() == 'image/webp')) { // If the file is an icon, webp or avif, we need to just move it since gd doesn't support resizing // icons or avif, and webp support and needs to be compiled into gd for resizing to be available @@ -126,7 +124,7 @@ class ImageUploadRequest extends Request } catch(NotReadableException $e) { Log::debug($e); - $validator = \Validator::make([], []); + $validator = Validator::make([], []); $validator->errors()->add($form_fieldname, trans('general.unaccepted_image_type', ['mimetype' => $image->getClientMimeType()])); throw new \Illuminate\Validation\ValidationException($validator); @@ -138,28 +136,28 @@ class ImageUploadRequest extends Request } // Remove Current image if exists - if (($item->{$form_fieldname}!='') && (Storage::disk('public')->exists($path.'/'.$item->{$db_fieldname}))) { - try { - Storage::disk('public')->delete($path.'/'.$item->{$form_fieldname}); - } catch (\Exception $e) { - Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?'); - } - } - + $item = $this->deleteExistingImage($item, $path, $db_fieldname); $item->{$db_fieldname} = $file_name; } // If the user isn't uploading anything new but wants to delete their old image, do so } elseif ($this->input('image_delete') == '1') { - Log::debug('Deleting image'); + $item = $this->deleteExistingImage($item, $path, $db_fieldname); + } + + return $item; + } + + public function deleteExistingImage($item, $path = null, $db_fieldname = 'image') { + + if ($item->{$db_fieldname}!='') { try { Storage::disk('public')->delete($path.'/'.$item->{$db_fieldname}); - $item->{$db_fieldname} = null; + $item->{$db_fieldname} = null; } catch (\Exception $e) { Log::debug($e); } - } return $item; diff --git a/app/Http/Requests/ItemImportRequest.php b/app/Http/Requests/ItemImportRequest.php index 2ea0839c93..59afdbe09f 100644 --- a/app/Http/Requests/ItemImportRequest.php +++ b/app/Http/Requests/ItemImportRequest.php @@ -38,10 +38,11 @@ class ItemImportRequest extends FormRequest $filename = config('app.private_uploads').'/imports/'.$import->file_path; $import->import_type = $this->input('import-type'); - $class = title_case($import->import_type); + $class = ucfirst($import->import_type); $classString = "App\\Importer\\{$class}Importer"; $importer = new $classString($filename); $import->field_map = request('column-mappings'); + $import->created_by = auth()->id(); $import->save(); $fieldMappings = []; @@ -60,7 +61,7 @@ class ItemImportRequest extends FormRequest $fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER); } $importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback']) - ->setUserId(Auth::id()) + ->setCreatedBy(auth()->id()) ->setUpdating($this->get('import-update')) ->setShouldNotify($this->get('send-welcome')) ->setUsernameFormat('firstname.lastname') diff --git a/app/Http/Requests/SaveUserRequest.php b/app/Http/Requests/SaveUserRequest.php index b38193c15a..4051e98043 100644 --- a/app/Http/Requests/SaveUserRequest.php +++ b/app/Http/Requests/SaveUserRequest.php @@ -6,6 +6,7 @@ use App\Models\Setting; use Illuminate\Contracts\Validation\Validator; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Http\Exceptions\HttpResponseException; +use App\Rules\UserCannotSwitchCompaniesIfItemsAssigned; class SaveUserRequest extends FormRequest { @@ -32,8 +33,9 @@ class SaveUserRequest extends FormRequest public function rules() { $rules = [ - 'department_id' => 'nullable|exists:departments,id', + 'department_id' => 'nullable|integer|exists:departments,id', 'manager_id' => 'nullable|exists:users,id', + 'company_id' => ['nullable', 'integer', 'exists:companies,id'] ]; switch ($this->method()) { @@ -52,11 +54,13 @@ class SaveUserRequest extends FormRequest $rules['first_name'] = 'required|string|min:1'; $rules['username'] = 'required_unless:ldap_import,1|string|min:1'; $rules['password'] = Setting::passwordComplexityRulesSaving('update').'|confirmed'; + $rules['company_id'] = [new UserCannotSwitchCompaniesIfItemsAssigned()]; break; // Save only what's passed case 'PATCH': $rules['password'] = Setting::passwordComplexityRulesSaving('update'); + $rules['company_id'] = [new UserCannotSwitchCompaniesIfItemsAssigned()]; break; default: diff --git a/app/Http/Requests/SettingsSamlRequest.php b/app/Http/Requests/SettingsSamlRequest.php index 8f02d64f03..2ab876141a 100644 --- a/app/Http/Requests/SettingsSamlRequest.php +++ b/app/Http/Requests/SettingsSamlRequest.php @@ -62,7 +62,7 @@ class SettingsSamlRequest extends FormRequest $custom_privateKey = ''; $custom_x509certNew = ''; if (! empty($this->input('saml_custom_settings'))) { - $req_custom_settings = preg_split('/\r\n|\r|\n/', $this->input('saml_custom_settings')); + $req_custom_settings = preg_split('/\r\n|\r|\n/', $this->input('saml_custom_settings', '')); $custom_settings = []; foreach ($req_custom_settings as $custom_setting) { diff --git a/app/Http/Requests/StoreAccessoryRequest.php b/app/Http/Requests/StoreAccessoryRequest.php new file mode 100644 index 0000000000..c41bae7b40 --- /dev/null +++ b/app/Http/Requests/StoreAccessoryRequest.php @@ -0,0 +1,56 @@ +category_id) { + if ($category = Category::find($this->category_id)) { + $this->merge([ + 'category_type' => $category->category_type ?? null, + ]); + } + } + + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return array_merge( + ['category_type' => 'in:accessory'], + parent::rules(), + ); + } + + public function messages(): array + { + $messages = ['category_type.in' => trans('admin/accessories/message.invalid_category_type')]; + return $messages; + } + + public function response(array $errors) + { + return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag); + } +} diff --git a/app/Http/Requests/StoreAssetModelRequest.php b/app/Http/Requests/StoreAssetModelRequest.php new file mode 100644 index 0000000000..0da47b0c72 --- /dev/null +++ b/app/Http/Requests/StoreAssetModelRequest.php @@ -0,0 +1,57 @@ +category_id) { + if ($category = Category::find($this->category_id)) { + $this->merge([ + 'category_type' => $category->category_type ?? null, + ]); + } + } + + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return array_merge( + ['category_type' => 'in:asset'], + parent::rules(), + ); + } + + public function messages(): array + { + $messages = ['category_type.in' => trans('admin/models/message.invalid_category_type')]; + return $messages; + } + + public function response(array $errors) + { + return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag); + } +} diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php index 007dd26843..66179ac739 100644 --- a/app/Http/Requests/StoreAssetRequest.php +++ b/app/Http/Requests/StoreAssetRequest.php @@ -2,15 +2,18 @@ namespace App\Http\Requests; +use App\Http\Requests\Traits\MayContainCustomFields; use App\Models\Asset; use App\Models\Company; use App\Models\Setting; use Carbon\Carbon; use Carbon\Exceptions\InvalidFormatException; use Illuminate\Support\Facades\Gate; +use App\Rules\AssetCannotBeCheckedOutToNondeployableStatus; class StoreAssetRequest extends ImageUploadRequest { + use MayContainCustomFields; /** * Determine if the user is authorized to make this request. * @@ -26,7 +29,8 @@ class StoreAssetRequest extends ImageUploadRequest // Guard against users passing in an array for company_id instead of an integer. // If the company_id is not an integer then we simply use what was // provided to be caught by model level validation later. - $idForCurrentUser = is_int($this->company_id) + // The use of is_numeric accounts for 1 and '1'. + $idForCurrentUser = is_numeric($this->company_id) ? Company::getIdForCurrentUser($this->company_id) : $this->company_id; @@ -35,7 +39,6 @@ class StoreAssetRequest extends ImageUploadRequest $this->merge([ 'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(), 'company_id' => $idForCurrentUser, - 'assigned_to' => $assigned_to ?? null, ]); } @@ -59,6 +62,7 @@ class StoreAssetRequest extends ImageUploadRequest return array_merge( $modelRules, + ['status_id' => [new AssetCannotBeCheckedOutToNondeployableStatus()]], parent::rules(), ); } diff --git a/app/Http/Requests/StoreConsumableRequest.php b/app/Http/Requests/StoreConsumableRequest.php new file mode 100644 index 0000000000..9062b07cc5 --- /dev/null +++ b/app/Http/Requests/StoreConsumableRequest.php @@ -0,0 +1,56 @@ +category_id) { + if ($category = Category::find($this->category_id)) { + $this->merge([ + 'category_type' => $category->category_type ?? null, + ]); + } + } + + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return array_merge( + ['category_type' => 'in:consumable'], + parent::rules(), + ); + } + + public function messages(): array + { + $messages = ['category_type.in' => trans('admin/consumables/message.invalid_category_type')]; + return $messages; + } + + public function response(array $errors) + { + return $this->redirector->back()->withInput()->withErrors($errors, $this->errorBag); + } +} diff --git a/app/Http/Requests/StoreLabelSettings.php b/app/Http/Requests/StoreLabelSettings.php new file mode 100644 index 0000000000..2b6fd83c6f --- /dev/null +++ b/app/Http/Requests/StoreLabelSettings.php @@ -0,0 +1,58 @@ +|string> + */ + public function rules(): array + { + $names = Label::find()?->map(function ($label) { + return $label->getName(); + })->values()->toArray(); + + if (empty($this->input('label2_template'))) { + $this->merge([ + 'label2_template' => 'DefaultLabel', + ]); + } + + return [ + 'labels_per_page' => 'numeric', + 'labels_width' => 'numeric|min:0.1', + 'labels_height' => 'numeric|min:0.1', + 'labels_pmargin_left' => 'numeric|nullable', + 'labels_pmargin_right' => 'numeric|nullable', + 'labels_pmargin_top' => 'numeric|nullable', + 'labels_pmargin_bottom' => 'numeric|nullable', + 'labels_display_bgutter' => 'numeric|nullable', + 'labels_display_sgutter' => 'numeric|nullable', + 'labels_fontsize' => 'numeric|min:5', + 'labels_pagewidth' => 'numeric|nullable', + 'labels_pageheight' => 'numeric|nullable', + 'qr_text' => 'max:31|nullable', + 'label2_template' => [ + 'required', + Rule::in($names), + ], + ]; + } +} diff --git a/app/Http/Requests/StoreLdapSettings.php b/app/Http/Requests/StoreLdapSettings.php new file mode 100644 index 0000000000..ce0fa41bf5 --- /dev/null +++ b/app/Http/Requests/StoreLdapSettings.php @@ -0,0 +1,36 @@ +|string> + */ + public function rules(): array + { + return [ + 'ldap_username_field' => 'not_in:sAMAccountName|required_if:ldap_enabled,1', + 'ldap_auth_filter_query' => 'not_in:uid=samaccountname|required_if:ldap_enabled,1', + 'ldap_filter' => 'nullable|regex:"^[^(]"|required_if:ldap_enabled,1', + 'ldap_server' => 'nullable|required_if:ldap_enabled,1|starts_with:ldap://,ldaps://', + 'ldap_basedn' => 'nullable|required_if:ldap_enabled,1', + 'ldap_fname_field' => 'nullable|required_if:ldap_enabled,1', + 'custom_forgot_pass_url' => 'nullable|url', + ]; + } + +} diff --git a/app/Http/Requests/StoreLocalizationSettings.php b/app/Http/Requests/StoreLocalizationSettings.php new file mode 100644 index 0000000000..4cea8826e8 --- /dev/null +++ b/app/Http/Requests/StoreLocalizationSettings.php @@ -0,0 +1,30 @@ +|string> + */ + public function rules(): array + { + return [ + 'default_currency' => 'required', + 'locale' => 'required', + ]; + } +} diff --git a/app/Http/Requests/StoreNotificationSettings.php b/app/Http/Requests/StoreNotificationSettings.php new file mode 100644 index 0000000000..f58d014c76 --- /dev/null +++ b/app/Http/Requests/StoreNotificationSettings.php @@ -0,0 +1,41 @@ +|string> + */ + public function rules(): array + { + return [ + 'alert_email' => 'email_array|nullable', + 'admin_cc_email' => 'email_array|nullable', + 'admin_cc_always' => [ + Rule::in('0', '1'), + ], + 'alert_threshold' => 'numeric|nullable', + 'alert_interval' => 'numeric|nullable|gt:0', + 'audit_warning_days' => 'numeric|nullable', + 'due_checkin_days' => 'numeric|nullable|gt:0', + 'audit_interval' => 'numeric|nullable|gt:0', + ]; + } + +} diff --git a/app/Http/Requests/StoreSecuritySettings.php b/app/Http/Requests/StoreSecuritySettings.php new file mode 100644 index 0000000000..42a529aa57 --- /dev/null +++ b/app/Http/Requests/StoreSecuritySettings.php @@ -0,0 +1,35 @@ +|string> + */ + public function rules(): array + { + return [ + 'pwd_secure_min' => 'numeric|required|min:8', + 'custom_forgot_pass_url' => 'url|nullable', + 'privacy_policy_link' => 'nullable|url', + 'login_remote_user_enabled' => 'numeric|nullable', + 'login_common_disabled' => 'numeric|nullable', + 'login_remote_user_custom_logout_url' => 'string|nullable', + 'login_remote_user_header_name' => 'string|nullable', + ]; + } +} diff --git a/app/Http/Requests/Traits/MayContainCustomFields.php b/app/Http/Requests/Traits/MayContainCustomFields.php new file mode 100644 index 0000000000..2b12ccff6e --- /dev/null +++ b/app/Http/Requests/Traits/MayContainCustomFields.php @@ -0,0 +1,56 @@ +has('model_id')!='') { + + $asset_model = AssetModel::find(request()->input('model_id')); + + // or if we have it available to route-model-binding + } elseif ((request()->route('asset') && (request()->route('asset')->model_id))) { + + $asset_model = AssetModel::find(request()->route('asset')->model_id); + + } else { + + if ($this->method() == 'POST') { + $asset_model = AssetModel::find($this->model_id); + } + + if ($this->method() == 'PATCH' || $this->method() == 'PUT') { + $asset_model = $this->asset->model; + } + } + + + // collect the custom fields in the request + $validator->after(function ($validator) use ($asset_model) { + $request_fields = $this->collect()->keys()->filter(function ($attributes) { + return str_starts_with($attributes, '_snipeit_'); + }); + + // if there are custom fields, find the ones that don't exist on the model's fieldset and add an error to the validator's error bag + if (count($request_fields) > 0 && $validator->errors()->isEmpty()) { + $request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column')) + ->each(function ($request_field_name) use ($request_fields, $validator) { + if (CustomField::where('db_column', $request_field_name)->exists()) { + $validator->errors()->add($request_field_name, trans('validation.custom.custom_field_not_found_on_model')); + } else { + $validator->errors()->add($request_field_name, trans('validation.custom.custom_field_not_found')); + } + }); + } + }); + } +} + diff --git a/app/Http/Requests/UpdateAssetRequest.php b/app/Http/Requests/UpdateAssetRequest.php new file mode 100644 index 0000000000..1b379358f9 --- /dev/null +++ b/app/Http/Requests/UpdateAssetRequest.php @@ -0,0 +1,53 @@ +asset); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + $rules = array_merge( + parent::rules(), + (new Asset)->getRules(), + // this is to overwrite rulesets that include required, and rewrite unique_undeleted + [ + 'model_id' => ['integer', 'exists:models,id,deleted_at,NULL', 'not_array'], + 'status_id' => ['integer', 'exists:status_labels,id'], + 'asset_tag' => [ + 'min:1', 'max:255', 'not_array', + Rule::unique('assets', 'asset_tag')->ignore($this->asset)->withoutTrashed() + ], + ], + ); + + // if the purchase cost is passed in as a string **and** the digit_separator is ',' (as is common in the EU) + // then we tweak the purchase_cost rule to make it a string + if (Setting::getSettings()->digit_separator === '1.234,56' && is_string($this->input('purchase_cost'))) { + $rules['purchase_cost'] = ['nullable', 'string']; + } + + return $rules; + } +} diff --git a/app/Http/Requests/UploadFileRequest.php b/app/Http/Requests/UploadFileRequest.php index 4762e52b75..e58f1a1be3 100644 --- a/app/Http/Requests/UploadFileRequest.php +++ b/app/Http/Requests/UploadFileRequest.php @@ -46,8 +46,6 @@ class UploadFileRequest extends Request $extension = $file->getClientOriginalExtension(); $file_name = $name_prefix.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$file->guessExtension(); - - Log::debug("Your filetype IS: ".$file->getMimeType()); // Check for SVG and sanitize it if ($file->getMimeType() === 'image/svg+xml') { Log::debug('This is an SVG'); @@ -66,7 +64,6 @@ class UploadFileRequest extends Request } else { $put_results = Storage::put($dirname.$file_name, file_get_contents($file)); - Log::debug("Here are the '$put_results' (should be 0 or 1 or true or false or something?)"); } return $file_name; } diff --git a/app/Http/Traits/TwoColumnUniqueUndeletedTrait.php b/app/Http/Traits/TwoColumnUniqueUndeletedTrait.php index 4aae02bfbd..0cf0edb455 100644 --- a/app/Http/Traits/TwoColumnUniqueUndeletedTrait.php +++ b/app/Http/Traits/TwoColumnUniqueUndeletedTrait.php @@ -11,15 +11,17 @@ trait TwoColumnUniqueUndeletedTrait * @param string $field * @return string */ - protected function prepareTwoColumnUniqueUndeletedRule($parameters, $field) + protected function prepareTwoColumnUniqueUndeletedRule($parameters) { $column = $parameters[0]; $value = $this->{$parameters[0]}; + // This is an existing model we're updating so ignore the current ID ($this->getKey()) if ($this->exists) { return 'two_column_unique_undeleted:'.$this->table.','.$this->getKey().','.$column.','.$value; } + // This is a new record, so we can ignore the current ID return 'two_column_unique_undeleted:'.$this->table.',0,'.$column.','.$value; } } diff --git a/app/Http/Transformers/AccessoriesTransformer.php b/app/Http/Transformers/AccessoriesTransformer.php index 7b79973a9a..b502490884 100644 --- a/app/Http/Transformers/AccessoriesTransformer.php +++ b/app/Http/Transformers/AccessoriesTransformer.php @@ -37,10 +37,15 @@ class AccessoriesTransformer 'purchase_date' => ($accessory->purchase_date) ? Helper::getFormattedDateObject($accessory->purchase_date, 'date') : null, 'purchase_cost' => Helper::formatCurrencyOutput($accessory->purchase_cost), 'order_number' => ($accessory->order_number) ? e($accessory->order_number) : null, - 'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, - 'remaining_qty' => (int) $accessory->numRemaining(), - 'users_count' => $accessory->users_count, - + 'min_qty' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, // Legacy - should phase out - replaced by below, for the bootstrap table formatter + 'min_amt' => ($accessory->min_amt) ? (int) $accessory->min_amt : null, + 'remaining_qty' => (int) ($accessory->qty - $accessory->checkouts_count), // Legacy - should phase out - replaced by below, for the bootstrap table formatter + 'remaining' => (int) ($accessory->qty - $accessory->checkouts_count), + 'checkouts_count' => $accessory->checkouts_count, + 'created_by' => ($accessory->adminuser) ? [ + 'id' => (int) $accessory->adminuser->id, + 'name'=> e($accessory->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($accessory->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($accessory->updated_at, 'datetime'), @@ -50,14 +55,14 @@ class AccessoriesTransformer 'checkout' => Gate::allows('checkout', Accessory::class), 'checkin' => false, 'update' => Gate::allows('update', Accessory::class), - 'delete' => Gate::allows('delete', Accessory::class), + 'delete' => $accessory->checkouts_count === 0 && Gate::allows('delete', Accessory::class), 'clone' => Gate::allows('create', Accessory::class), ]; $permissions_array['user_can_checkout'] = false; - if ($accessory->numRemaining() > 0) { + if (($accessory->qty - $accessory->checkouts_count) > 0) { $permissions_array['user_can_checkout'] = true; } @@ -66,27 +71,39 @@ class AccessoriesTransformer return $array; } - public function transformCheckedoutAccessory($accessory, $accessory_users, $total) + public function transformCheckedoutAccessory($accessory_checkouts, $total) { $array = []; - foreach ($accessory_users as $user) { + foreach ($accessory_checkouts as $checkout) { $array[] = [ - - 'assigned_pivot_id' => $user->pivot->id, - 'id' => (int) $user->id, - 'username' => e($user->username), - 'name' => e($user->getFullNameAttribute()), - 'first_name'=> e($user->first_name), - 'last_name'=> e($user->last_name), - 'employee_number' => e($user->employee_num), - 'checkout_notes' => e($user->pivot->note), - 'last_checkout' => Helper::getFormattedDateObject($user->pivot->created_at, 'datetime'), - 'type' => 'user', - 'available_actions' => ['checkin' => true], + 'id' => $checkout->id, + 'assigned_to' => $this->transformAssignedTo($checkout), + 'note' => $checkout->note ? e($checkout->note) : null, + 'created_by' => $checkout->adminuser ? [ + 'id' => (int) $checkout->adminuser->id, + 'name'=> e($checkout->adminuser->present()->fullName), + ]: null, + 'created_at' => Helper::getFormattedDateObject($checkout->created_at, 'datetime'), + 'available_actions' => Gate::allows('checkout', Accessory::class) ? ['checkin' => true] : ['checkin' => false], ]; } return (new DatatablesTransformer)->transformDatatables($array, $total); } + + public function transformAssignedTo($accessoryCheckout) + { + if (is_null($accessoryCheckout->assigned)) { + return null; + } + + if ($accessoryCheckout->checkedOutToUser()) { + return (new UsersTransformer)->transformUserCompact($accessoryCheckout->assigned); + } elseif ($accessoryCheckout->checkedOutToLocation()) { + return (new LocationsTransformer())->transformLocationCompact($accessoryCheckout->assigned); + } elseif ($accessoryCheckout->checkedOutToAsset()) { + return (new AssetsTransformer())->transformAssetCompact($accessoryCheckout->assigned); + } + } } diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index ef00c74471..702ea123d8 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -140,11 +140,13 @@ class ActionlogsTransformer } else { if ($actionlog->item) { if ($actionlog->itemType() == 'asset') { - $file_url = route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]); + $file_url = route('show/assetfile', ['asset' => $actionlog->item->id, 'fileId' => $actionlog->id]); + } elseif ($actionlog->itemType() == 'accessory') { + $file_url = route('show.accessoryfile', ['accessoryId' => $actionlog->item->id, 'fileId' => $actionlog->id]); } elseif ($actionlog->itemType() == 'license') { $file_url = route('show.licensefile', ['licenseId' => $actionlog->item->id, 'fileId' => $actionlog->id]); } elseif ($actionlog->itemType() == 'user') { - $file_url = route('show/userfile', ['userId' => $actionlog->item->id, 'fileId' => $actionlog->id]); + $file_url = route('show/userfile', ['user' => $actionlog->item->id, 'fileId' => $actionlog->id]); } } } @@ -158,7 +160,6 @@ class ActionlogsTransformer [ 'url' => $file_url, 'filename' => $actionlog->filename, - 'inlineable' => (bool) Helper::show_file_inline($actionlog->filename), ] : null, 'item' => ($actionlog->item) ? [ @@ -176,11 +177,17 @@ class ActionlogsTransformer 'next_audit_date' => ($actionlog->itemType()=='asset') ? Helper::getFormattedDateObject($actionlog->calcNextAuditDate(null, $actionlog->item), 'date'): null, 'days_to_next_audit' => $actionlog->daysUntilNextAudit($settings->audit_interval, $actionlog->item), 'action_type' => $actionlog->present()->actionType(), - 'admin' => ($actionlog->admin) ? [ - 'id' => (int) $actionlog->admin->id, - 'name' => e($actionlog->admin->getFullNameAttribute()), - 'first_name'=> e($actionlog->admin->first_name), - 'last_name'=> e($actionlog->admin->last_name) + 'admin' => ($actionlog->adminuser) ? [ + 'id' => (int) $actionlog->adminuser->id, + 'name' => e($actionlog->adminuser->getFullNameAttribute()), + 'first_name'=> e($actionlog->adminuser->first_name), + 'last_name'=> e($actionlog->adminuser->last_name) + ] : null, + 'created_by' => ($actionlog->adminuser) ? [ + 'id' => (int) $actionlog->adminuser->id, + 'name' => e($actionlog->adminuser->getFullNameAttribute()), + 'first_name'=> e($actionlog->adminuser->first_name), + 'last_name'=> e($actionlog->adminuser->last_name) ] : null, 'target' => ($actionlog->target) ? [ 'id' => (int) $actionlog->target->id, @@ -205,11 +212,11 @@ class ActionlogsTransformer - public function transformCheckedoutActionlog (Collection $accessories_users, $total) + public function transformCheckedoutActionlog (Collection $accessories_checkout, $total) { $array = array(); - foreach ($accessories_users as $user) { + foreach ($accessories_checkout as $user) { $array[] = (new UsersTransformer)->transformUser($user); } return (new DatatablesTransformer)->transformDatatables($array, $total); @@ -256,7 +263,7 @@ class ActionlogsTransformer $clean_meta['rtd_location_id']['old'] = $clean_meta['rtd_location_id']['old'] ? "[id: ".$clean_meta['rtd_location_id']['old']."] ". $oldRtdName : ''; $clean_meta['rtd_location_id']['new'] = $clean_meta['rtd_location_id']['new'] ? "[id: ".$clean_meta['rtd_location_id']['new']."] ". $newRtdName : ''; - $clean_meta['Default Location'] = $clean_meta['rtd_location_id']; + $clean_meta[trans('admin/hardware/form.default_location')] = $clean_meta['rtd_location_id']; unset($clean_meta['rtd_location_id']); } @@ -272,7 +279,7 @@ class ActionlogsTransformer $clean_meta['location_id']['old'] = $clean_meta['location_id']['old'] ? "[id: ".$clean_meta['location_id']['old']."] ". $oldLocationName : ''; $clean_meta['location_id']['new'] = $clean_meta['location_id']['new'] ? "[id: ".$clean_meta['location_id']['new']."] ". $newLocationName : ''; - $clean_meta['Current Location'] = $clean_meta['location_id']; + $clean_meta[trans('admin/locations/message.current_location')] = $clean_meta['location_id']; unset($clean_meta['location_id']); } @@ -287,7 +294,7 @@ class ActionlogsTransformer $clean_meta['model_id']['old'] = "[id: ".$clean_meta['model_id']['old']."] ".$oldModelName; $clean_meta['model_id']['new'] = "[id: ".$clean_meta['model_id']['new']."] ".$newModelName; /** model is required at asset creation */ - $clean_meta['Model'] = $clean_meta['model_id']; + $clean_meta[trans('admin/hardware/form.model')] = $clean_meta['model_id']; unset($clean_meta['model_id']); } if(array_key_exists('company_id', $clean_meta)) { @@ -300,7 +307,7 @@ class ActionlogsTransformer $clean_meta['company_id']['old'] = $clean_meta['company_id']['old'] ? "[id: ".$clean_meta['company_id']['old']."] ". $oldCompanyName : trans('general.unassigned'); $clean_meta['company_id']['new'] = $clean_meta['company_id']['new'] ? "[id: ".$clean_meta['company_id']['new']."] ". $newCompanyName : trans('general.unassigned'); - $clean_meta['Company'] = $clean_meta['company_id']; + $clean_meta[trans('general.company')] = $clean_meta['company_id']; unset($clean_meta['company_id']); } if(array_key_exists('supplier_id', $clean_meta)) { @@ -313,7 +320,7 @@ class ActionlogsTransformer $clean_meta['supplier_id']['old'] = $clean_meta['supplier_id']['old'] ? "[id: ".$clean_meta['supplier_id']['old']."] ". $oldSupplierName : trans('general.unassigned'); $clean_meta['supplier_id']['new'] = $clean_meta['supplier_id']['new'] ? "[id: ".$clean_meta['supplier_id']['new']."] ". $newSupplierName : trans('general.unassigned'); - $clean_meta['Supplier'] = $clean_meta['supplier_id']; + $clean_meta[trans('general.supplier')] = $clean_meta['supplier_id']; unset($clean_meta['supplier_id']); } if(array_key_exists('status_id', $clean_meta)) { @@ -326,11 +333,11 @@ class ActionlogsTransformer $clean_meta['status_id']['old'] = $clean_meta['status_id']['old'] ? "[id: ".$clean_meta['status_id']['old']."] ". $oldStatusName : trans('general.unassigned'); $clean_meta['status_id']['new'] = $clean_meta['status_id']['new'] ? "[id: ".$clean_meta['status_id']['new']."] ". $newStatusName : trans('general.unassigned'); - $clean_meta['Status'] = $clean_meta['status_id']; + $clean_meta[trans('general.status_label')] = $clean_meta['status_id']; unset($clean_meta['status_id']); } if(array_key_exists('asset_eol_date', $clean_meta)) { - $clean_meta['EOL date'] = $clean_meta['asset_eol_date']; + $clean_meta[trans('admin/hardware/form.eol_date')] = $clean_meta['asset_eol_date']; unset($clean_meta['asset_eol_date']); } @@ -340,4 +347,4 @@ class ActionlogsTransformer -} \ No newline at end of file +} diff --git a/app/Http/Transformers/AssetMaintenancesTransformer.php b/app/Http/Transformers/AssetMaintenancesTransformer.php index 88ac447c25..ab4ca04cc6 100644 --- a/app/Http/Transformers/AssetMaintenancesTransformer.php +++ b/app/Http/Transformers/AssetMaintenancesTransformer.php @@ -29,14 +29,15 @@ class AssetMaintenancesTransformer 'name'=> ($assetmaintenance->asset->name) ? e($assetmaintenance->asset->name) : null, 'asset_tag'=> e($assetmaintenance->asset->asset_tag), 'serial'=> e($assetmaintenance->asset->serial), - 'deleted_at'=> e($assetmaintenance->asset->deleted_at), - 'created_at'=> e($assetmaintenance->asset->created_at), + 'deleted_at'=> Helper::getFormattedDateObject($assetmaintenance->asset->deleted_at, 'datetime'), + 'created_at' => Helper::getFormattedDateObject($assetmaintenance->asset->created_at, 'datetime'), + 'updated_at' => Helper::getFormattedDateObject($assetmaintenance->asset->updated_at, 'datetime'), ] : null, 'model' => (($assetmaintenance->asset) && ($assetmaintenance->asset->model)) ? [ 'id' => (int) $assetmaintenance->asset->model->id, 'name'=> ($assetmaintenance->asset->model->name) ? e($assetmaintenance->asset->model->name).' '.e($assetmaintenance->asset->model->model_number) : null, ] : null, - 'status_label' => ($assetmaintenance->asset->assetstatus) ? [ + 'status_label' => (($assetmaintenance->asset) && ($assetmaintenance->asset->assetstatus)) ? [ 'id' => (int) $assetmaintenance->asset->assetstatus->id, 'name'=> e($assetmaintenance->asset->assetstatus->name), 'status_type'=> e($assetmaintenance->asset->assetstatus->getStatuslabelType()), @@ -58,13 +59,23 @@ class AssetMaintenancesTransformer 'name'=> e($assetmaintenance->asset->defaultLoc->name), ] : null, 'notes' => ($assetmaintenance->notes) ? Helper::parseEscapedMarkedownInline($assetmaintenance->notes) : null, - 'supplier' => ($assetmaintenance->supplier) ? ['id' => $assetmaintenance->supplier->id, 'name'=> e($assetmaintenance->supplier->name)] : null, + 'supplier' => ($assetmaintenance->supplier) ? [ + 'id' => $assetmaintenance->supplier->id, + 'name'=> e($assetmaintenance->supplier->name) + ] : null, 'cost' => Helper::formatCurrencyOutput($assetmaintenance->cost), 'asset_maintenance_type' => e($assetmaintenance->asset_maintenance_type), 'start_date' => Helper::getFormattedDateObject($assetmaintenance->start_date, 'date'), 'asset_maintenance_time' => $assetmaintenance->asset_maintenance_time, 'completion_date' => Helper::getFormattedDateObject($assetmaintenance->completion_date, 'date'), - 'user_id' => ($assetmaintenance->admin) ? ['id' => $assetmaintenance->admin->id, 'name'=> e($assetmaintenance->admin->getFullNameAttribute())] : null, + 'user_id' => ($assetmaintenance->adminuser) ? [ + 'id' => $assetmaintenance->adminuser->id, + 'name'=> e($assetmaintenance->adminuser->present()->fullName()) + ] : null, // legacy to not change the shape of the API + 'created_by' => ($assetmaintenance->adminuser) ? [ + 'id' => (int) $assetmaintenance->adminuser->id, + 'name'=> e($assetmaintenance->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($assetmaintenance->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($assetmaintenance->updated_at, 'datetime'), 'is_warranty'=> $assetmaintenance->is_warranty, @@ -72,7 +83,7 @@ class AssetMaintenancesTransformer ]; $permissions_array['available_actions'] = [ - 'update' => (Gate::allows('update', Asset::class) && ($assetmaintenance->asset->deleted_at=='')) ? true : false, + 'update' => (Gate::allows('update', Asset::class) && ((($assetmaintenance->asset) && $assetmaintenance->asset->deleted_at==''))) ? true : false, 'delete' => Gate::allows('delete', Asset::class), ]; diff --git a/app/Http/Transformers/AssetModelsTransformer.php b/app/Http/Transformers/AssetModelsTransformer.php index a7b1c6a49f..2d47ca47db 100644 --- a/app/Http/Transformers/AssetModelsTransformer.php +++ b/app/Http/Transformers/AssetModelsTransformer.php @@ -46,8 +46,9 @@ class AssetModelsTransformer 'name'=> e($assetmodel->manufacturer->name), ] : null, 'image' => ($assetmodel->image != '') ? Storage::disk('public')->url('models/'.e($assetmodel->image)) : null, - 'model_number' => e($assetmodel->model_number), + 'model_number' => ($assetmodel->model_number ? e($assetmodel->model_number): null), 'min_amt' => ($assetmodel->min_amt) ? (int) $assetmodel->min_amt : null, + 'remaining' => (int) ($assetmodel->assets_count - $assetmodel->min_amt), 'depreciation' => ($assetmodel->depreciation) ? [ 'id' => (int) $assetmodel->depreciation->id, 'name'=> e($assetmodel->depreciation->name), @@ -65,6 +66,10 @@ class AssetModelsTransformer 'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None', 'requestable' => ($assetmodel->requestable == '1') ? true : false, 'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes), + 'created_by' => ($assetmodel->adminuser) ? [ + 'id' => (int) $assetmodel->adminuser->id, + 'name'=> e($assetmodel->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($assetmodel->deleted_at, 'datetime'), @@ -83,6 +88,42 @@ class AssetModelsTransformer return $array; } + public function transformAssetModelFiles($assetmodel, $total) + { + + $array = []; + foreach ($assetmodel->uploads as $file) { + $array[] = self::transformAssetModelFile($file, $assetmodel); + } + + return (new DatatablesTransformer)->transformDatatables($array, $total); + } + + public function transformAssetModelFile($file, $assetmodel) + { + + $array = [ + 'id' => (int) $file->id, + 'filename' => e($file->filename), + 'note' => $file->note, + 'url' => route('show/modelfile', [$assetmodel->id, $file->id]), + 'created_by' => ($file->adminuser) ? [ + 'id' => (int) $file->adminuser->id, + 'name'=> e($file->adminuser->present()->fullName), + ] : null, + 'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'), + 'updated_at' => Helper::getFormattedDateObject($file->updated_at, 'datetime'), + 'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'), + ]; + + $permissions_array['available_actions'] = [ + 'delete' => (Gate::allows('update', AssetModel::class) && ($assetmodel->deleted_at == '')), + ]; + + $array += $permissions_array; + return $array; + } + public function transformAssetModelsDatatable($assetmodels) { return (new DatatablesTransformer)->transformDatatables($assetmodels); diff --git a/app/Http/Transformers/AssetsTransformer.php b/app/Http/Transformers/AssetsTransformer.php index eeb10eb0d2..3f1e4ac8c2 100644 --- a/app/Http/Transformers/AssetsTransformer.php +++ b/app/Http/Transformers/AssetsTransformer.php @@ -3,12 +3,14 @@ namespace App\Http\Transformers; use App\Helpers\Helper; +use App\Models\Accessory; +use App\Models\AccessoryCheckout; use App\Models\Asset; use App\Models\Setting; use Illuminate\Support\Facades\Gate; use Illuminate\Database\Eloquent\Collection; use Carbon\Carbon; -use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Storage; class AssetsTransformer { @@ -40,7 +42,7 @@ class AssetsTransformer 'requestable' => ($asset->requestable ? true : false), 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, - 'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date).' months' : null, + 'eol' => (($asset->asset_eol_date != '') && ($asset->purchase_date != '')) ? (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true) . ' months' : null, 'asset_eol_date' => ($asset->asset_eol_date != '') ? Helper::getFormattedDateObject($asset->asset_eol_date, 'date') : null, 'status_label' => ($asset->assetstatus) ? [ 'id' => (int) $asset->assetstatus->id, @@ -80,13 +82,17 @@ class AssetsTransformer 'assigned_to' => $this->transformAssignedTo($asset), 'warranty_months' => ($asset->warranty_months > 0) ? e($asset->warranty_months.' '.trans('admin/hardware/form.months')) : null, 'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null, + 'created_by' => ($asset->adminuser) ? [ + 'id' => (int) $asset->adminuser->id, + 'name'=> e($asset->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($asset->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($asset->updated_at, 'datetime'), 'last_audit_date' => Helper::getFormattedDateObject($asset->last_audit_date, 'datetime'), 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date, 'date'), 'deleted_at' => Helper::getFormattedDateObject($asset->deleted_at, 'datetime'), 'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'), - 'age' => $asset->purchase_date ? $asset->purchase_date->diffForHumans() : '', + 'age' => $asset->purchase_date ? $asset->purchase_date->locale(app()->getLocale())->diffForHumans() : '', 'last_checkout' => Helper::getFormattedDateObject($asset->last_checkout, 'datetime'), 'last_checkin' => Helper::getFormattedDateObject($asset->last_checkin, 'datetime'), 'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'), @@ -149,6 +155,7 @@ class AssetsTransformer 'clone' => Gate::allows('create', Asset::class) ? true : false, 'restore' => ($asset->deleted_at!='' && Gate::allows('create', Asset::class)) ? true : false, 'update' => ($asset->deleted_at=='' && Gate::allows('update', Asset::class)) ? true : false, + 'audit' => Gate::allows('audit', Asset::class) ? true : false, 'delete' => ($asset->deleted_at=='' && $asset->assigned_to =='' && Gate::allows('delete', Asset::class) && ($asset->deleted_at == '')) ? true : false, ]; @@ -221,7 +228,7 @@ class AssetsTransformer public function transformRequestedAsset(Asset $asset) { $array = [ - 'id' => (int) $asset->id, + 'id' => (int)$asset->id, 'name' => e($asset->name), 'asset_tag' => e($asset->asset_tag), 'serial' => e($asset->serial), @@ -230,8 +237,8 @@ class AssetsTransformer 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, 'expected_checkin' => Helper::getFormattedDateObject($asset->expected_checkin, 'date'), 'location' => ($asset->location) ? e($asset->location->name) : null, - 'status'=> ($asset->assetstatus) ? $asset->present()->statusMeta : null, - 'assigned_to_self' => ($asset->assigned_to == \Auth::user()->id), + 'status' => ($asset->assetstatus) ? $asset->present()->statusMeta : null, + 'assigned_to_self' => ($asset->assigned_to == auth()->id()), ]; if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) { @@ -240,7 +247,7 @@ class AssetsTransformer foreach ($asset->model->fieldset->fields as $field) { // Only display this if it's allowed via the custom field setting - if (($field->field_encrypted=='0') && ($field->show_in_requestable_list=='1')) { + if (($field->field_encrypted == '0') && ($field->show_in_requestable_list == '1')) { $value = $asset->{$field->db_column}; if (($field->format == 'DATE') && (!is_null($value)) && ($value != '')) { @@ -258,13 +265,70 @@ class AssetsTransformer $permissions_array['available_actions'] = [ - 'cancel' => ($asset->isRequestedBy(\Auth::user())) ? true : false, - 'request' => ($asset->isRequestedBy(\Auth::user())) ? false : true, + 'cancel' => ($asset->isRequestedBy(auth()->user())) ? true : false, + 'request' => ($asset->isRequestedBy(auth()->user())) ? false : true, ]; $array += $permissions_array; return $array; - - } + + public function transformAssetCompact(Asset $asset) + { + $array = [ + 'id' => (int) $asset->id, + 'image' => ($asset->getImageUrl()) ? $asset->getImageUrl() : null, + 'type' => 'asset', + 'name' => e($asset->present()->fullName()), + 'model' => ($asset->model) ? e($asset->model->name) : null, + 'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null, + 'asset_tag' => e($asset->asset_tag), + 'serial' => e($asset->serial), + ]; + + return $array; + } + + public function transformCheckedoutAccessories($accessory_checkouts, $total) + { + + $array = []; + foreach ($accessory_checkouts as $checkout) { + $array[] = self::transformCheckedoutAccessory($checkout); + } + + return (new DatatablesTransformer)->transformDatatables($array, $total); + } + + + public function transformCheckedoutAccessory(AccessoryCheckout $accessory_checkout) + { + if ($accessory_checkout->accessory) { + $array = [ + 'id' => $accessory_checkout->id, + 'accessory' => [ + 'id' => $accessory_checkout->accessory->id, + 'name' => $accessory_checkout->accessory->name, + ], + 'assigned_to' => $accessory_checkout->assigned_to, + 'image' => ($accessory_checkout->accessory->image) ? Storage::disk('public')->url('accessories/' . e($accessory_checkout->accessory->image)) : null, + 'note' => $accessory_checkout->note ? e($accessory_checkout->note) : null, + 'created_by' => $accessory_checkout->adminuser ? [ + 'id' => (int)$accessory_checkout->adminuser->id, + 'name' => e($accessory_checkout->adminuser->present()->fullName), + ] : null, + 'created_at' => Helper::getFormattedDateObject($accessory_checkout->created_at, 'datetime'), + 'deleted_at' => Helper::getFormattedDateObject($accessory_checkout->deleted_at, 'datetime'), + ]; + + $permissions_array['available_actions'] = [ + 'checkout' => false, + 'checkin' => Gate::allows('checkin', Accessory::class), + ]; + + $array += $permissions_array; + return $array; + } + } + } diff --git a/app/Http/Transformers/CategoriesTransformer.php b/app/Http/Transformers/CategoriesTransformer.php index d5e1ceb51b..0d1834649d 100644 --- a/app/Http/Transformers/CategoriesTransformer.php +++ b/app/Http/Transformers/CategoriesTransformer.php @@ -62,6 +62,11 @@ class CategoriesTransformer 'consumables_count' => (int) $category->consumables_count, 'components_count' => (int) $category->components_count, 'licenses_count' => (int) $category->licenses_count, + 'created_by' => ($category->adminuser) ? [ + 'id' => (int) $category->adminuser->id, + 'name'=> e($category->adminuser->present()->fullName()), + ] : null, + 'notes' => Helper::parseEscapedMarkedownInline($category->notes), 'created_at' => Helper::getFormattedDateObject($category->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($category->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/CompaniesTransformer.php b/app/Http/Transformers/CompaniesTransformer.php index fe8befc27a..8ca5344de6 100644 --- a/app/Http/Transformers/CompaniesTransformer.php +++ b/app/Http/Transformers/CompaniesTransformer.php @@ -30,14 +30,19 @@ class CompaniesTransformer 'fax' => ($company->fax!='') ? e($company->fax): null, 'email' => ($company->email!='') ? e($company->email): null, 'image' => ($company->image) ? Storage::disk('public')->url('companies/'.e($company->image)) : null, - 'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'), - 'updated_at' => Helper::getFormattedDateObject($company->updated_at, 'datetime'), 'assets_count' => (int) $company->assets_count, 'licenses_count' => (int) $company->licenses_count, 'accessories_count' => (int) $company->accessories_count, 'consumables_count' => (int) $company->consumables_count, 'components_count' => (int) $company->components_count, 'users_count' => (int) $company->users_count, + 'created_by' => ($company->adminuser) ? [ + 'id' => (int) $company->adminuser->id, + 'name'=> e($company->adminuser->present()->fullName()), + ] : null, + 'notes' => Helper::parseEscapedMarkedownInline($company->notes), + 'created_at' => Helper::getFormattedDateObject($company->created_at, 'datetime'), + 'updated_at' => Helper::getFormattedDateObject($company->updated_at, 'datetime'), ]; $permissions_array['available_actions'] = [ diff --git a/app/Http/Transformers/ComponentsTransformer.php b/app/Http/Transformers/ComponentsTransformer.php index d18870bdc3..90d10ba9a5 100644 --- a/app/Http/Transformers/ComponentsTransformer.php +++ b/app/Http/Transformers/ComponentsTransformer.php @@ -38,6 +38,8 @@ class ComponentsTransformer 'name' => e($component->category->name), ] : null, 'supplier' => ($component->supplier) ? ['id' => $component->supplier->id, 'name'=> e($component->supplier->name)] : null, + 'manufacturer' => ($component->manufacturer) ? ['id' => $component->manufacturer->id, 'name'=> e($component->manufacturer->name)] : null, + 'model_number' => ($component->model_number) ? e($component->model_number) : null, 'order_number' => e($component->order_number), 'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'), 'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost), @@ -47,6 +49,10 @@ class ComponentsTransformer 'name' => e($component->company->name), ] : null, 'notes' => ($component->notes) ? Helper::parseEscapedMarkedownInline($component->notes) : null, + 'created_by' => ($component->adminuser) ? [ + 'id' => (int) $component->adminuser->id, + 'name'=> e($component->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($component->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($component->updated_at, 'datetime'), 'user_can_checkout' => ($component->numRemaining() > 0) ? 1 : 0, @@ -56,7 +62,7 @@ class ComponentsTransformer 'checkout' => Gate::allows('checkout', Component::class), 'checkin' => Gate::allows('checkin', Component::class), 'update' => Gate::allows('update', Component::class), - 'delete' => Gate::allows('delete', Component::class), + 'delete' => $component->isDeletable(), ]; $array += $permissions_array; diff --git a/app/Http/Transformers/ConsumablesTransformer.php b/app/Http/Transformers/ConsumablesTransformer.php index f23ebc0382..b31e31ac96 100644 --- a/app/Http/Transformers/ConsumablesTransformer.php +++ b/app/Http/Transformers/ConsumablesTransformer.php @@ -40,6 +40,10 @@ class ConsumablesTransformer 'purchase_date' => Helper::getFormattedDateObject($consumable->purchase_date, 'date'), 'qty' => (int) $consumable->qty, 'notes' => ($consumable->notes) ? Helper::parseEscapedMarkedownInline($consumable->notes) : null, + 'created_by' => ($consumable->adminuser) ? [ + 'id' => (int) $consumable->adminuser->id, + 'name'=> e($consumable->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($consumable->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($consumable->updated_at, 'datetime'), ]; @@ -55,6 +59,7 @@ class ConsumablesTransformer 'checkin' => Gate::allows('checkin', Consumable::class), 'update' => Gate::allows('update', Consumable::class), 'delete' => Gate::allows('delete', Consumable::class), + 'clone' => (Gate::allows('create', Consumable::class) && ($consumable->deleted_at == '')), ]; $array += $permissions_array; diff --git a/app/Http/Transformers/CustomFieldsTransformer.php b/app/Http/Transformers/CustomFieldsTransformer.php index d6401a3e5e..501d264b55 100644 --- a/app/Http/Transformers/CustomFieldsTransformer.php +++ b/app/Http/Transformers/CustomFieldsTransformer.php @@ -50,6 +50,9 @@ class CustomFieldsTransformer 'display_in_user_view' => ($field->display_in_user_view =='1') ? true : false, 'auto_add_to_fieldsets' => ($field->auto_add_to_fieldsets == '1') ? true : false, 'show_in_listview' => ($field->show_in_listview == '1') ? true : false, + 'display_checkin' => ($field->display_checkin == '1') ? true : false, + 'display_checkout' => ($field->display_checkout == '1') ? true : false, + 'display_audit' => ($field->display_audit == '1') ? true : false, 'created_at' => Helper::getFormattedDateObject($field->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($field->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/DepartmentsTransformer.php b/app/Http/Transformers/DepartmentsTransformer.php index 546cedf04f..3d1e4c6f90 100644 --- a/app/Http/Transformers/DepartmentsTransformer.php +++ b/app/Http/Transformers/DepartmentsTransformer.php @@ -44,6 +44,7 @@ class DepartmentsTransformer 'name' => e($department->location->name), ] : null, 'users_count' => e($department->users_count), + 'notes' => Helper::parseEscapedMarkedownInline($department->notes), 'created_at' => Helper::getFormattedDateObject($department->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($department->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/DepreciationReportTransformer.php b/app/Http/Transformers/DepreciationReportTransformer.php index 47d1cbc476..00eb984fa5 100644 --- a/app/Http/Transformers/DepreciationReportTransformer.php +++ b/app/Http/Transformers/DepreciationReportTransformer.php @@ -61,17 +61,14 @@ class DepreciationReportTransformer /** * Override the previously set null values if there is a valid model and associated depreciation */ - if (($asset->model) && ($asset->model->depreciation)) { + if (($asset->model) && ($asset->model->depreciation) && ($asset->model->depreciation->months !== 0)) { $depreciated_value = Helper::formatCurrencyOutput($asset->getDepreciatedValue()); - if($asset->model->eol==0 || $asset->model->eol==null ){ - $monthly_depreciation = Helper::formatCurrencyOutput($asset->purchase_cost / $asset->model->depreciation->months); - } - else { - $monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0)); - } + $monthly_depreciation =Helper::formatCurrencyOutput($asset->purchase_cost / $asset->model->depreciation->months); $diff = Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue())); } - + else if($asset->model->eol !== null) { + $monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0)); + } if ($asset->assigned) { $checkout_target = $asset->assigned->name; diff --git a/app/Http/Transformers/DepreciationsTransformer.php b/app/Http/Transformers/DepreciationsTransformer.php index 78a01b4c1e..64d4c88f7e 100644 --- a/app/Http/Transformers/DepreciationsTransformer.php +++ b/app/Http/Transformers/DepreciationsTransformer.php @@ -7,6 +7,7 @@ use App\Models\Depreciable; use App\Models\Depreciation; use Illuminate\Support\Facades\Gate; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\Log; class DepreciationsTransformer { @@ -26,7 +27,14 @@ class DepreciationsTransformer 'id' => (int) $depreciation->id, 'name' => e($depreciation->name), 'months' => $depreciation->months.' '.trans('general.months'), - 'depreciation_min' => $depreciation->depreciation_min, + 'depreciation_min' => $depreciation->depreciation_type === 'percent' ? $depreciation->depreciation_min.'%' : $depreciation->depreciation_min, + 'assets_count' => $depreciation->assets_count, + 'models_count' => $depreciation->models_count, + 'licenses_count' => $depreciation->licenses_count, + 'created_by' => ($depreciation->adminuser) ? [ + 'id' => (int) $depreciation->adminuser->id, + 'name'=> e($depreciation->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime') ]; diff --git a/app/Http/Transformers/GroupsTransformer.php b/app/Http/Transformers/GroupsTransformer.php index bf7e2bfd70..9495aeeecc 100644 --- a/app/Http/Transformers/GroupsTransformer.php +++ b/app/Http/Transformers/GroupsTransformer.php @@ -24,9 +24,13 @@ class GroupsTransformer $array = [ 'id' => (int) $group->id, 'name' => e($group->name), - 'permissions' => json_decode($group->permissions), + 'permissions' => $group->decodePermissions(), 'users_count' => (int) $group->users_count, - 'created_by' => ($group->admin) ? e($group->admin->present()->fullName) : null, + 'notes' => Helper::parseEscapedMarkedownInline($group->notes), + 'created_by' => ($group->adminuser) ? [ + 'id' => (int) $group->adminuser->id, + 'name'=> e($group->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($group->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($group->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/LicenseSeatsTransformer.php b/app/Http/Transformers/LicenseSeatsTransformer.php index 47133a9b95..57f4087e9f 100644 --- a/app/Http/Transformers/LicenseSeatsTransformer.php +++ b/app/Http/Transformers/LicenseSeatsTransformer.php @@ -2,6 +2,7 @@ namespace App\Http\Transformers; +use App\Helpers\Helper; use App\Models\License; use App\Models\LicenseSeat; use Illuminate\Support\Facades\Gate; @@ -12,20 +13,20 @@ class LicenseSeatsTransformer public function transformLicenseSeats(Collection $seats, $total) { $array = []; - $seat_count = 0; + foreach ($seats as $seat) { - $seat_count++; - $array[] = self::transformLicenseSeat($seat, $seat_count); + $array[] = self::transformLicenseSeat($seat); } return (new DatatablesTransformer)->transformDatatables($array, $total); } - public function transformLicenseSeat(LicenseSeat $seat, $seat_count = 0) + public function transformLicenseSeat(LicenseSeat $seat) { $array = [ 'id' => (int) $seat->id, 'license_id' => (int) $seat->license->id, + 'updated_at' => Helper::getFormattedDateObject($seat->updated_at, 'datetime'), // we use updated_at here because the record gets updated when it's checked in or out 'assigned_user' => ($seat->user) ? [ 'id' => (int) $seat->user->id, 'name'=> e($seat->user->present()->fullName), @@ -36,24 +37,23 @@ class LicenseSeatsTransformer 'name' => e($seat->user->department->name), ] : null, + 'created_at' => Helper::getFormattedDateObject($seat->created_at, 'datetime'), ] : null, 'assigned_asset' => ($seat->asset) ? [ 'id' => (int) $seat->asset->id, 'name'=> e($seat->asset->present()->fullName), + 'created_at' => Helper::getFormattedDateObject($seat->created_at, 'datetime'), ] : null, 'location' => ($seat->location()) ? [ 'id' => (int) $seat->location()->id, 'name'=> e($seat->location()->name), + 'created_at' => Helper::getFormattedDateObject($seat->created_at, 'datetime'), ] : null, 'reassignable' => (bool) $seat->license->reassignable, 'notes' => e($seat->notes), 'user_can_checkout' => (($seat->assigned_to == '') && ($seat->asset_id == '')), ]; - if ($seat_count != 0) { - $array['name'] = 'Seat '.$seat_count; - } - $permissions_array['available_actions'] = [ 'checkout' => Gate::allows('checkout', License::class), 'checkin' => Gate::allows('checkin', License::class), diff --git a/app/Http/Transformers/LicensesTransformer.php b/app/Http/Transformers/LicensesTransformer.php index fa218da4d1..b63a5725a4 100644 --- a/app/Http/Transformers/LicensesTransformer.php +++ b/app/Http/Transformers/LicensesTransformer.php @@ -38,6 +38,7 @@ class LicensesTransformer 'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'), 'seats' => (int) $license->seats, 'free_seats_count' => (int) $license->free_seats_count, + 'remaining' => (int) $license->free_seats_count, 'min_amt' => ($license->min_amt) ? (int) ($license->min_amt) : null, 'license_name' => ($license->license_name) ? e($license->license_name) : null, 'license_email' => ($license->license_email) ? e($license->license_email) : null, @@ -45,6 +46,10 @@ class LicensesTransformer 'maintained' => ($license->maintained == 1) ? true : false, 'supplier' => ($license->supplier) ? ['id' => (int) $license->supplier->id, 'name'=> e($license->supplier->name)] : null, 'category' => ($license->category) ? ['id' => (int) $license->category->id, 'name'=> e($license->category->name)] : null, + 'created_by' => ($license->adminuser) ? [ + 'id' => (int) $license->adminuser->id, + 'name'=> e($license->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($license->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($license->updated_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($license->deleted_at, 'datetime'), @@ -57,7 +62,7 @@ class LicensesTransformer 'checkin' => Gate::allows('checkin', License::class), 'clone' => Gate::allows('create', License::class), 'update' => Gate::allows('update', License::class), - 'delete' => (Gate::allows('delete', License::class) && ($license->seats == $license->availCount()->count())) ? true : false, + 'delete' => (Gate::allows('delete', License::class) && ($license->free_seats_count > 0)) ? true : false, ]; $array += $permissions_array; diff --git a/app/Http/Transformers/LocationsTransformer.php b/app/Http/Transformers/LocationsTransformer.php index 513b967f42..4965ff99d5 100644 --- a/app/Http/Transformers/LocationsTransformer.php +++ b/app/Http/Transformers/LocationsTransformer.php @@ -3,6 +3,8 @@ namespace App\Http\Transformers; use App\Helpers\Helper; +use App\Models\Accessory; +use App\Models\AccessoryCheckout; use App\Models\Location; use Illuminate\Support\Facades\Gate; use Illuminate\Database\Eloquent\Collection; @@ -45,19 +47,30 @@ class LocationsTransformer 'zip' => ($location->zip) ? e($location->zip) : null, 'phone' => ($location->phone!='') ? e($location->phone): null, 'fax' => ($location->fax!='') ? e($location->fax): null, + 'accessories_count' => (int) $location->accessories_count, + 'assigned_accessories_count' => (int) $location->assigned_accessories_count, 'assigned_assets_count' => (int) $location->assigned_assets_count, 'assets_count' => (int) $location->assets_count, 'rtd_assets_count' => (int) $location->rtd_assets_count, 'users_count' => (int) $location->users_count, 'currency' => ($location->currency) ? e($location->currency) : null, 'ldap_ou' => ($location->ldap_ou) ? e($location->ldap_ou) : null, + 'notes' => Helper::parseEscapedMarkedownInline($location->notes), 'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'), + 'created_by' => $location->adminuser ? [ + 'id' => (int) $location->adminuser->id, + 'name'=> e($location->adminuser->present()->fullName), + ]: null, 'updated_at' => Helper::getFormattedDateObject($location->updated_at, 'datetime'), 'parent' => ($location->parent) ? [ 'id' => (int) $location->parent->id, 'name'=> e($location->parent->name), ] : null, 'manager' => ($location->manager) ? (new UsersTransformer)->transformUser($location->manager) : null, + 'company' => ($location->company) ? [ + 'id' => (int) $location->company->id, + 'name'=> e($location->company->name) + ] : null, 'children' => $children_arr, ]; @@ -76,4 +89,85 @@ class LocationsTransformer return $array; } } + + + public function transformCheckedoutAccessories($accessory_checkouts, $total) + { + + $array = []; + foreach ($accessory_checkouts as $checkout) { + $array[] = self::transformCheckedoutAccessory($checkout); + } + + return (new DatatablesTransformer)->transformDatatables($array, $total); + } + + + public function transformCheckedoutAccessory(AccessoryCheckout $accessory_checkout) + { + + $array = [ + 'id' => $accessory_checkout->id, + 'assigned_to' => $accessory_checkout->assigned_to, + 'accessory' => $this->transformAccessory($accessory_checkout->accessory), + 'image' => ($accessory_checkout?->accessory?->image) ? Storage::disk('public')->url('accessories/' . e($accessory_checkout->accessory->image)) : null, + 'note' => $accessory_checkout->note ? e($accessory_checkout->note) : null, + 'created_by' => $accessory_checkout->adminuser ? [ + 'id' => (int) $accessory_checkout->adminuser->id, + 'name'=> e($accessory_checkout->adminuser->present()->fullName), + ]: null, + 'created_at' => Helper::getFormattedDateObject($accessory_checkout->created_at, 'datetime'), + ]; + + $permissions_array['available_actions'] = [ + 'checkout' => false, + 'checkin' => Gate::allows('checkin', Accessory::class), + ]; + + $array += $permissions_array; + return $array; + } + + + + /** + * This gives a compact view of the location data without any additional relational queries, + * allowing us to 1) deliver a smaller payload and 2) avoid additional queries on relations that + * have not been easy/lazy loaded already + * + * @param Location $location + * @return array + * @throws \Exception + */ + public function transformLocationCompact(Location $location = null) + { + if ($location) { + + $array = [ + 'id' => (int) $location->id, + 'image' => ($location->image) ? Storage::disk('public')->url('locations/'.e($location->image)) : null, + 'type' => "location", + 'name' => e($location->name), + 'created_by' => $location->adminuser ? [ + 'id' => (int) $location->adminuser->id, + 'name'=> e($location->adminuser->present()->fullName), + ]: null, + 'created_at' => Helper::getFormattedDateObject($location->created_at, 'datetime'), + ]; + + return $array; + } + } + + private function transformAccessory(?Accessory $accessory): ?array + { + if ($accessory) { + return [ + 'id' => $accessory->id, + 'name' => $accessory->name, + ]; + } + + return null; + } } diff --git a/app/Http/Transformers/ManufacturersTransformer.php b/app/Http/Transformers/ManufacturersTransformer.php index 9c84fd50fe..cf17eb7764 100644 --- a/app/Http/Transformers/ManufacturersTransformer.php +++ b/app/Http/Transformers/ManufacturersTransformer.php @@ -36,6 +36,12 @@ class ManufacturersTransformer 'licenses_count' => (int) $manufacturer->licenses_count, 'consumables_count' => (int) $manufacturer->consumables_count, 'accessories_count' => (int) $manufacturer->accessories_count, + 'components_count' => (int) $manufacturer->components_count, + 'notes' => Helper::parseEscapedMarkedownInline($manufacturer->notes), + 'created_by' => ($manufacturer->adminuser) ? [ + 'id' => (int) $manufacturer->adminuser->id, + 'name'=> e($manufacturer->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($manufacturer->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($manufacturer->updated_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($manufacturer->deleted_at, 'datetime'), diff --git a/app/Http/Transformers/PredefinedKitsTransformer.php b/app/Http/Transformers/PredefinedKitsTransformer.php index a5d37e5c72..61c9e476a9 100644 --- a/app/Http/Transformers/PredefinedKitsTransformer.php +++ b/app/Http/Transformers/PredefinedKitsTransformer.php @@ -2,6 +2,8 @@ namespace App\Http\Transformers; +use App\Helpers\Helper; +use App\Models\Asset; use App\Models\PredefinedKit; use App\Models\SnipeModel; use Illuminate\Support\Facades\Gate; @@ -30,12 +32,18 @@ class PredefinedKitsTransformer $array = [ 'id' => (int) $kit->id, 'name' => e($kit->name), + 'created_by' => ($kit->adminuser) ? [ + 'id' => (int) $kit->adminuser->id, + 'name'=> e($kit->adminuser->present()->fullName()), + ] : null, + 'created_at' => Helper::getFormattedDateObject($kit->created_at, 'datetime'), + 'updated_at' => Helper::getFormattedDateObject($kit->updated_at, 'datetime'), ]; $permissions_array['available_actions'] = [ 'update' => Gate::allows('update', PredefinedKit::class), 'delete' => Gate::allows('delete', PredefinedKit::class), - 'checkout' => Gate::allows('checkout', PredefinedKit::class), + 'checkout' => Gate::allows('checkout', Asset::class), // 'clone' => Gate::allows('create', PredefinedKit::class), // 'restore' => Gate::allows('create', PredefinedKit::class), ]; diff --git a/app/Http/Transformers/StatuslabelsTransformer.php b/app/Http/Transformers/StatuslabelsTransformer.php index 41dd336068..751edb7016 100644 --- a/app/Http/Transformers/StatuslabelsTransformer.php +++ b/app/Http/Transformers/StatuslabelsTransformer.php @@ -30,6 +30,10 @@ class StatuslabelsTransformer 'default_label' => ($statuslabel->default_label == '1') ? true : false, 'assets_count' => (int) $statuslabel->assets_count, 'notes' => e($statuslabel->notes), + 'created_by' => ($statuslabel->adminuser) ? [ + 'id' => (int) $statuslabel->adminuser->id, + 'name'=> e($statuslabel->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($statuslabel->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($statuslabel->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/SuppliersTransformer.php b/app/Http/Transformers/SuppliersTransformer.php index 1fdc93c193..750c969c63 100644 --- a/app/Http/Transformers/SuppliersTransformer.php +++ b/app/Http/Transformers/SuppliersTransformer.php @@ -45,6 +45,10 @@ class SuppliersTransformer 'components_count' => (int) $supplier->components_count, 'notes' => ($supplier->notes) ? Helper::parseEscapedMarkedownInline($supplier->notes) : null, 'created_at' => Helper::getFormattedDateObject($supplier->created_at, 'datetime'), + 'created_by' => $supplier->adminuser ? [ + 'id' => (int) $supplier->adminuser->id, + 'name'=> e($supplier->adminuser->present()->fullName), + ]: null, 'updated_at' => Helper::getFormattedDateObject($supplier->updated_at, 'datetime'), ]; diff --git a/app/Http/Transformers/UploadedFilesTransformer.php b/app/Http/Transformers/UploadedFilesTransformer.php new file mode 100644 index 0000000000..a18c9f9b65 --- /dev/null +++ b/app/Http/Transformers/UploadedFilesTransformer.php @@ -0,0 +1,56 @@ +transformDatatables($array, $total); + } + + + public function transformFile(Actionlog $file) + { + $snipeModel = $file->item_type; + + + // This will be used later as we extend out this transformer to handle more types of uploads + if ($file->item_type == Asset::class) { + $file_url = route('show/assetfile', [$file->item_id, $file->id]); + } + + $array = [ + 'id' => (int) $file->id, + 'filename' => e($file->filename), + 'url' => $file_url, + 'created_by' => ($file->adminuser) ? [ + 'id' => (int) $file->adminuser->id, + 'name'=> e($file->adminuser->present()->fullName), + ] : null, + 'created_at' => Helper::getFormattedDateObject($file->created_at, 'datetime'), + 'updated_at' => Helper::getFormattedDateObject($file->updated_at, 'datetime'), + 'deleted_at' => Helper::getFormattedDateObject($file->deleted_at, 'datetime'), + ]; + + $permissions_array['available_actions'] = [ + 'delete' => (Gate::allows('update', $snipeModel) && ($file->deleted_at == '')), + ]; + + $array += $permissions_array; + return $array; + } + +} diff --git a/app/Http/Transformers/UsersTransformer.php b/app/Http/Transformers/UsersTransformer.php index 8b3b94b64d..7f05fa5248 100644 --- a/app/Http/Transformers/UsersTransformer.php +++ b/app/Http/Transformers/UsersTransformer.php @@ -4,8 +4,8 @@ namespace App\Http\Transformers; use App\Helpers\Helper; use App\Models\User; -use Illuminate\Support\Facades\Gate; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\Gate; class UsersTransformer { @@ -24,7 +24,7 @@ class UsersTransformer $array = [ 'id' => (int) $user->id, - 'avatar' => e($user->present()->gravatar), + 'avatar' => e($user->present()->gravatar) ?? null, 'name' => e($user->getFullNameAttribute()), 'first_name' => e($user->first_name), 'last_name' => e($user->last_name), @@ -110,6 +110,37 @@ class UsersTransformer return $array; } + /** + * This gives a compact view of the user data without any additional relational queries, + * allowing us to 1) deliver a smaller payload and 2) avoid additional queries on relations that + * have not been easy/lazy loaded already + * + * @param User $user + * @return array + * @throws \Exception + */ + public function transformUserCompact(User $user) : array + { + + $array = [ + 'id' => (int) $user->id, + 'image' => e($user->present()->gravatar) ?? null, + 'type' => 'user', + 'name' => e($user->getFullNameAttribute()), + 'first_name' => e($user->first_name), + 'last_name' => e($user->last_name), + 'username' => e($user->username), + 'created_by' => $user->adminuser ? [ + 'id' => (int) $user->adminuser->id, + 'name'=> e($user->adminuser->present()->fullName), + ]: null, + 'created_at' => Helper::getFormattedDateObject($user->created_at, 'datetime'), + 'deleted_at' => ($user->deleted_at) ? Helper::getFormattedDateObject($user->deleted_at, 'datetime') : null, + ]; + + return $array; + } + public function transformUsersDatatable($users) { return (new DatatablesTransformer)->transformDatatables($users); diff --git a/app/Importer/AccessoryImporter.php b/app/Importer/AccessoryImporter.php index eb17c5acad..bc9c1909ff 100644 --- a/app/Importer/AccessoryImporter.php +++ b/app/Importer/AccessoryImporter.php @@ -42,6 +42,7 @@ class AccessoryImporter extends ItemImporter } $this->log('No Matching Accessory, Creating a new one'); $accessory = new Accessory(); + $accessory->created_by = auth()->id(); $this->item['model_number'] = $this->findCsvMatch($row, "model_number"); $this->item['min_amt'] = $this->findCsvMatch($row, "min_amt"); $accessory->fill($this->sanitizeItemForStoring($accessory)); diff --git a/app/Importer/AssetImporter.php b/app/Importer/AssetImporter.php index 02ffa2077c..1d03d6f95b 100644 --- a/app/Importer/AssetImporter.php +++ b/app/Importer/AssetImporter.php @@ -3,14 +3,10 @@ namespace App\Importer; use App\Models\Asset; -use App\Models\AssetModel; use App\Models\Statuslabel; use App\Models\User; use App\Events\CheckoutableCheckedIn; -use Carbon\CarbonImmutable; -use Illuminate\Support\Facades\Auth; -use Carbon\Carbon; -use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Crypt; class AssetImporter extends ItemImporter { @@ -20,10 +16,22 @@ class AssetImporter extends ItemImporter { parent::__construct($filename); - $this->defaultStatusLabelId = Statuslabel::first()->id; - + $this->defaultStatusLabelId = Statuslabel::first()?->id; + if (!is_null(Statuslabel::deployable()->first())) { - $this->defaultStatusLabelId = Statuslabel::deployable()->first()->id; + $this->defaultStatusLabelId = Statuslabel::deployable()->first()?->id; + } + + if (is_null($this->defaultStatusLabelId)) { + $defaultLabel = Statuslabel::create([ + 'name' => 'Default Status', + 'deployable' => 0, + 'pending' => 1, + 'archived' => 0, + 'notes' => 'Default status label created by AssetImporter', + ]); + + $this->defaultStatusLabelId = $defaultLabel->id; } } @@ -72,11 +80,22 @@ class AssetImporter extends ItemImporter $asset_tag = Asset::autoincrement_asset(); } - $asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first(); + + + if ($this->findCsvMatch($row, 'id')!='') { + // Override asset if an ID was given + \Log::debug('Finding asset by ID: '.$this->findCsvMatch($row, 'id')); + $asset = Asset::find($this->findCsvMatch($row, 'id')); + } else { + $asset = Asset::where(['asset_tag'=> (string) $asset_tag])->first(); + } + if ($asset) { if (! $this->updating) { - $this->log('A matching Asset '.$asset_tag.' already exists'); - return; + $exists_error = trans('general.import_asset_tag_exists', ['asset_tag' => $asset_tag]); + $this->log($exists_error); + $this->addErrorToBag($asset, 'asset_tag', $exists_error); + return $exists_error; } $this->log('Updating Asset'); @@ -179,16 +198,16 @@ class AssetImporter extends ItemImporter $this->log('Asset '.$this->item['name'].' with serial number '.$this->item['serial'].' was created'); // If we have a target to checkout to, lets do so. - //-- user_id is a property of the abstract class Importer, which this class inherits from and it's set by + //-- created_by is a property of the abstract class Importer, which this class inherits from and it's set by //-- the class that needs to use it (command importer or GUI importer inside the project). if (isset($target) && ($target !== false)) { if (!is_null($asset->assigned_to)){ if ($asset->assigned_to != $target->id) { - event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), Auth::user(), 'Checkin from CSV Importer', $checkin_date)); + event(new CheckoutableCheckedIn($asset, User::find($asset->assigned_to), auth()->user(), 'Checkin from CSV Importer', $checkin_date)); } } - $asset->fresh()->checkOut($target, $this->user_id, $checkout_date, null, 'Checkout from CSV Importer', $asset->name); + $asset->fresh()->checkOut($target, $this->created_by, $checkout_date, null, 'Checkout from CSV Importer', $asset->name); } return; diff --git a/app/Importer/AssetModelImporter.php b/app/Importer/AssetModelImporter.php new file mode 100644 index 0000000000..7cfd8a530d --- /dev/null +++ b/app/Importer/AssetModelImporter.php @@ -0,0 +1,174 @@ +createAssetModelIfNotExists($row); + } + + /** + * Create a model if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createModelIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createAssetModelIfNotExists(array $row) + { + + $editingAssetModel = false; + $assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($assetModel) { + if (! $this->updating) { + $this->log('A matching Model '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Model'); + $editingAssetModel = true; + } else { + $this->log('No Matching Model, Create a new one'); + $assetModel = new AssetModel(); + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['category'] = trim($this->findCsvMatch($row, 'category')); + $this->item['manufacturer'] = trim($this->findCsvMatch($row, 'manufacturer')); + $this->item['min_amt'] = trim($this->findCsvMatch($row, 'min_amt')); + $this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number')); + $this->item['eol'] = trim($this->findCsvMatch($row, 'eol')); + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + $this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset')); + $this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation')); + $this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0; + + if (!empty($this->item['category'])) { + if ($category = $this->createOrFetchCategory($this->item['category'])) { + $this->item['category_id'] = $category; + } + } + if (!empty($this->item['manufacturer'])) { + if ($manufacturer = $this->createOrFetchManufacturer($this->item['manufacturer'])) { + $this->item['manufacturer_id'] = $manufacturer; + } + } + + if (!empty($this->item['depreciation'])) { + if ($depreciation = $this->fetchDepreciation($this->item['depreciation'])) { + $this->item['depreciation_id'] = $depreciation; + } + } + + if (!empty($this->item['fieldset'])) { + if ($fieldset = $this->createOrFetchCustomFieldset($this->item['fieldset'])) { + $this->item['fieldset_id'] = $fieldset; + } + } + + Log::debug('Item array is: '); + Log::debug(print_r($this->item, true)); + + + if ($editingAssetModel) { + Log::debug('Updating existing model'); + $assetModel->update($this->sanitizeItemForUpdating($assetModel)); + } else { + Log::debug('Creating model'); + $assetModel->fill($this->sanitizeItemForStoring($assetModel)); + $assetModel->created_by = auth()->id(); + } + + if ($assetModel->save()) { + $this->log('AssetModel '.$assetModel->name.' created or updated from CSV import'); + return $assetModel; + + } else { + $this->log($assetModel->getErrors()->first()); + $this->addErrorToBag($assetModel, $assetModel->getErrors()->keys()[0], $assetModel->getErrors()->first()); + return $assetModel->getErrors(); + } + + } + + + /** + * Fetch an existing depreciation, or create new if it doesn't exist. + * + * We only do a fetch vs create here since Depreciations have additional fields required + * and cannot be created without them (months, for example.)) + * + * @author A. Gianotto + * @since 7.1.3 + * @param $depreciation_name string + * @return int id of depreciation created/found + */ + public function fetchDepreciation($depreciation_name) : ?int + { + if ($depreciation_name != '') { + + if ($depreciation = Depreciation::where('name', '=', $depreciation_name)->first()) { + $this->log('A matching Depreciation '.$depreciation_name.' already exists'); + return $depreciation->id; + } + } + + return null; + } + + /** + * Fetch an existing fieldset, or create new if it doesn't exist + * + * @author A. Gianotto + * @since 7.1.3 + * @param $fieldset_name string + * @return int id of fieldset created/found + */ + public function createOrFetchCustomFieldset($fieldset_name) : ?int + { + if ($fieldset_name != '') { + $fieldset = CustomFieldset::where('name', '=', $fieldset_name)->first(); + + if ($fieldset) { + $this->log('A matching fieldset '.$fieldset_name.' already exists'); + return $fieldset->id; + } + + $fieldset = new CustomFieldset(); + $fieldset->name = $fieldset_name; + + if ($fieldset->save()) { + $this->log('Fieldset '.$fieldset_name.' was created'); + + return $fieldset->id; + } + $this->logError($fieldset, 'Fieldset'); + } + + return null; + } +} \ No newline at end of file diff --git a/app/Importer/CategoryImporter.php b/app/Importer/CategoryImporter.php new file mode 100644 index 0000000000..39b477c96a --- /dev/null +++ b/app/Importer/CategoryImporter.php @@ -0,0 +1,99 @@ +createCategoryIfNotExists($row); + } + + /** + * Create a category if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createCategoryIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createCategoryIfNotExists(array $row) + { + + $editingCategory = false; + + $category = Category::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($this->findCsvMatch($row, 'id')!='') { + // Override category if an ID was given + \Log::debug('Finding category by ID: '.$this->findCsvMatch($row, 'id')); + $category = Category::find($this->findCsvMatch($row, 'id')); + } + + + if ($category) { + if (! $this->updating) { + $this->log('A matching Category '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Category'); + $editingCategory = true; + } else { + $this->log('No Matching Category, Create a new one'); + $category = new Category; + $category->created_by = auth()->id(); + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + $this->item['eula_text'] = trim($this->findCsvMatch($row, 'eula_text')); + $this->item['category_type'] = trim(strtolower($this->findCsvMatch($row, 'category_type'))); + $this->item['use_default_eula'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'use_default_eula'))) == 1) ? 1 : 0; + $this->item['require_acceptance'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'require_acceptance'))) == 1) ? 1 : 0; + $this->item['checkin_email'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'checkin_email'))) == 1) ? 1 : 0; + + + Log::debug('Item array is: '); + Log::debug(print_r($this->item, true)); + + + if ($editingCategory) { + Log::debug('Updating existing category'); + $category->update($this->sanitizeItemForUpdating($category)); + } else { + Log::debug('Creating category'); + $category->fill($this->sanitizeItemForStoring($category)); + } + + if ($category->save()) { + $this->log('Category '.$category->name.' created or updated from CSV import'); + return $category; + + } else { + Log::debug($category->getErrors()); + $this->logError($category, 'Category "'.$this->item['name'].'"'); + return $category->errors; + } + + + } +} \ No newline at end of file diff --git a/app/Importer/ComponentImporter.php b/app/Importer/ComponentImporter.php index f72d4cbfd7..3979ba499d 100644 --- a/app/Importer/ComponentImporter.php +++ b/app/Importer/ComponentImporter.php @@ -47,6 +47,7 @@ class ComponentImporter extends ItemImporter } $this->log('No matching component, creating one'); $component = new Component; + $component->created_by = auth()->id(); $component->fill($this->sanitizeItemForStoring($component)); // This sets an attribute on the Loggable trait for the action log @@ -58,7 +59,7 @@ class ComponentImporter extends ItemImporter if (isset($this->item['asset_tag']) && ($asset = Asset::where('asset_tag', $this->item['asset_tag'])->first())) { $component->assets()->attach($component->id, [ 'component_id' => $component->id, - 'user_id' => $this->user_id, + 'created_by' => auth()->id(), 'created_at' => date('Y-m-d H:i:s'), 'assigned_qty' => 1, // Only assign the first one to the asset 'asset_id' => $asset->id, diff --git a/app/Importer/ConsumableImporter.php b/app/Importer/ConsumableImporter.php index 9e7019b086..40c597e896 100644 --- a/app/Importer/ConsumableImporter.php +++ b/app/Importer/ConsumableImporter.php @@ -28,9 +28,9 @@ class ConsumableImporter extends ItemImporter { $consumable = Consumable::where('name', trim($this->item['name']))->first(); if ($consumable) { + if (! $this->updating) { $this->log('A matching Consumable '.$this->item['name'].' already exists. '); - return; } $this->log('Updating Consumable'); @@ -39,11 +39,10 @@ class ConsumableImporter extends ItemImporter return; } + $this->log('No matching consumable, creating one'); $consumable = new Consumable(); - $this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number')); - $this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number')); - $this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt")); + $consumable->created_by = auth()->id(); $consumable->fill($this->sanitizeItemForStoring($consumable)); // This sets an attribute on the Loggable trait for the action log diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index 9738723509..0d4b8d4932 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -21,8 +21,7 @@ abstract class Importer * Id of User performing import * @var */ - - protected $user_id; + protected $created_by; /** * Are we updating items in the import * @var bool @@ -40,6 +39,7 @@ abstract class Importer * @var array */ private $defaultFieldMap = [ + 'id' => 'id', 'asset_tag' => 'asset tag', 'activated' => 'activated', 'category' => 'category', @@ -149,21 +149,28 @@ abstract class Importer { $headerRow = $this->csv->fetchOne(); $this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record - $results = $this->normalizeInputArray($this->csv->getRecords($headerRow)); $this->populateCustomFields($headerRow); - DB::transaction(function () use (&$results) { + DB::transaction(function () use ($headerRow) { + $importedItemsCount = 0; Model::unguard(); - $resultsCount = count($results); - foreach ($results as $row) { + + foreach ($this->csv->getRecords($headerRow) as $row) { + //Lowercase header values to ensure we're comparing values properly. + $row = array_change_key_case($row, CASE_LOWER); + $this->handle($row); + + $importedItemsCount++; + if ($this->progressCallback) { - call_user_func($this->progressCallback, $resultsCount); + call_user_func($this->progressCallback, $importedItemsCount); } $this->log('------------- Action Summary ----------------'); } + Model::reguard(); }); } @@ -236,22 +243,6 @@ abstract class Importer return $key; } - /** - * Used to lowercase header values to ensure we're comparing values properly. - * - * @param $results - * @return array - */ - public function normalizeInputArray($results) - { - $newArray = []; - foreach ($results as $index => $arrayToNormalize) { - $newArray[$index] = array_change_key_case($arrayToNormalize); - } - - return $newArray; - } - /** * Figure out the fieldname of the custom field * @@ -281,6 +272,13 @@ abstract class Importer } } + protected function addErrorToBag($item, $field, $error_message) + { + if ($this->errorCallback) { + call_user_func($this->errorCallback, $item, $field, [$field => [$error_message]]); + } + } + /** * Finds the user matching given data, or creates a new one if there is no match. * This is NOT used by the User Import, only for Asset/Accessory/etc where @@ -366,6 +364,7 @@ abstract class Importer // No luck finding a user on username or first name, let's create one. $user = new User; + $user->first_name = $user_array['first_name']; $user->last_name = $user_array['last_name']; $user->username = $user_array['username']; @@ -388,7 +387,7 @@ abstract class Importer } /** - * Matches a user by user_id if user_name provided is a number + * Matches a user by created_by if user_name provided is a number * @param string $user_name users full name from csv * @return User User Matching ID */ @@ -405,13 +404,13 @@ abstract class Importer /** * Sets the Id of User performing import. * - * @param mixed $user_id the user id + * @param mixed $created_by the user id * * @return self */ - public function setUserId($user_id) + public function setCreatedBy($created_by) { - $this->user_id = $user_id; + $this->created_by = $created_by; return $this; } @@ -495,6 +494,16 @@ abstract class Importer public function fetchHumanBoolean($value) { + $true = [ + 'yes', + 'y', + 'true', + ]; + + if (in_array(strtolower($value), $true)) { + return 1; + } + return (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); } @@ -531,6 +540,7 @@ abstract class Importer return null; } + /** * Fetch an existing manager * diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php index ee680413d8..9283e79ab9 100644 --- a/app/Importer/ItemImporter.php +++ b/app/Importer/ItemImporter.php @@ -24,6 +24,12 @@ class ItemImporter extends Importer protected function handle($row) { + + /** + * This section adds the most common fields into the $item array so we don't have to manually add them to + * things like accessories, consumables, etc. + */ + // Need to reset this between iterations or we'll have stale data. $this->item = []; @@ -73,29 +79,20 @@ class ItemImporter extends Importer $this->item['notes'] = $this->findCsvMatch($row, 'notes'); $this->item['order_number'] = $this->findCsvMatch($row, 'order_number'); $this->item['purchase_cost'] = $this->findCsvMatch($row, 'purchase_cost'); + $this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number')); + $this->item['min_amt'] = $this->findCsvMatch($row, 'min_amt'); + $this->item['qty'] = $this->findCsvMatch($row, 'quantity'); + $this->item['requestable'] = $this->findCsvMatch($row, 'requestable'); + $this->item['created_by'] = auth()->id(); + $this->item['serial'] = $this->findCsvMatch($row, 'serial'); + $this->item['item_no'] = trim($this->findCsvMatch($row, 'item_no')); + $this->item['purchase_date'] = null; if ($this->findCsvMatch($row, 'purchase_date') != '') { $this->item['purchase_date'] = date('Y-m-d', strtotime($this->findCsvMatch($row, 'purchase_date'))); } -// $this->item['asset_eol_date'] = null; -// if ($this->findCsvMatch($row, 'asset_eol_date') != '') { -// $csvMatch = $this->findCsvMatch($row, 'asset_eol_date'); -// \Log::warning('EOL Date for $csvMatch is '.$csvMatch); -// try { -// $this->item['asset_eol_date'] = CarbonImmutable::parse($csvMatch)->format('Y-m-d'); -// } catch (\Exception $e) { -// Log::info($e->getMessage()); -// $this->log('Unable to parse date: '.$csvMatch); -// } -// } - - - $this->item['qty'] = $this->findCsvMatch($row, 'quantity'); - $this->item['requestable'] = $this->findCsvMatch($row, 'requestable'); - $this->item['user_id'] = $this->user_id; - $this->item['serial'] = $this->findCsvMatch($row, 'serial'); // NO need to call this method if we're running the user import. // TODO: Merge these methods. $this->item['checkout_class'] = $this->findCsvMatch($row, 'checkout_class'); @@ -113,7 +110,7 @@ class ItemImporter extends Importer protected function determineCheckout($row) { // Locations don't get checked out to anyone/anything - if (get_class($this) == LocationImporter::class) { + if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class) || (get_class($this) == SupplierImporter::class) || (get_class($this) == ManufacturerImporter::class) || (get_class($this) == CategoryImporter::class)) { return; } @@ -196,55 +193,65 @@ class ItemImporter extends Importer { $condition = array(); $asset_model_name = $this->findCsvMatch($row, 'asset_model'); + $asset_model_category = $this->findCsvMatch($row, 'category'); $asset_modelNumber = $this->findCsvMatch($row, 'model_number'); + // TODO: At the moment, this means we can't update the model number if the model name stays the same. if (! $this->shouldUpdateField($asset_model_name)) { return; } + if ((empty($asset_model_name)) && (! empty($asset_modelNumber))) { $asset_model_name = $asset_modelNumber; } elseif ((empty($asset_model_name)) && (empty($asset_modelNumber))) { $asset_model_name = 'Unknown'; } - if ((!empty($asset_model_name)) && (empty($asset_modelNumber))) { - $condition[] = ['name', '=', $asset_model_name]; - } elseif ((!empty($asset_model_name)) && (!empty($asset_modelNumber))) { - $condition[] = ['name', '=', $asset_model_name]; - $condition[] = ['model_number', '=', $asset_modelNumber]; + $asset_model = AssetModel::select('id'); + + if (!empty($asset_model_name)) { + $asset_model = $asset_model->where('name', '=', $asset_model_name); + + if (!empty($asset_modelNumber)) { + $asset_model = $asset_model->where('model_number', '=', $asset_modelNumber); + } } $editingModel = $this->updating; - $asset_model = AssetModel::where($condition)->first(); + $asset_model = $asset_model->first(); if ($asset_model) { + if (! $this->updating) { $this->log('A matching model already exists, returning it.'); - return $asset_model->id; } + $this->log('Matching Model found, updating it.'); $item = $this->sanitizeItemForStoring($asset_model, $editingModel); $item['name'] = $asset_model_name; $item['notes'] = $this->findCsvMatch($row, 'model_notes'); - if(!empty($asset_modelNumber)){ + if (!empty($asset_modelNumber)){ $item['model_number'] = $asset_modelNumber; } $asset_model->update($item); $asset_model->save(); $this->log('Asset Model Updated'); - + return $asset_model->id; - } - $this->log('No Matching Model, Creating a new one'); + } + + $this->log('No Matching Model, Creating a new one'); $asset_model = new AssetModel(); + $asset_model->created_by = auth()->id(); $item = $this->sanitizeItemForStoring($asset_model, $editingModel); $item['name'] = $asset_model_name; $item['model_number'] = $asset_modelNumber; $item['notes'] = $this->findCsvMatch($row, 'model_notes'); + $item['category_id'] = $this->createOrFetchCategory($asset_model_category); $asset_model->fill($item); $item = null; @@ -254,6 +261,7 @@ class ItemImporter extends Importer return $asset_model->id; } + $this->log('Asset Model Errors: '.$asset_model->getErrors()); $this->logError($asset_model, 'Asset Model "'.$asset_model_name.'"'); return null; @@ -274,21 +282,28 @@ class ItemImporter extends Importer $classname = class_basename(get_class($this)); $item_type = strtolower(substr($classname, 0, strpos($classname, 'Importer'))); + // If we're importing asset models only (without attached assets), override the category type to asset + if ($item_type == 'assetmodel') { + $item_type = 'asset'; + } + if (empty($asset_category)) { $asset_category = 'Unnamed Category'; } + + $category = Category::where(['name' => $asset_category, 'category_type' => $item_type])->first(); - if ($category) { - $this->log('A matching category: '.$asset_category.' already exists'); + if ($category) { + $this->log('A matching category: '.$category->name.' already exists'); return $category->id; } $category = new Category(); + $category->created_by = auth()->id(); $category->name = $asset_category; $category->category_type = $item_type; - $category->user_id = $this->user_id; if ($category->save()) { $this->log('Category '.$asset_category.' was created'); @@ -317,6 +332,7 @@ class ItemImporter extends Importer return $company->id; } $company = new Company(); + $company->created_by = auth()->id(); $company->name = $asset_company_name; if ($company->save()) { @@ -373,6 +389,7 @@ class ItemImporter extends Importer } $this->log('Creating a new status'); $status = new Statuslabel(); + $status->created_by = auth()->id(); $status->name = trim($asset_statuslabel_name); $status->deployable = 1; @@ -412,7 +429,7 @@ class ItemImporter extends Importer //Otherwise create a manufacturer. $manufacturer = new Manufacturer(); $manufacturer->name = trim($item_manufacturer); - $manufacturer->user_id = $this->user_id; + $manufacturer->created_by = auth()->id(); if ($manufacturer->save()) { $this->log('Manufacturer '.$manufacturer->name.' was created'); @@ -436,14 +453,13 @@ class ItemImporter extends Importer { if (empty($asset_location)) { $this->log('No location given, so none created.'); - return null; } + $location = Location::where(['name' => $asset_location])->first(); if ($location) { $this->log('Location '.$asset_location.' already exists'); - return $location->id; } // No matching locations in the collection, create a new one. @@ -453,7 +469,7 @@ class ItemImporter extends Importer $location->city = ''; $location->state = ''; $location->country = ''; - $location->user_id = $this->user_id; + $location->created_by = auth()->id(); if ($location->save()) { $this->log('Location '.$asset_location.' was created'); @@ -489,11 +505,10 @@ class ItemImporter extends Importer $supplier = new Supplier(); $supplier->name = $item_supplier; - $supplier->user_id = $this->user_id; + $supplier->created_by = auth()->id(); if ($supplier->save()) { $this->log('Supplier '.$item_supplier.' was created'); - return $supplier->id; } $this->logError($supplier, 'Supplier'); diff --git a/app/Importer/LicenseImporter.php b/app/Importer/LicenseImporter.php index b7c55cdba6..d5df4aba3c 100644 --- a/app/Importer/LicenseImporter.php +++ b/app/Importer/LicenseImporter.php @@ -68,6 +68,7 @@ class LicenseImporter extends ItemImporter $this->item['order_number'] = trim($this->findCsvMatch($row, 'order_number')); $this->item['reassignable'] = trim($this->findCsvMatch($row, 'reassignable')); $this->item['manufacturer'] = $this->createOrFetchManufacturer(trim($this->findCsvMatch($row, 'manufacturer'))); + $this->item['min_amt'] = trim($this->findCsvMatch($row, 'min_amt')); if($this->item['reassignable'] == "") { @@ -84,6 +85,7 @@ class LicenseImporter extends ItemImporter $license->update($this->sanitizeItemForUpdating($license)); } else { $license->fill($this->sanitizeItemForStoring($license)); + $license->created_by = auth()->id(); } // This sets an attribute on the Loggable trait for the action log @@ -103,13 +105,13 @@ class LicenseImporter extends ItemImporter if ($checkout_target) { $targetLicense->assigned_to = $checkout_target->id; - $targetLicense->user_id = Auth::id(); + $targetLicense->created_by = auth()->id(); if ($asset) { $targetLicense->asset_id = $asset->id; } $targetLicense->save(); } elseif ($asset) { - $targetLicense->user_id = Auth::id(); + $targetLicense->created_by = auth()->id(); $targetLicense->asset_id = $asset->id; $targetLicense->save(); } diff --git a/app/Importer/LocationImporter.php b/app/Importer/LocationImporter.php index 3b1b65b08f..1fcef6625d 100644 --- a/app/Importer/LocationImporter.php +++ b/app/Importer/LocationImporter.php @@ -38,8 +38,16 @@ class LocationImporter extends ItemImporter { $editingLocation = false; + $location = Location::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + if ($this->findCsvMatch($row, 'id')!='') { + // Override location if an ID was given + \Log::debug('Finding location by ID: '.$this->findCsvMatch($row, 'id')); + $location = Location::find($this->findCsvMatch($row, 'id')); + } + + if ($location) { if (! $this->updating) { $this->log('A matching Location '.$this->item['name'].' already exists'); @@ -51,6 +59,7 @@ class LocationImporter extends ItemImporter } else { $this->log('No Matching Location, Create a new one'); $location = new Location; + $location->created_by = auth()->id(); } // Pull the records from the CSV to determine their values @@ -65,7 +74,8 @@ class LocationImporter extends ItemImporter $this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou')); $this->item['manager'] = trim($this->findCsvMatch($row, 'manager')); $this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username')); - $this->item['user_id'] = \Auth::user()->id; + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + if ($this->findCsvMatch($row, 'parent_location')) { $this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location'))); @@ -95,6 +105,7 @@ class LocationImporter extends ItemImporter } else { Log::debug($location->getErrors()); + $this->logError($location, 'Location "'.$this->item['name'].'"'); return $location->errors; } diff --git a/app/Importer/ManufacturerImporter.php b/app/Importer/ManufacturerImporter.php new file mode 100644 index 0000000000..5272bf698a --- /dev/null +++ b/app/Importer/ManufacturerImporter.php @@ -0,0 +1,101 @@ +createManufacturerIfNotExists($row); + } + + /** + * Create a supplier if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createManufacturerIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createManufacturerIfNotExists(array $row) + { + + $editingManufacturer = false; + + $supplier = Manufacturer::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($this->findCsvMatch($row, 'id')!='') { + // Override supplier if an ID was given + \Log::debug('Finding supplier by ID: '.$this->findCsvMatch($row, 'id')); + $supplier = Manufacturer::find($this->findCsvMatch($row, 'id')); + } + + + if ($supplier) { + if (! $this->updating) { + $this->log('A matching Manufacturer '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Manufacturer'); + $editingManufacturer = true; + } else { + $this->log('No Matching Manufacturer, Create a new one'); + $supplier = new Manufacturer; + $supplier->created_by = auth()->id(); + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['support_phone'] = trim($this->findCsvMatch($row, 'support_phone')); + $this->item['fax'] = trim($this->findCsvMatch($row, 'fax')); + $this->item['support_email'] = trim($this->findCsvMatch($row, 'support_email')); + $this->item['contact'] = trim($this->findCsvMatch($row, 'contact')); + $this->item['url'] = trim($this->findCsvMatch($row, 'url')); + $this->item['support_url'] = trim($this->findCsvMatch($row, 'support_url')); + $this->item['warranty_lookup_url'] = trim($this->findCsvMatch($row, 'warranty_lookup_url')); + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + + + Log::debug('Item array is: '); + Log::debug(print_r($this->item, true)); + + + if ($editingManufacturer) { + Log::debug('Updating existing supplier'); + $supplier->update($this->sanitizeItemForUpdating($supplier)); + } else { + Log::debug('Creating supplier'); + $supplier->fill($this->sanitizeItemForStoring($supplier)); + } + + if ($supplier->save()) { + $this->log('Manufacturer '.$supplier->name.' created or updated from CSV import'); + return $supplier; + + } else { + Log::debug($supplier->getErrors()); + $this->logError($supplier, 'Manufacturer "'.$this->item['name'].'"'); + return $supplier->errors; + } + + + } +} \ No newline at end of file diff --git a/app/Importer/SupplierImporter.php b/app/Importer/SupplierImporter.php new file mode 100644 index 0000000000..7878aff835 --- /dev/null +++ b/app/Importer/SupplierImporter.php @@ -0,0 +1,105 @@ +createSupplierIfNotExists($row); + } + + /** + * Create a supplier if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createSupplierIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createSupplierIfNotExists(array $row) + { + + $editingSupplier = false; + + $supplier = Supplier::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($this->findCsvMatch($row, 'id')!='') { + // Override supplier if an ID was given + \Log::debug('Finding supplier by ID: '.$this->findCsvMatch($row, 'id')); + $supplier = Supplier::find($this->findCsvMatch($row, 'id')); + } + + + if ($supplier) { + if (! $this->updating) { + $this->log('A matching Supplier '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Supplier'); + $editingSupplier = true; + } else { + $this->log('No Matching Supplier, Create a new one'); + $supplier = new Supplier; + $supplier->created_by = auth()->id(); + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['address'] = trim($this->findCsvMatch($row, 'address')); + $this->item['address2'] = trim($this->findCsvMatch($row, 'address2')); + $this->item['city'] = trim($this->findCsvMatch($row, 'city')); + $this->item['state'] = trim($this->findCsvMatch($row, 'state')); + $this->item['country'] = trim($this->findCsvMatch($row, 'country')); + $this->item['zip'] = trim($this->findCsvMatch($row, 'zip')); + $this->item['phone'] = trim($this->findCsvMatch($row, 'phone')); + $this->item['fax'] = trim($this->findCsvMatch($row, 'fax')); + $this->item['email'] = trim($this->findCsvMatch($row, 'email')); + $this->item['contact'] = trim($this->findCsvMatch($row, 'contact')); + $this->item['url'] = trim($this->findCsvMatch($row, 'url')); + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + + + Log::debug('Item array is: '); + Log::debug(print_r($this->item, true)); + + + if ($editingSupplier) { + Log::debug('Updating existing supplier'); + $supplier->update($this->sanitizeItemForUpdating($supplier)); + } else { + Log::debug('Creating supplier'); + $supplier->fill($this->sanitizeItemForStoring($supplier)); + } + + if ($supplier->save()) { + $this->log('Supplier '.$supplier->name.' created or updated from CSV import'); + return $supplier; + + } else { + Log::debug($supplier->getErrors()); + $this->logError($supplier, 'Supplier "'.$this->item['name'].'"'); + return $supplier->errors; + } + + + } +} \ No newline at end of file diff --git a/app/Importer/UserImporter.php b/app/Importer/UserImporter.php index 2cb6abd659..77317b3d09 100644 --- a/app/Importer/UserImporter.php +++ b/app/Importer/UserImporter.php @@ -67,6 +67,7 @@ class UserImporter extends ItemImporter $this->item['vip'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'vip'))) ==1 ) ? '1' : 0; $this->item['autoassign_licenses'] = ($this->fetchHumanBoolean(trim($this->findCsvMatch($row, 'autoassign_licenses'))) ==1 ) ? '1' : 0; + $this->handleEmptyStringsForDates(); $user_department = trim($this->findCsvMatch($row, 'department')); if ($this->shouldUpdateField($user_department)) { @@ -113,6 +114,7 @@ class UserImporter extends ItemImporter $this->log('No matching user, creating one'); $user = new User(); + $user->created_by = auth()->id(); $user->fill($this->sanitizeItemForStoring($user)); if ($user->save()) { @@ -164,7 +166,7 @@ class UserImporter extends ItemImporter $department = new department(); $department->name = $department_name; - $department->user_id = $this->user_id; + $department->created_by = $this->created_by; if ($department->save()) { $this->log('department ' . $department_name . ' was created'); @@ -179,4 +181,22 @@ class UserImporter extends ItemImporter { $this->send_welcome = $send; } + + /** + * Since the findCsvMatch() method will set '' for columns that are present but empty, + * we need to set those empty strings to null to avoid passing bad data to the database + * (ie ending up with 0000-00-00 instead of the intended null). + * + * @return void + */ + private function handleEmptyStringsForDates(): void + { + if ($this->item['start_date'] === '') { + $this->item['start_date'] = null; + } + + if ($this->item['end_date'] === '') { + $this->item['end_date'] = null; + } + } } diff --git a/app/LegacyEncrypter/BaseEncrypter.php b/app/LegacyEncrypter/BaseEncrypter.php deleted file mode 100644 index adc1e451d4..0000000000 --- a/app/LegacyEncrypter/BaseEncrypter.php +++ /dev/null @@ -1,81 +0,0 @@ -key); - } - - /** - * Get the JSON array from the given payload. - * - * @param string $payload - * @return array - * - * @throws \Illuminate\Contracts\Encryption\DecryptException - */ - protected function getJsonPayload($payload) - { - $payload = json_decode(base64_decode($payload), true); - - // If the payload is not valid JSON or does not have the proper keys set we will - // assume it is invalid and bail out of the routine since we will not be able - // to decrypt the given value. We'll also check the MAC for this encryption. - if (! $payload || $this->invalidPayload($payload)) { - throw new DecryptException('The payload is invalid.'); - } - - if (! $this->validMac($payload)) { - throw new DecryptException('The MAC is invalid.'); - } - - return $payload; - } - - /** - * Verify that the encryption payload is valid. - * - * @param array|mixed $data - * @return bool - */ - protected function invalidPayload($data) - { - return ! is_array($data) || ! isset($data['iv']) || ! isset($data['value']) || ! isset($data['mac']); - } - - /** - * Determine if the MAC for the given payload is valid. - * - * @param array $payload - * @return bool - * - * @throws \RuntimeException - */ - protected function validMac(array $payload) - { - $bytes = random_bytes(16); - - $calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true); - - return hash_equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac); - } -} diff --git a/app/LegacyEncrypter/McryptEncrypter.php b/app/LegacyEncrypter/McryptEncrypter.php deleted file mode 100644 index ac0a49fc58..0000000000 --- a/app/LegacyEncrypter/McryptEncrypter.php +++ /dev/null @@ -1,214 +0,0 @@ -key = $key; - $this->cipher = $cipher; - $this->block = mcrypt_get_iv_size($this->cipher, MCRYPT_MODE_CBC); - } else { - throw new RuntimeException('The only supported ciphers are MCRYPT_RIJNDAEL_128 and MCRYPT_RIJNDAEL_256.'); - } - } - - /** - * Determine if the given key and cipher combination is valid. - * - * @param string $key - * @param string $cipher - * @return bool - */ - public static function supported($key, $cipher) - { - return defined('MCRYPT_RIJNDAEL_128') && - ($cipher === MCRYPT_RIJNDAEL_128 || $cipher === MCRYPT_RIJNDAEL_256); - } - - /** - * Encrypt the given value. - * - * @param string $value - * @return string - * - * @throws \Illuminate\Contracts\Encryption\EncryptException - */ - public function encrypt($value, $serialize = true) - { - $iv = mcrypt_create_iv($this->getIvSize(), $this->getRandomizer()); - - $value = base64_encode($this->padAndMcrypt($value, $iv)); - - // Once we have the encrypted value we will go ahead base64_encode the input - // vector and create the MAC for the encrypted value so we can verify its - // authenticity. Then, we'll JSON encode the data in a "payload" array. - $mac = $this->hash($iv = base64_encode($iv), $value); - - $json = json_encode(compact('iv', 'value', 'mac')); - - if (! is_string($json)) { - throw new EncryptException('Could not encrypt the data.'); - } - - return base64_encode($json); - } - - /** - * Pad and use mcrypt on the given value and input vector. - * - * @param string $value - * @param string $iv - * @return string - */ - protected function padAndMcrypt($value, $iv) - { - $value = $this->addPadding(serialize($value)); - - return mcrypt_encrypt($this->cipher, $this->key, $value, MCRYPT_MODE_CBC, $iv); - } - - /** - * Decrypt the given value. - * - * @param string $payload - * @return string - */ - public function decrypt($payload, $unserialize = true) - { - $payload = $this->getJsonPayload($payload); - - // We'll go ahead and remove the PKCS7 padding from the encrypted value before - // we decrypt it. Once we have the de-padded value, we will grab the vector - // and decrypt the data, passing back the unserialized from of the value. - $value = base64_decode($payload['value']); - - $iv = base64_decode($payload['iv']); - - return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv))); - } - - /** - * Run the mcrypt decryption routine for the value. - * - * @param string $value - * @param string $iv - * @return string - * - * @throws \Illuminate\Contracts\Encryption\DecryptException - */ - protected function mcryptDecrypt($value, $iv) - { - try { - return mcrypt_decrypt($this->cipher, $this->key, $value, MCRYPT_MODE_CBC, $iv); - } catch (Exception $e) { - throw new DecryptException($e->getMessage()); - } - } - - /** - * Add PKCS7 padding to a given value. - * - * @param string $value - * @return string - */ - protected function addPadding($value) - { - $pad = $this->block - (strlen($value) % $this->block); - - return $value.str_repeat(chr($pad), $pad); - } - - /** - * Remove the padding from the given value. - * - * @param string $value - * @return string - */ - protected function stripPadding($value) - { - $pad = ord($value[($len = strlen($value)) - 1]); - - return $this->paddingIsValid($pad, $value) ? substr($value, 0, $len - $pad) : $value; - } - - /** - * Determine if the given padding for a value is valid. - * - * @param string $pad - * @param string $value - * @return bool - */ - protected function paddingIsValid($pad, $value) - { - $beforePad = strlen($value) - $pad; - - return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad); - } - - /** - * Get the IV size for the cipher. - * - * @return int - */ - protected function getIvSize() - { - return mcrypt_get_iv_size($this->cipher, MCRYPT_MODE_CBC); - } - - /** - * Get the random data source available for the OS. - * - * @return int - */ - protected function getRandomizer() - { - if (defined('MCRYPT_DEV_URANDOM')) { - return MCRYPT_DEV_URANDOM; - } - - if (defined('MCRYPT_DEV_RANDOM')) { - return MCRYPT_DEV_RANDOM; - } - - mt_srand(); - - return MCRYPT_RAND; - } -} diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php index 4bc367f025..ec843b980b 100644 --- a/app/Listeners/CheckoutableListener.php +++ b/app/Listeners/CheckoutableListener.php @@ -3,14 +3,22 @@ namespace App\Listeners; use App\Events\CheckoutableCheckedOut; +use App\Mail\CheckinAccessoryMail; +use App\Mail\CheckinLicenseMail; +use App\Mail\CheckoutAccessoryMail; +use App\Mail\CheckoutAssetMail; +use App\Mail\CheckinAssetMail; +use App\Mail\CheckoutConsumableMail; +use App\Mail\CheckoutLicenseMail; use App\Models\Accessory; use App\Models\Asset; use App\Models\CheckoutAcceptance; use App\Models\Component; use App\Models\Consumable; use App\Models\LicenseSeat; -use App\Models\Recipients\AdminRecipient; +use App\Models\Location; use App\Models\Setting; +use App\Models\User; use App\Notifications\CheckinAccessoryNotification; use App\Notifications\CheckinAssetNotification; use App\Notifications\CheckinLicenseSeatNotification; @@ -19,9 +27,13 @@ use App\Notifications\CheckoutAssetNotification; use App\Notifications\CheckoutConsumableNotification; use App\Notifications\CheckoutLicenseSeatNotification; use GuzzleHttp\Exception\ClientException; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Notification; use Exception; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; +use Osama\LaravelTeamsNotification\TeamsNotification; class CheckoutableListener { @@ -29,55 +41,98 @@ class CheckoutableListener Component::class, ]; + /** + * Register the listeners for the subscriber. + * + * @param Illuminate\Events\Dispatcher $events + */ + public function subscribe($events) + { + $events->listen( + \App\Events\CheckoutableCheckedIn::class, + 'App\Listeners\CheckoutableListener@onCheckedIn' + ); + + $events->listen( + \App\Events\CheckoutableCheckedOut::class, + 'App\Listeners\CheckoutableListener@onCheckedOut' + ); + } + /** * Notify the user and post to webhook about the checked out checkoutable * and add a record to the checkout_requests table. */ public function onCheckedOut($event) { - if ($this->shouldNotSendAnyNotifications($event->checkoutable)){ + if ($this->shouldNotSendAnyNotifications($event->checkoutable)) { return; } - /** - * Make a checkout acceptance and attach it in the notification - */ $acceptance = $this->getCheckoutAcceptance($event); - try { - if (! $event->checkedOutTo->locale) { - Notification::locale(Setting::getSettings()->locale)->send( - $this->getNotifiables($event), - $this->getCheckoutNotification($event, $acceptance) - ); - } else { - Notification::send( - $this->getNotifiables($event), - $this->getCheckoutNotification($event, $acceptance) - ); + $shouldSendEmailToUser = $this->shouldSendCheckoutEmailToUser($event->checkoutable); + $shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress($acceptance); + $shouldSendWebhookNotification = $this->shouldSendWebhookNotification(); + + if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) { + return; + } + + if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) { + $mailable = $this->getCheckoutMailType($event, $acceptance); + $notifiable = $this->getNotifiableUser($event); + + $notifiableHasEmail = $notifiable instanceof User && $notifiable->email; + + $shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail; + + [$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable); + + if (!empty($to)) { + try { + Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable); + Log::info('Checkout Mail sent to checkout target'); + } catch (ClientException $e) { + Log::debug("Exception caught during checkout email: " . $e->getMessage()); + } catch (Exception $e) { + Log::debug("Exception caught during checkout email: " . $e->getMessage()); + } } + } - if ($this->shouldSendWebhookNotification()) { - - //slack doesn't include the url in its messaging format so this is needed to hit the endpoint - - if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') { - - - Notification::route('slack', Setting::getSettings()->webhook_endpoint) - ->notify($this->getCheckoutNotification($event)); - } + if ($shouldSendWebhookNotification) { + try { + if ($this->newMicrosoftTeamsWebhookEnabled()) { + $message = $this->getCheckoutNotification($event)->toMicrosoftTeams(); + $notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint); + $notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams + } else { + Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint) + ->notify($this->getCheckoutNotification($event, $acceptance)); + } + } catch (ClientException $e) { + if (strpos($e->getMessage(), 'channel_not_found') !== false) { + Log::warning(Setting::getSettings()->webhook_selected . " notification failed: " . $e->getMessage()); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_channel_not_found')); + } else { + Log::error("ClientException caught during checkin notification: " . $e->getMessage()); + } + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); + } catch (Exception $e) { + Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [ + 'error' => $e->getMessage(), + 'webhook_endpoint' => Setting::getSettings()->webhook_endpoint, + 'event' => $event, + ]); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); } - } catch (ClientException $e) { - Log::warning("Exception caught during checkout notification: " . $e->getMessage()); - } catch (Exception $e) { - Log::warning("Exception caught during checkout notification: " . $e->getMessage()); } } /** * Notify the user and post to webhook about the checked in checkoutable - */ + */ public function onCheckedIn($event) { Log::debug('onCheckedIn in the Checkoutable listener fired'); @@ -86,49 +141,80 @@ class CheckoutableListener return; } - /** - * Send the appropriate notification - */ - if ($event->checkedOutTo && $event->checkoutable){ - $acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id) - ->where('assigned_to_id', $event->checkedOutTo->id) - ->get(); + $shouldSendEmailToUser = $this->checkoutableCategoryShouldSendEmail($event->checkoutable); + $shouldSendEmailToAlertAddress = $this->shouldSendEmailToAlertAddress(); + $shouldSendWebhookNotification = $this->shouldSendWebhookNotification(); - foreach($acceptances as $acceptance){ - if($acceptance->isPending()){ - $acceptance->delete(); + if (!$shouldSendEmailToUser && !$shouldSendEmailToAlertAddress && !$shouldSendWebhookNotification) { + return; + } + + if ($shouldSendEmailToUser || $shouldSendEmailToAlertAddress) { + /** + * Send the appropriate notification + */ + if ($event->checkedOutTo && $event->checkoutable) { + $acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id) + ->where('assigned_to_id', $event->checkedOutTo->id) + ->get(); + + foreach ($acceptances as $acceptance) { + if ($acceptance->isPending()) { + $acceptance->delete(); + } } } + + $mailable = $this->getCheckinMailType($event); + $notifiable = $this->getNotifiableUser($event); + + $notifiableHasEmail = $notifiable instanceof User && $notifiable->email; + + $shouldSendEmailToUser = $shouldSendEmailToUser && $notifiableHasEmail; + + [$to, $cc] = $this->generateEmailRecipients($shouldSendEmailToUser, $shouldSendEmailToAlertAddress, $notifiable); + + try { + if (!empty($to)) { + Mail::to(array_flatten($to))->cc(array_flatten($cc))->send($mailable); + Log::info('Checkin Mail sent to CC addresses'); + } + } catch (ClientException $e) { + Log::debug("Exception caught during checkin email: " . $e->getMessage()); + } catch (Exception $e) { + Log::debug("Exception caught during checkin email: " . $e->getMessage()); + } } - try { - // Use default locale - if (! $event->checkedOutTo->locale) { - Notification::locale(Setting::getSettings()->locale)->send( - $this->getNotifiables($event), - $this->getCheckinNotification($event) - ); - } else { - Notification::send( - $this->getNotifiables($event), - $this->getCheckinNotification($event) - ); - } - //slack doesn't include the url in its messaging format so this is needed to hit the endpoint - if(Setting::getSettings()->webhook_selected =='slack' || Setting::getSettings()->webhook_selected =='general') { - - if ($this->shouldSendWebhookNotification()) { - Notification::route('slack', Setting::getSettings()->webhook_endpoint) + if ($shouldSendWebhookNotification) { + // Send Webhook notification + try { + if ($this->newMicrosoftTeamsWebhookEnabled()) { + $message = $this->getCheckinNotification($event)->toMicrosoftTeams(); + $notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint); + $notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams + } else { + Notification::route($this->webhookSelected(), Setting::getSettings()->webhook_endpoint) ->notify($this->getCheckinNotification($event)); } + } catch (ClientException $e) { + if (strpos($e->getMessage(), 'channel_not_found') !== false) { + Log::warning(Setting::getSettings()->webhook_selected . " notification failed: " . $e->getMessage()); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_channel_not_found')); + } else { + Log::error("ClientException caught during checkin notification: " . $e->getMessage()); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); + } + } catch (Exception $e) { + Log::warning(ucfirst(Setting::getSettings()->webhook_selected) . ' webhook notification failed:', [ + 'error' => $e->getMessage(), + 'webhook_endpoint' => Setting::getSettings()->webhook_endpoint, + 'event' => $event, + ]); + return redirect()->back()->with('warning', ucfirst(Setting::getSettings()->webhook_selected) . trans('admin/settings/message.webhook.webhook_fail')); } - - } catch (ClientException $e) { - Log::warning("Exception caught during checkout notification: " . $e->getMessage()); - } catch (Exception $e) { - Log::warning("Exception caught during checkin notification: " . $e->getMessage()); } - } + } /** * Generates a checkout acceptance @@ -137,7 +223,11 @@ class CheckoutableListener */ private function getCheckoutAcceptance($event) { - if (! $event->checkoutable->requireAcceptance()) { + $checkedOutToType = get_class($event->checkedOutTo); + if ($checkedOutToType != "App\Models\User") { + return null; + } + if (!$event->checkoutable->requireAcceptance()) { return null; } @@ -146,40 +236,13 @@ class CheckoutableListener $acceptance->assignedTo()->associate($event->checkedOutTo); $acceptance->save(); - return $acceptance; - } - - /** - * Gets the entities to be notified of the passed event - * - * @param Event $event - * @return Collection - */ - private function getNotifiables($event) - { - $notifiables = collect(); - - /** - * Notify who checked out the item as long as the model can route notifications - */ - if (method_exists($event->checkedOutTo, 'routeNotificationFor')) { - $notifiables->push($event->checkedOutTo); - } - - /** - * Notify Admin users if the settings is activated - */ - if ((Setting::getSettings()) && (Setting::getSettings()->admin_cc_email != '')) { - $notifiables->push(new AdminRecipient()); - } - - return $notifiables; + return $acceptance; } /** * Get the appropriate notification for the event - * - * @param CheckoutableCheckedIn $event + * + * @param CheckoutableCheckedIn $event * @return Notification */ private function getCheckinNotification($event) @@ -193,7 +256,7 @@ class CheckoutableListener break; case Asset::class: $notificationClass = CheckinAssetNotification::class; - break; + break; case LicenseSeat::class: $notificationClass = CheckinLicenseSeatNotification::class; break; @@ -201,9 +264,8 @@ class CheckoutableListener Log::debug('Notification class: '.$notificationClass); - return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); + return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); } - /** * Get the appropriate notification for the event * @@ -224,7 +286,7 @@ class CheckoutableListener break; case Consumable::class: $notificationClass = CheckoutConsumableNotification::class; - break; + break; case LicenseSeat::class: $notificationClass = CheckoutLicenseSeatNotification::class; break; @@ -233,23 +295,64 @@ class CheckoutableListener return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note); } + private function getCheckoutMailType($event, $acceptance){ + $lookup = [ + Accessory::class => CheckoutAccessoryMail::class, + Asset::class => CheckoutAssetMail::class, + LicenseSeat::class => CheckoutLicenseMail::class, + Consumable::class => CheckoutConsumableMail::class, + ]; + $mailable= $lookup[get_class($event->checkoutable)]; + + return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note); + + } + + private function getCheckinMailType($event){ + $lookup = [ + Accessory::class => CheckinAccessoryMail::class, + Asset::class => CheckinAssetMail::class, + LicenseSeat::class => CheckinLicenseMail::class, + ]; + + $mailable= $lookup[get_class($event->checkoutable)]; + + return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); + + } /** - * Register the listeners for the subscriber. + * This gets the recipient objects based on the type of checkoutable. + * The 'name' property for users is set in the boot method in the User model. * - * @param Illuminate\Events\Dispatcher $events + * @see \App\Models\User::boot() + * @param $event + * @return mixed */ - public function subscribe($events) + private function getNotifiableUser($event) { - $events->listen( - \App\Events\CheckoutableCheckedIn::class, - 'App\Listeners\CheckoutableListener@onCheckedIn' - ); - $events->listen( - \App\Events\CheckoutableCheckedOut::class, - 'App\Listeners\CheckoutableListener@onCheckedOut' - ); + // If it's assigned to an asset, get that asset's assignedTo object + if ($event->checkedOutTo instanceof Asset){ + $event->checkedOutTo->load('assignedTo'); + return $event->checkedOutTo->assignedto; + + // If it's assigned to a location, get that location's manager object + } elseif ($event->checkedOutTo instanceof Location) { + return $event->checkedOutTo->manager; + + // Otherwise just return the assigned to object + } else { + return $event->checkedOutTo; + } + } + + private function webhookSelected(){ + if(Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general'){ + return 'slack'; + } + + return Setting::getSettings()->webhook_selected; } private function shouldNotSendAnyNotifications($checkoutable): bool @@ -257,8 +360,115 @@ class CheckoutableListener return in_array(get_class($checkoutable), $this->skipNotificationsFor); } + private function shouldSendEmailNotifications(Model $checkoutable): bool + { + //runs a check if the category wants to send checkin/checkout emails to users + $category = match (true) { + $checkoutable instanceof Asset => $checkoutable->model->category, + $checkoutable instanceof Accessory, + $checkoutable instanceof Consumable => $checkoutable->category, + $checkoutable instanceof LicenseSeat => $checkoutable->license->category, + default => null, + }; + + if (!$category?->checkin_email) { + return false; + } + return true; + } + private function shouldSendWebhookNotification(): bool { return Setting::getSettings() && Setting::getSettings()->webhook_endpoint; } + + private function checkoutableCategoryShouldSendEmail(Model $checkoutable): bool + { + if ($checkoutable instanceof LicenseSeat) { + return $checkoutable->license->checkin_email(); + } + return (method_exists($checkoutable, 'checkin_email') && $checkoutable->checkin_email()); + } + + private function newMicrosoftTeamsWebhookEnabled(): bool + { + return Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'); + } + + private function shouldSendCheckoutEmailToUser(Model $checkoutable): bool + { + /** + * Send an email if any of the following conditions are met: + * 1. The asset requires acceptance + * 2. The item has a EULA + * 3. The item should send an email at check-in/check-out + */ + + if ($checkoutable->requireAcceptance()) { + return true; + } + + if ($checkoutable->getEula()) { + return true; + } + + if ($this->checkoutableCategoryShouldSendEmail($checkoutable)) { + return true; + } + + return false; + } + + private function shouldSendEmailToAlertAddress($acceptance = null): bool + { + $setting = Setting::getSettings(); + + if (!$setting) { + return false; + } + + if (is_null($acceptance) && !$setting->admin_cc_always) { + return false; + } + + return (bool) $setting->admin_cc_email; + } + + private function getFormattedAlertAddresses(): array + { + $alertAddresses = Setting::getSettings()->admin_cc_email; + + if ($alertAddresses !== '') { + return array_filter(array_map('trim', explode(',', $alertAddresses))); + } + + return []; + } + + private function generateEmailRecipients( + bool $shouldSendEmailToUser, + bool $shouldSendEmailToAlertAddress, + mixed $notifiable + ): array { + $to = []; + $cc = []; + + // if user && cc: to user, cc admin + if ($shouldSendEmailToUser && $shouldSendEmailToAlertAddress) { + $to[] = $notifiable; + $cc[] = $this->getFormattedAlertAddresses(); + } + + // if user && no cc: to user + if ($shouldSendEmailToUser && !$shouldSendEmailToAlertAddress) { + $to[] = $notifiable; + } + + // if no user && cc: to admin + if (!$shouldSendEmailToUser && $shouldSendEmailToAlertAddress) { + $to[] = $this->getFormattedAlertAddresses(); + } + + return array($to, $cc); + } } diff --git a/app/Listeners/LogListener.php b/app/Listeners/LogListener.php index 57eee03f17..d7973e2103 100644 --- a/app/Listeners/LogListener.php +++ b/app/Listeners/LogListener.php @@ -65,6 +65,7 @@ class LogListener $logaction->filename = $event->acceptance->stored_eula_file; $logaction->note = $event->acceptance->note; $logaction->action_type = 'accepted'; + $logaction->action_date = $event->acceptance->accepted_at; // TODO: log the actual license seat that was checked out if ($event->acceptance->checkoutable instanceof LicenseSeat) { @@ -82,6 +83,7 @@ class LogListener $logaction->accept_signature = $event->acceptance->signature_filename; $logaction->note = $event->acceptance->note; $logaction->action_type = 'declined'; + $logaction->action_date = $event->acceptance->declined_at; // TODO: log the actual license seat that was checked out if ($event->acceptance->checkoutable instanceof LicenseSeat) { @@ -111,7 +113,7 @@ class LogListener $logaction->target_type = User::class; $logaction->action_type = 'merged'; $logaction->note = trans('general.merged_log_this_user_from', $to_from_array); - $logaction->user_id = $event->admin->id; + $logaction->created_by = $event->admin->id ?? null; $logaction->save(); // Add a record to the users being merged TO @@ -122,7 +124,7 @@ class LogListener $logaction->item_type = User::class; $logaction->action_type = 'merged'; $logaction->note = trans('general.merged_log_this_user_into', $to_from_array); - $logaction->user_id = $event->admin->id; + $logaction->created_by = $event->admin->id ?? null; $logaction->save(); @@ -141,6 +143,7 @@ class LogListener 'CheckoutAccepted', 'CheckoutDeclined', 'UserMerged', + 'NoteAdded', ]; foreach ($list as $event) { diff --git a/app/Livewire/CategoryEditForm.php b/app/Livewire/CategoryEditForm.php index 28a06657c1..fd8bef6489 100644 --- a/app/Livewire/CategoryEditForm.php +++ b/app/Livewire/CategoryEditForm.php @@ -12,11 +12,11 @@ class CategoryEditForm extends Component public $originalSendCheckInEmailValue; - public $requireAcceptance; + public bool $requireAcceptance; - public $sendCheckInEmail; + public bool $sendCheckInEmail; - public $useDefaultEula; + public bool $useDefaultEula; public function mount() { diff --git a/app/Livewire/CustomFieldSetDefaultValuesForModel.php b/app/Livewire/CustomFieldSetDefaultValuesForModel.php index a4a9f9fe71..a5f179b1cb 100644 --- a/app/Livewire/CustomFieldSetDefaultValuesForModel.php +++ b/app/Livewire/CustomFieldSetDefaultValuesForModel.php @@ -2,6 +2,8 @@ namespace App\Livewire; +use App\Models\CustomField; +use Livewire\Attributes\Computed; use Livewire\Component; use App\Models\CustomFieldset; @@ -12,37 +14,109 @@ class CustomFieldSetDefaultValuesForModel extends Component public $add_default_values; public $fieldset_id; - public $fields; public $model_id; - public function mount() + public array $selectedValues = []; + + public function mount($model_id = null) { - if(is_null($this->model_id)){ - return; + $this->model_id = $model_id; + $this->fieldset_id = $this->model?->fieldset_id; + $this->add_default_values = ($this->model?->defaultValues->count() > 0); + + + $this->initializeSelectedValuesArray(); + if (session()->has('errors')) { + $errors = session('errors')->keys(); + $selectedValuesKeys = array_keys($this->selectedValues); + if (count(array_intersect($selectedValuesKeys, $errors)) > 0) { + $this->add_default_values = true; + }; } - - $this->model = AssetModel::find($this->model_id); // It's possible to do some clever route-model binding here, but let's keep it simple, shall we? - $this->fieldset_id = $this->model->fieldset_id; - - $this->fields = null; - - if ($fieldset = CustomFieldset::find($this->fieldset_id)) { - $this->fields = CustomFieldset::find($this->fieldset_id)->fields; - } - - $this->add_default_values = ($this->model->defaultValues->count() > 0); + $this->populatedSelectedValuesArray(); } - public function updatedFieldsetId() + #[Computed] + public function model() { - if (CustomFieldset::find($this->fieldset_id)) { - $this->fields = CustomFieldset::find($this->fieldset_id)->fields; + return AssetModel::find($this->model_id); + } + + #[Computed] + public function fields() + { + $customFieldset = CustomFieldset::find($this->fieldset_id); + + if ($customFieldset) { + return $customFieldset?->fields; } - + + return collect(); } public function render() { return view('livewire.custom-field-set-default-values-for-model'); } + + /** + * Livewire property binding plays nicer with arrays when it knows + * which keys will be present instead of them being + * dynamically added (this is especially true for checkboxes). + * + * Let's go ahead and initialize selectedValues with all the potential keys (custom field db_columns). + * + * @return void + */ + private function initializeSelectedValuesArray(): void + { + CustomField::all()->each(function ($field) { + $this->selectedValues[$field->db_column] = null; + + if ($field->element === 'checkbox') { + $this->selectedValues[$field->db_column] = []; + } + }); + } + + /** + * Populate the selectedValues array with the + * default values or old input for each field. + * + * @return void + */ + private function populatedSelectedValuesArray(): void + { + $this->fields->each(function ($field) { + $this->selectedValues[$field->db_column] = $this->getSelectedValueForField($field); + + // if the element is a checkbox and the value was just sent to null, make it + // an array since Livewire can't bind to non-array values for checkboxes. + if ($field->element === 'checkbox' && is_null($this->selectedValues[$field->db_column])) { + $this->selectedValues[$field->db_column] = []; + } + }); + } + + private function getSelectedValueForField(CustomField $field) + { + $defaultValue = $field->defaultValue($this->model_id); + + // if old() contains a value for default_values that means + // the user has submitted the form and we were redirected + // back with the old input. + // Let's use what they had previously set. + if (old('default_values')) { + $defaultValue = old('default_values.' . $field->id); + } + + // on first load the default value for checkboxes will be + // a comma-separated string but if we're loading the page + // with old input then it was already parsed into an array. + if ($field->element === 'checkbox' && is_string($defaultValue)) { + $defaultValue = explode(', ', $defaultValue); + } + + return $defaultValue; + } } diff --git a/app/Livewire/Importer.php b/app/Livewire/Importer.php index e164af36d6..4802eb8125 100644 --- a/app/Livewire/Importer.php +++ b/app/Livewire/Importer.php @@ -3,30 +3,25 @@ namespace App\Livewire; use App\Models\CustomField; -use Livewire\Component; - use App\Models\Import; use Illuminate\Support\Facades\Storage; - -use Illuminate\Foundation\Auth\Access\AuthorizesRequests; - +use Livewire\Attributes\Computed; +use Livewire\Component; class Importer extends Component { - use AuthorizesRequests; - - public $files; - - public $progress; //upload progress - '-1' means don't show + public $progress = -1; //upload progress - '-1' means don't show public $progress_message; - public $progress_bar_class; + public $progress_bar_class = 'progress-bar-warning'; public $message; //status/error message? public $message_type; //success/error? //originally from ImporterFile public $import_errors; // - public ?Import $activeFile = null; + public $activeFileId; + public $headerRow = []; + public $typeOfImport; public $importTypes; public $columnOptions; public $statusType; @@ -35,26 +30,27 @@ class Importer extends Component public $send_welcome; public $run_backup; public $field_map; // we need a separate variable for the field-mapping, because the keys in the normal array are too complicated for Livewire to understand - public $file_id; // TODO: I can't figure out *why* we need this, but it really seems like we do. I can't seem to pull the id from the activeFile for some reason? // Make these variables public - we set the properties in the constructor so we can localize them (versus the old static arrays) public $accessories_fields; public $assets_fields; public $users_fields; + public $assetmodels_fields; + public $suppliers_fields; public $licenses_fields; public $locations_fields; public $consumables_fields; public $components_fields; + public $manufacturers_fields; + public $categories_fields; public $aliases_fields; protected $rules = [ 'files.*.file_path' => 'required|string', 'files.*.created_at' => 'required|string', 'files.*.filesize' => 'required|integer', - 'activeFile' => 'Import', - 'activeFile.import_type' => 'string', - 'activeFile.field_map' => 'array', - 'activeFile.header_row' => 'array', + 'headerRow' => 'array', + 'typeOfImport' => 'string', 'field_map' => 'array' ]; @@ -68,21 +64,22 @@ class Importer extends Component { $tmp = array(); if ($this->activeFile) { - $tmp = array_combine($this->activeFile->header_row, $this->field_map); + $tmp = array_combine($this->headerRow, $this->field_map); $tmp = array_filter($tmp); } return json_encode($tmp); } - - private function getColumns($type) { switch ($type) { case 'asset': $results = $this->assets_fields; break; + case 'assetModel': + $results = $this->assetmodels_fields; + break; case 'accessory': $results = $this->accessories_fields; break; @@ -101,10 +98,20 @@ class Importer extends Component case 'location': $results = $this->locations_fields; break; + case 'supplier': + $results = $this->suppliers_fields; + break; + case 'manufacturer': + $results = $this->manufacturers_fields; + break; + case 'category': + $results = $this->categories_fields; + break; default: $results = []; } asort($results, SORT_FLAG_CASE | SORT_STRING); + if ($type == "asset") { // add Custom Fields after a horizontal line $results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’"; @@ -115,114 +122,109 @@ class Importer extends Component return $results; } - public function updating($name, $new_import_type) + public function updatingTypeOfImport($type) { - if ($name == "activeFile.import_type") { - // go through each header, find a matching field to try and map it to. - foreach ($this->activeFile->header_row as $i => $header) { - // do we have something mapped already? - if (array_key_exists($i, $this->field_map)) { - // yes, we do. Is it valid for this type of import? - // (e.g. the import type might have been changed...?) - if (array_key_exists($this->field_map[$i], $this->columnOptions[$new_import_type])) { - //yes, this key *is* valid. Continue on to the next field. - continue; - } else { - //no, this key is *INVALID* for this import type. Better set it to null - // and we'll hope that the $aliases_fields or something else picks it up. - $this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh. - } // TODO - strictly speaking, this isn't necessary here I don't think. + // go through each header, find a matching field to try and map it to. + foreach ($this->headerRow as $i => $header) { + // do we have something mapped already? + if (array_key_exists($i, $this->field_map)) { + // yes, we do. Is it valid for this type of import? + // (e.g. the import type might have been changed...?) + if (array_key_exists($this->field_map[$i], $this->columnOptions[$type])) { + //yes, this key *is* valid. Continue on to the next field. + continue; + } else { + //no, this key is *INVALID* for this import type. Better set it to null, + // and we'll hope that the $aliases_fields or something else picks it up. + $this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh. + } // TODO - strictly speaking, this isn't necessary here I don't think. + } + // first, check for exact matches + foreach ($this->columnOptions[$type] as $v => $text) { + if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose! + $this->field_map[$i] = $v; + continue 2; //don't bother with the alias check, go to the next header } - // first, check for exact matches - foreach ($this->columnOptions[$new_import_type] as $value => $text) { - if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose! - $this->field_map[$i] = $value; - continue 2; //don't bother with the alias check, go to the next header - } - } - // if you got here, we didn't find a match. Try the $aliases_fields - foreach ($this->aliases_fields as $key => $alias_values) { - foreach ($alias_values as $alias_value) { - if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE! - // Make *absolutely* sure that this key actually _exists_ in this import type - - // you can trigger this by importing accessories with a 'Warranty' column (which don't exist - // in "Accessories"!) - if (array_key_exists($key, $this->columnOptions[$new_import_type])) { - $this->field_map[$i] = $key; - continue 3; // bust out of both of these loops; as well as the surrounding one - e.g. move on to the next header - } + } + // if you got here, we didn't find a match. Try the $aliases_fields + foreach ($this->aliases_fields as $key => $alias_values) { + foreach ($alias_values as $alias_value) { + if (strcasecmp($alias_value, $header) === 0) { // aLsO CaSe-INSENSitiVE! + // Make *absolutely* sure that this key actually _exists_ in this import type - + // you can trigger this by importing accessories with a 'Warranty' column (which don't exist + // in "Accessories"!) + if (array_key_exists($key, $this->columnOptions[$type])) { + $this->field_map[$i] = $key; + continue 3; // bust out of both of these loops and the surrounding one - e.g. move on to the next header } } } - // and if you got here, we got nothing. Let's recommend 'null' - $this->field_map[$i] = null; // Booooo :( } + // and if you got here, we got nothing. Let's recommend 'null' + $this->field_map[$i] = null; // Booooo :( } } - public function boot() { // FIXME - delete or undelete. - ///////$this->activeFile = null; // I do *not* understand why I have to do this, but, well, whatever. - } - - public function mount() { $this->authorize('import'); - $this->progress = -1; // '-1' means 'don't show the progressbar' - $this->progress_bar_class = 'progress-bar-warning'; $this->importTypes = [ - 'asset' => trans('general.assets'), - 'accessory' => trans('general.accessories'), - 'consumable' => trans('general.consumables'), - 'component' => trans('general.components'), - 'license' => trans('general.licenses'), - 'user' => trans('general.users'), - 'location' => trans('general.locations'), + 'accessory' => trans('general.accessories'), + 'asset' => trans('general.assets'), + 'assetModel' => trans('general.asset_models'), + 'component' => trans('general.components'), + 'consumable' => trans('general.consumables'), + 'license' => trans('general.licenses'), + 'location' => trans('general.locations'), + 'user' => trans('general.users'), + 'supplier' => trans('general.suppliers'), + 'manufacturer' => trans('general.manufacturers'), + 'category' => trans('general.categories'), ]; /** * These are the item-type specific columns */ - $this->accessories_fields = [ + $this->accessories_fields = [ + 'category' => trans('general.category'), 'company' => trans('general.company'), - 'location' => trans('general.location'), - 'quantity' => trans('general.qty'), 'item_name' => trans('general.item_name_var', ['item' => trans('general.accessory')]), + 'location' => trans('general.location'), + 'manufacturer' => trans('general.manufacturer'), + 'min_amt' => trans('mail.min_QTY'), 'model_number' => trans('general.model_no'), 'notes' => trans('general.notes'), - 'category' => trans('general.category'), - 'supplier' => trans('general.supplier'), - 'min_amt' => trans('mail.min_QTY'), + 'order_number' => trans('general.order_number'), 'purchase_cost' => trans('general.purchase_cost'), 'purchase_date' => trans('general.purchase_date'), - 'manufacturer' => trans('general.manufacturer'), - 'order_number' => trans('general.order_number'), + 'quantity' => trans('general.qty'), + 'supplier' => trans('general.supplier'), ]; $this->assets_fields = [ - 'company' => trans('general.company'), - 'location' => trans('general.location'), - 'item_name' => trans('general.item_name_var', ['item' => trans('general.asset')]), - 'asset_tag' => trans('general.asset_tag'), + 'id' => trans('general.id'), + 'asset_eol_date' => trans('admin/hardware/form.eol_date'), 'asset_model' => trans('general.model_name'), + 'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]), + 'asset_tag' => trans('general.asset_tag'), 'byod' => trans('general.byod'), - 'model_number' => trans('general.model_no'), - 'status' => trans('general.status'), - 'warranty_months' => trans('admin/hardware/form.warranty'), 'category' => trans('general.category'), - 'requestable' => trans('admin/hardware/general.requestable'), - 'serial' => trans('general.serial_number'), - 'supplier' => trans('general.supplier'), + 'company' => trans('general.company'), + 'image' => trans('general.importer.image_filename'), + 'item_name' => trans('general.item_name_var', ['item' => trans('general.asset')]), + 'location' => trans('general.location'), + 'manufacturer' => trans('general.manufacturer'), + 'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]), + 'model_number' => trans('general.model_no'), + 'order_number' => trans('general.order_number'), 'purchase_cost' => trans('general.purchase_cost'), 'purchase_date' => trans('general.purchase_date'), - 'purchase_order' => trans('admin/licenses/form.purchase_order'), - 'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]), - 'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]), - 'manufacturer' => trans('general.manufacturer'), - 'order_number' => trans('general.order_number'), - 'image' => trans('general.importer.image_filename'), - 'asset_eol_date' => trans('admin/hardware/form.eol_date'), + 'requestable' => trans('admin/hardware/general.requestable'), + 'serial' => trans('general.serial_number'), + 'status' => trans('general.status'), + 'supplier' => trans('general.supplier'), + 'warranty_months' => trans('admin/hardware/form.warranty'), /** * Checkout fields: * Assets can be checked out to other assets, people, or locations, but we currently @@ -247,109 +249,166 @@ class Importer extends Component ]; $this->consumables_fields = [ + 'category' => trans('general.category'), + 'checkout_class' => trans('general.importer.checkout_type'), 'company' => trans('general.company'), - 'location' => trans('general.location'), - 'quantity' => trans('general.qty'), 'item_name' => trans('general.item_name_var', ['item' => trans('general.consumable')]), + 'item_no' => trans('admin/consumables/general.item_no'), + 'location' => trans('general.location'), + 'manufacturer' => trans('general.manufacturer'), + 'min_amt' => trans('general.min_amt'), 'model_number' => trans('general.model_no'), 'notes' => trans('general.notes'), - 'min_amt' => trans('mail.min_QTY'), - 'category' => trans('general.category'), + 'order_number' => trans('general.order_number'), 'purchase_cost' => trans('general.purchase_cost'), 'purchase_date' => trans('general.purchase_date'), - 'checkout_class' => trans('general.importer.checkout_type'), + 'quantity' => trans('general.qty'), 'supplier' => trans('general.supplier'), - 'manufacturer' => trans('general.manufacturer'), - 'order_number' => trans('general.order_number'), - 'item_no' => trans('admin/consumables/general.item_no'), ]; $this->components_fields = [ + 'category' => trans('general.category'), 'company' => trans('general.company'), - 'location' => trans('general.location'), - 'quantity' => trans('general.qty'), 'item_name' => trans('general.item_name_var', ['item' => trans('general.component')]), + 'location' => trans('general.location'), + 'manufacturer' => trans('general.manufacturer'), + 'min_amt' => trans('mail.min_QTY'), 'model_number' => trans('general.model_no'), 'notes' => trans('general.notes'), - 'category' => trans('general.category'), - 'supplier' => trans('general.supplier'), - 'min_amt' => trans('mail.min_QTY'), + 'order_number' => trans('general.order_number'), 'purchase_cost' => trans('general.purchase_cost'), 'purchase_date' => trans('general.purchase_date'), - 'manufacturer' => trans('general.manufacturer'), - 'order_number' => trans('general.order_number'), + 'quantity' => trans('general.qty'), 'serial' => trans('general.serial_number'), + 'supplier' => trans('general.supplier'), ]; $this->licenses_fields = [ - 'company' => trans('general.company'), - 'location' => trans('general.location'), - 'item_name' => trans('general.item_name_var', ['item' => trans('general.license')]), 'asset_tag' => trans('general.importer.checked_out_to_tag'), + 'category' => trans('general.category'), + 'checkout_class' => trans('general.importer.checkout_type'), + 'company' => trans('general.company'), + 'email' => trans('general.importer.checked_out_to_email'), 'expiration_date' => trans('admin/licenses/form.expiration'), 'full_name' => trans('general.importer.checked_out_to_fullname'), + 'item_name' => trans('general.item_name_var', ['item' => trans('general.license')]), 'license_email' => trans('admin/licenses/form.to_email'), 'license_name' => trans('admin/licenses/form.to_name'), - 'purchase_order' => trans('admin/licenses/form.purchase_order'), - 'order_number' => trans('general.order_number'), - 'reassignable' => trans('admin/licenses/form.reassignable'), - 'seats' => trans('admin/licenses/form.seats'), + 'location' => trans('general.location'), + 'maintained' => trans('admin/licenses/form.maintained'), + 'manufacturer' => trans('general.manufacturer'), + 'min_amt' => trans('general.min_amt'), 'notes' => trans('general.notes'), - 'category' => trans('general.category'), - 'supplier' => trans('general.supplier'), + 'order_number' => trans('general.order_number'), 'purchase_cost' => trans('general.purchase_cost'), 'purchase_date' => trans('general.purchase_date'), - 'maintained' => trans('admin/licenses/form.maintained'), - 'checkout_class' => trans('general.importer.checkout_type'), + 'purchase_order' => trans('admin/licenses/form.purchase_order'), + 'reassignable' => trans('admin/licenses/form.reassignable'), + 'seats' => trans('admin/licenses/form.seats'), 'serial' => trans('general.license_serial'), - 'email' => trans('general.importer.checked_out_to_email'), + 'supplier' => trans('general.supplier'), + 'termination_date' => trans('admin/licenses/form.termination_date'), 'username' => trans('general.importer.checked_out_to_username'), - 'manufacturer' => trans('general.manufacturer'), ]; - $this->users_fields = [ + $this->users_fields = [ 'id' => trans('general.id'), - 'company' => trans('general.company'), - 'location' => trans('general.location'), - 'department' => trans('general.department'), - 'first_name' => trans('general.first_name'), - 'last_name' => trans('general.last_name'), - 'notes' => trans('general.notes'), - 'username' => trans('admin/users/table.username'), - 'jobtitle' => trans('admin/users/table.title'), - 'phone_number' => trans('admin/users/table.phone'), - 'manager_first_name' => trans('general.importer.manager_first_name'), - 'manager_last_name' => trans('general.importer.manager_last_name'), 'activated' => trans('general.activated'), 'address' => trans('general.address'), - 'city' => trans('general.city'), - 'state' => trans('general.state'), - 'country' => trans('general.country'), - 'zip' => trans('general.zip'), - 'vip' => trans('general.importer.vip'), - 'remote' => trans('admin/users/general.remote'), - 'email' => trans('admin/users/table.email'), - 'website' => trans('general.website'), 'avatar' => trans('general.image'), + 'city' => trans('general.city'), + 'company' => trans('general.company'), + 'country' => trans('general.country'), + 'department' => trans('general.department'), + 'email' => trans('admin/users/table.email'), + 'employee_num' => trans('general.employee_number'), + 'end_date' => trans('general.end_date'), + 'first_name' => trans('general.first_name'), 'gravatar' => trans('general.importer.gravatar'), - 'start_date' => trans('general.start_date'), - 'end_date' => trans('general.end_date'), - 'employee_num' => trans('general.employee_number'), + 'jobtitle' => trans('admin/users/table.title'), + 'last_name' => trans('general.last_name'), + 'location' => trans('general.location'), + 'manager_first_name' => trans('general.importer.manager_first_name'), + 'manager_last_name' => trans('general.importer.manager_last_name'), + 'notes' => trans('general.notes'), + 'phone_number' => trans('admin/users/table.phone'), + 'remote' => trans('admin/users/general.remote'), + 'start_date' => trans('general.start_date'), + 'state' => trans('general.state'), + 'username' => trans('admin/users/table.username'), + 'vip' => trans('general.importer.vip'), + 'website' => trans('general.website'), + 'zip' => trans('general.zip'), ]; - $this->locations_fields = [ - 'name' => trans('general.item_name_var', ['item' => trans('general.location')]), + $this->locations_fields = [ + 'id' => trans('general.id'), + 'name' => trans('general.name'), 'address' => trans('general.address'), 'address2' => trans('general.importer.address2'), 'city' => trans('general.city'), - 'state' => trans('general.state'), 'country' => trans('general.country'), - 'zip' => trans('general.zip'), 'currency' => trans('general.importer.currency'), 'ldap_ou' => trans('admin/locations/table.ldap_ou'), - 'manager_username' => trans('general.importer.manager_username'), 'manager' => trans('general.importer.manager_full_name'), + 'manager_username' => trans('general.importer.manager_username'), + 'notes' => trans('general.notes'), 'parent_location' => trans('admin/locations/table.parent'), + 'state' => trans('general.state'), + 'zip' => trans('general.zip'), + ]; + + $this->suppliers_fields = [ + 'id' => trans('general.id'), + 'name' => trans('general.name'), + 'address' => trans('general.address'), + 'address2' => trans('general.importer.address2'), + 'city' => trans('general.city'), + 'notes' => trans('general.notes'), + 'state' => trans('general.state'), + 'zip' => trans('general.zip'), + 'phone' => trans('general.phone'), + 'fax' => trans('general.fax'), + 'url' => trans('general.url'), + 'contact' => trans('general.contact'), + 'email' => trans('general.email'), + ]; + + $this->manufacturers_fields = [ + 'id' => trans('general.id'), + 'name' => trans('general.name'), + 'notes' => trans('general.notes'), + 'support_phone' => trans('admin/manufacturers/table.support_phone'), + 'support_url' => trans('admin/manufacturers/table.support_url'), + 'support_email' => trans('admin/manufacturers/table.support_email'), + 'warranty_lookup_url' => trans('admin/manufacturers/table.warranty_lookup_url'), + 'url' => trans('general.url'), + ]; + + $this->categories_fields = [ + 'id' => trans('general.id'), + 'name' => trans('general.name'), + 'notes' => trans('general.notes'), + 'category_type' => trans('admin/categories/general.import_category_type'), + 'eula_text' => trans('admin/categories/general.import_eula_text'), + 'use_default_eula' => trans('admin/categories/general.use_default_eula_column'), + 'require_acceptance' => trans('admin/categories/general.import_require_acceptance'), + 'checkin_email' => trans('admin/categories/general.import_checkin_email'), + ]; + + + + $this->assetmodels_fields = [ + 'category' => trans('general.category'), + 'eol' => trans('general.eol'), + 'fieldset' => trans('admin/models/general.fieldset'), + 'item_name' => trans('general.item_name_var', ['item' => trans('general.asset_model')]), + 'manufacturer' => trans('general.manufacturer'), + 'min_amt' => trans('mail.min_QTY'), + 'model_number' => trans('general.model_no'), + 'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]), + 'requestable' => trans('admin/models/general.requestable'), + ]; // "real fieldnames" to a list of aliases for that field @@ -363,6 +422,8 @@ class Importer extends Component 'consumable name', 'component name', 'name', + 'supplier name', + 'location name', ], 'item_no' => [ 'item number', @@ -374,6 +435,28 @@ class Importer extends Component 'model name', 'model', ], + 'eol_date' => + [ + 'eol', + 'eol date', + 'asset eol date', + ], + 'eol' => + [ + 'eol', + 'EOL', + 'eol months', + ], + 'depreciation' => + [ + 'Depreciation', + 'depreciation', + ], + 'requestable' => + [ + 'requestable', + 'Requestable', + ], 'gravatar' => [ 'gravatar', @@ -504,35 +587,35 @@ class Importer extends Component ]; $this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean - foreach($this->importTypes AS $type => $name) { + foreach ($this->importTypes as $type => $name) { $this->columnOptions[$type] = $this->getColumns($type); } - if ($this->activeFile) { - $this->field_map = $this->activeFile->field_map ? array_values($this->activeFile->field_map) : []; - } } public function selectFile($id) { $this->clearMessage(); - $this->activeFile = Import::find($id); + $this->activeFileId = $id; if (!$this->activeFile) { $this->message = trans('admin/hardware/message.import.file_missing'); $this->message_type = 'danger'; - return; } + $this->headerRow = $this->activeFile->header_row; + $this->typeOfImport = $this->activeFile->import_type; + $this->field_map = null; - foreach($this->activeFile->header_row as $element) { - if(isset($this->activeFile->field_map[$element])) { + foreach ($this->headerRow as $element) { + if (isset($this->activeFile->field_map[$element])) { $this->field_map[] = $this->activeFile->field_map[$element]; } else { $this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings } } + $this->file_id = $id; $this->import_errors = null; $this->statusText = null; @@ -541,21 +624,33 @@ class Importer extends Component public function destroy($id) { - // TODO: why don't we just do File::find($id)? This seems dumb. - foreach($this->files as $file) { - if ($id == $file->id) { - if (Storage::delete('private_uploads/imports/'.$file->file_path)) { - $file->delete(); + $this->authorize('import'); - $this->message = trans('admin/hardware/message.import.file_delete_success'); - $this->message_type = 'success'; - return; - } else { - $this->message = trans('admin/hardware/message.import.file_delete_error'); - $this->message_type = 'danger'; - } - } + $import = Import::find($id); + + // Check that the import wasn't deleted after while page was already loaded... + // @todo: next up...handle the file being missing for other interactions... + // for example having an import open in two tabs, deleting it, and then changing + // the import type in the other tab. The error message below wouldn't display in that case. + if (!$import) { + $this->message = trans('admin/hardware/message.import.file_already_deleted'); + $this->message_type = 'danger'; + + return; } + + if (Storage::delete('private_uploads/imports/' . $import->file_path)) { + $import->delete(); + $this->message = trans('admin/hardware/message.import.file_delete_success'); + $this->message_type = 'success'; + + unset($this->files); + + return; + } + + $this->message = trans('admin/hardware/message.import.file_delete_error'); + $this->message_type = 'danger'; } public function clearMessage() @@ -564,11 +659,22 @@ class Importer extends Component $this->message_type = null; } + #[Computed] + public function files() + { + return Import::orderBy('id', 'desc')->get(); + } + + #[Computed] + public function activeFile() + { + return Import::find($this->activeFileId); + } + public function render() { - $this->files = Import::orderBy('id','desc')->get(); //HACK - slows down renders. return view('livewire.importer') - ->extends('layouts.default') - ->section('content'); + ->extends('layouts.default') + ->section('content'); } } diff --git a/app/Livewire/LocationScopeCheck.php b/app/Livewire/LocationScopeCheck.php new file mode 100644 index 0000000000..e176d41f0a --- /dev/null +++ b/app/Livewire/LocationScopeCheck.php @@ -0,0 +1,28 @@ +mismatched = Helper::test_locations_fmcs(false); + } + + public function mount() { + $this->setting = Setting::getSettings(); + $this->mismatched = Helper::test_locations_fmcs(false); + } + + public function render() + { + return view('livewire.location-scope-check'); + } +} diff --git a/app/Livewire/OauthClients.php b/app/Livewire/OauthClients.php index 02fc77848b..e114c2278e 100644 --- a/app/Livewire/OauthClients.php +++ b/app/Livewire/OauthClients.php @@ -22,8 +22,8 @@ class OauthClients extends Component public function render() { return view('livewire.oauth-clients', [ - 'clients' => app(ClientRepository::class)->activeForUser(auth()->user()->id), - 'authorized_tokens' => app(TokenRepository::class)->forUser(auth()->user()->id)->where('revoked', false), + 'clients' => app(ClientRepository::class)->activeForUser(auth()->id()), + 'authorized_tokens' => app(TokenRepository::class)->forUser(auth()->id())->where('revoked', false), ]); } @@ -35,7 +35,7 @@ class OauthClients extends Component ]); app(ClientRepository::class)->create( - auth()->user()->id, + auth()->id(), $this->name, $this->redirect, ); @@ -47,10 +47,10 @@ class OauthClients extends Component { // test for safety // ->delete must be of type Client - thus the model binding - if ($clientId->user_id == auth()->user()->id) { + if ($clientId->user_id == auth()->id()) { app(ClientRepository::class)->delete($clientId); } else { - Log::warning('User ' . auth()->user()->id . ' attempted to delete client ' . $clientId->id . ' which belongs to user ' . $clientId->user_id); + Log::warning('User ' . auth()->id() . ' attempted to delete client ' . $clientId->id . ' which belongs to user ' . $clientId->created_by); $this->authorizationError = 'You are not authorized to delete this client.'; } } @@ -58,10 +58,10 @@ class OauthClients extends Component public function deleteToken($tokenId): void { $token = app(TokenRepository::class)->find($tokenId); - if ($token->user_id == auth()->user()->id) { + if ($token->created_by == auth()->id()) { app(TokenRepository::class)->revokeAccessToken($tokenId); } else { - Log::warning('User ' . auth()->user()->id . ' attempted to delete token ' . $tokenId . ' which belongs to user ' . $token->user_id); + Log::warning('User ' . auth()->id() . ' attempted to delete token ' . $tokenId . ' which belongs to user ' . $token->created_by); $this->authorizationError = 'You are not authorized to delete this token.'; } } @@ -84,12 +84,12 @@ class OauthClients extends Component ]); $client = app(ClientRepository::class)->find($editClientId->id); - if ($client->user_id == auth()->user()->id) { + if ($client->created_by == auth()->id()) { $client->name = $this->editName; $client->redirect = $this->editRedirect; $client->save(); } else { - Log::warning('User ' . auth()->user()->id . ' attempted to edit client ' . $editClientId->id . ' which belongs to user ' . $client->user_id); + Log::warning('User ' . auth()->id() . ' attempted to edit client ' . $editClientId->id . ' which belongs to user ' . $client->created_by); $this->authorizationError = 'You are not authorized to edit this client.'; } diff --git a/app/Livewire/PersonalAccessTokens.php b/app/Livewire/PersonalAccessTokens.php index 2d2f56662a..79543a609e 100644 --- a/app/Livewire/PersonalAccessTokens.php +++ b/app/Livewire/PersonalAccessTokens.php @@ -23,7 +23,7 @@ class PersonalAccessTokens extends Component public function render() { return view('livewire.personal-access-tokens', [ - 'tokens' => Auth::user()->tokens, + 'tokens' => auth()->user()->tokens, ]); } @@ -38,7 +38,7 @@ class PersonalAccessTokens extends Component { $this->validate(); - $newToken = Auth::user()->createToken($this->name); + $newToken = auth()->user()->createToken($this->name); $this->newTokenString = $newToken->accessToken; @@ -49,6 +49,6 @@ class PersonalAccessTokens extends Component { //this needs safety (though the scope of auth::user might kind of do it...) //seems like it does, test more - Auth::user()->tokens()->find($tokenId)->delete(); + auth()->user()->tokens()->find($tokenId)?->delete(); } } diff --git a/app/Livewire/SlackSettingsForm.php b/app/Livewire/SlackSettingsForm.php index 45b8b7b41e..535a83413f 100644 --- a/app/Livewire/SlackSettingsForm.php +++ b/app/Livewire/SlackSettingsForm.php @@ -4,10 +4,11 @@ namespace App\Livewire; use GuzzleHttp\Client; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Str; use Livewire\Component; use App\Models\Setting; use App\Helpers\Helper; - +use Osama\LaravelTeamsNotification\TeamsNotification; class SlackSettingsForm extends Component { public $webhook_endpoint; @@ -19,6 +20,7 @@ class SlackSettingsForm extends Component public $webhook_placeholder; public $webhook_icon; public $webhook_selected; + public $teams_webhook_deprecated; public array $webhook_text; public Setting $setting; @@ -62,32 +64,34 @@ class SlackSettingsForm extends Component "name" => trans('admin/settings/general.ms_teams'), "icon" => "fa-brands fa-microsoft", "placeholder" => "https://abcd.webhook.office.com/webhookb2/XXXXXXX", - "link" => "https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=dotnet#create-incoming-webhooks-1", + "link" => "https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498", "test" => "msTeamTestWebhook" ), ]; $this->setting = Setting::getSettings(); $this->save_button = trans('general.save'); - $this->webhook_selected = $this->setting->webhook_selected; - $this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"]; - $this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"]; - $this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"]; - $this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"]; - $this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"]; + $this->webhook_selected = ($this->setting->webhook_selected !== '') ? $this->setting->webhook_selected : 'slack'; + $this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"] ?? $this->webhook_text['slack']["name"]; + $this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"] ?? $this->webhook_text['slack']["icon"]; + $this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"] ?? $this->webhook_text['slack']["placeholder"]; + $this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"] ?? $this->webhook_text['slack']["link"]; + $this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"] ?? $this->webhook_text['slack']["test"]; $this->webhook_endpoint = $this->setting->webhook_endpoint; $this->webhook_channel = $this->setting->webhook_channel; $this->webhook_botname = $this->setting->webhook_botname; $this->webhook_options = $this->setting->webhook_selected; - if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){ + $this->teams_webhook_deprecated = !Str::contains($this->webhook_endpoint, 'workflows'); + if($this->webhook_selected === 'microsoft' || $this->webhook_selected === 'google'){ $this->webhook_channel = '#NA'; } - if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){ $this->isDisabled= ''; } - + if($this->webhook_selected === 'microsoft' && $this->teams_webhook_deprecated) { + session()->flash('warning', trans('admin/settings/message.webhook.ms_teams_deprecation')); + } } public function updated($field) { @@ -109,7 +113,11 @@ class SlackSettingsForm extends Component if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){ $this->webhook_channel = '#NA'; } + } + public function updatedwebhookEndpoint() + { + $this->teams_webhook_deprecated = !Str::contains($this->webhook_endpoint, 'workflows'); } private function isButtonDisabled() { @@ -126,7 +134,9 @@ class SlackSettingsForm extends Component public function render() { $this->isButtonDisabled(); + return view('livewire.slack-settings-form'); + } public function testWebhook(){ @@ -149,7 +159,7 @@ class SlackSettingsForm extends Component ]); try { - $test = $webhook->post($this->webhook_endpoint, ['body' => $payload]); + $test = $webhook->post($this->webhook_endpoint, ['body' => $payload, ['headers' => ['Content-Type' => 'application/json']]]); if(($test->getStatusCode() == 302)||($test->getStatusCode() == 301)){ return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint])); @@ -214,7 +224,7 @@ class SlackSettingsForm extends Component try { $response = Http::withHeaders([ - 'content-type' => 'applications/json', + 'content-type' => 'application/json', ])->post($this->webhook_endpoint, $payload)->throw(); @@ -236,20 +246,32 @@ class SlackSettingsForm extends Component } public function msTeamTestWebhook(){ - $payload = - [ - "@type" => "MessageCard", - "@context" => "http://schema.org/extensions", - "summary" => trans('mail.snipe_webhook_summary'), - "title" => trans('mail.snipe_webhook_test'), - "text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]), - ]; + try { - try { - $response = Http::withHeaders([ - 'content-type' => 'applications/json', - ])->post($this->webhook_endpoint, - $payload)->throw(); + if($this->teams_webhook_deprecated){ + //will use the deprecated webhook format + $payload = + [ + "@type" => "MessageCard", + "@context" => "http://schema.org/extensions", + "summary" => trans('mail.snipe_webhook_summary'), + "title" => trans('mail.snipe_webhook_test'), + "text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]), + ]; + $response = Http::withHeaders([ + 'content-type' => 'application/json', + ])->post($this->webhook_endpoint, + $payload)->throw(); + } + else { + $notification = new TeamsNotification($this->webhook_endpoint); + $message = trans('general.webhook_test_msg', ['app' => $this->webhook_name]); + $notification->success()->sendMessage($message); + + $response = Http::withHeaders([ + 'content-type' => 'application/json', + ])->post($this->webhook_endpoint); + } if(($response->getStatusCode() == 302)||($response->getStatusCode() == 301)){ return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint])); diff --git a/app/Mail/CheckinAccessoryMail.php b/app/Mail/CheckinAccessoryMail.php new file mode 100644 index 0000000000..311ee87a25 --- /dev/null +++ b/app/Mail/CheckinAccessoryMail.php @@ -0,0 +1,70 @@ +item = $accessory; + $this->target = $checkedOutTo; + $this->admin = $checkedInby; + $this->note = $note; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Accessory_Checkin_Notification'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'mail.markdown.checkin-accessory', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckinAssetMail.php b/app/Mail/CheckinAssetMail.php new file mode 100644 index 0000000000..355c2f9f13 --- /dev/null +++ b/app/Mail/CheckinAssetMail.php @@ -0,0 +1,93 @@ +target = $checkedOutTo; + $this->item = $asset; + $this->admin = $checkedInBy; + $this->note = $note; + + $this->settings = Setting::getSettings(); + $this->expected_checkin = ''; + + if ($this->item->expected_checkin) { + $this->expected_checkin = Helper::getFormattedDateObject($this->item->expected_checkin, 'date', + false); + } + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Asset_Checkin_Notification'), + ); + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return Content + */ + public function content(): Content + { + $this->item->load('assetstatus'); + $fields = []; + + // Check if the item has custom fields associated with it + if (($this->item->model) && ($this->item->model->fieldset)) { + $fields = $this->item->model->fieldset->fields; + } + + return new Content( + markdown: 'mail.markdown.checkin-asset', + with: [ + 'item' => $this->item, + 'status' => $this->item->assetstatus?->name, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'fields' => $fields, + 'expected_checkin' => $this->expected_checkin, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckinLicenseMail.php b/app/Mail/CheckinLicenseMail.php new file mode 100644 index 0000000000..6895fb9695 --- /dev/null +++ b/app/Mail/CheckinLicenseMail.php @@ -0,0 +1,71 @@ +target = $checkedOutTo; + $this->item = $licenseSeat; + $this->admin = $checkedInBy; + $this->note = $note; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.License_Checkin_Notification'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'mail.markdown.checkin-license', + with: [ + 'license_seat' => $this->item, + 'license' => $this->item->license, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutAccessoryMail.php b/app/Mail/CheckoutAccessoryMail.php new file mode 100644 index 0000000000..64c02e31ed --- /dev/null +++ b/app/Mail/CheckoutAccessoryMail.php @@ -0,0 +1,82 @@ +item = $accessory; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->checkout_qty = $accessory->checkout_qty; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: (trans('mail.Accessory_Checkout_Notification')), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + Log::debug($this->item->getImageUrl()); + $eula = $this->item->getEula(); + $req_accept = $this->item->requireAcceptance(); + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + + return new Content( + markdown: 'mail.markdown.checkout-accessory', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + 'checkout_qty' => $this->checkout_qty, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutAssetMail.php b/app/Mail/CheckoutAssetMail.php new file mode 100644 index 0000000000..7ac20861ed --- /dev/null +++ b/app/Mail/CheckoutAssetMail.php @@ -0,0 +1,147 @@ +item = $asset; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + + $this->settings = Setting::getSettings(); + + $this->last_checkout = ''; + $this->expected_checkin = ''; + + $this->firstTimeSending = $firstTimeSending; + + if ($this->item->last_checkout) { + $this->last_checkout = Helper::getFormattedDateObject($this->item->last_checkout, 'date', + false); + } + + if ($this->item->expected_checkin) { + $this->expected_checkin = Helper::getFormattedDateObject($this->item->expected_checkin, 'date', + false); + } + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: $this->getSubject(), + ); + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return Content + */ + public function content(): Content + { + $this->item->load('assetstatus'); + $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; + $req_accept = $this->requiresAcceptance(); + $fields = []; + + // Check if the item has custom fields associated with it + if (($this->item->model) && ($this->item->model->fieldset)) { + $fields = $this->item->model->fieldset->fields; + } + + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + + return new Content( + markdown: 'mail.markdown.checkout-asset', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'status' => $this->item->assetstatus?->name, + 'note' => $this->note, + 'target' => $this->target, + 'fields' => $fields, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + 'last_checkout' => $this->last_checkout, + 'expected_checkin' => $this->expected_checkin, + 'introduction_line' => $this->introductionLine(), + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } + + private function getSubject(): string + { + if ($this->firstTimeSending) { + return trans('mail.Asset_Checkout_Notification'); + } + + return trans('mail.unaccepted_asset_reminder'); + } + + private function introductionLine(): string + { + if ($this->firstTimeSending && $this->requiresAcceptance()) { + return trans('mail.new_item_checked_with_acceptance'); + } + + if ($this->firstTimeSending && !$this->requiresAcceptance()) { + return trans('mail.new_item_checked'); + } + + if (!$this->firstTimeSending && $this->requiresAcceptance()) { + return trans('mail.recent_item_checked'); + } + + // we shouldn't get here but let's send a default message just in case + return trans('new_item_checked'); + } + + private function requiresAcceptance(): int|bool + { + return method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; + } +} diff --git a/app/Mail/CheckoutConsumableMail.php b/app/Mail/CheckoutConsumableMail.php new file mode 100644 index 0000000000..6a3d806790 --- /dev/null +++ b/app/Mail/CheckoutConsumableMail.php @@ -0,0 +1,84 @@ +item = $consumable; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->qty = $consumable->checkout_qty; + + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Confirm_consumable_delivery'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + + $eula = $this->item->getEula(); + $req_accept = $this->item->requireAcceptance(); + + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + + return new Content( + markdown: 'mail.markdown.checkout-consumable', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + 'qty' => $this->qty, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutLicenseMail.php b/app/Mail/CheckoutLicenseMail.php new file mode 100644 index 0000000000..9462c6c332 --- /dev/null +++ b/app/Mail/CheckoutLicenseMail.php @@ -0,0 +1,79 @@ +item = $licenseSeat; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Confirm_license_delivery'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; + $req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; + + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + return new Content( + markdown: 'mail.markdown.checkout-license', + with: [ + 'license_seat' => $this->item, + 'license' => $this->item->license, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/ExpiringAssetsMail.php b/app/Mail/ExpiringAssetsMail.php new file mode 100644 index 0000000000..13d322f060 --- /dev/null +++ b/app/Mail/ExpiringAssetsMail.php @@ -0,0 +1,62 @@ +assets = $params; + $this->threshold = $threshold; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Expiring_Assets_Report'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'notifications.markdown.report-expiring-assets', + with: [ + 'assets' => $this->assets, + 'threshold' => $this->threshold, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/ExpiringLicenseMail.php b/app/Mail/ExpiringLicenseMail.php new file mode 100644 index 0000000000..77d94df637 --- /dev/null +++ b/app/Mail/ExpiringLicenseMail.php @@ -0,0 +1,62 @@ +licenses = $params; + $this->threshold = $threshold; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.Expiring_Licenses_Report'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'notifications.markdown.report-expiring-licenses', + with: [ + 'licenses' => $this->licenses, + 'threshold' => $this->threshold, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/SendUpcomingAuditMail.php b/app/Mail/SendUpcomingAuditMail.php new file mode 100644 index 0000000000..03faa081eb --- /dev/null +++ b/app/Mail/SendUpcomingAuditMail.php @@ -0,0 +1,65 @@ +assets = $params; + $this->threshold = $threshold; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans_choice('mail.upcoming-audits', $this->assets->count(), ['count' => $this->assets->count(), 'threshold' => $this->threshold]), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + + + return new Content( + + markdown: 'notifications.markdown.upcoming-audits', + with: [ + 'assets' => $this->assets, + 'threshold' => $this->threshold, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/UnacceptedAssetReminderMail.php b/app/Mail/UnacceptedAssetReminderMail.php new file mode 100644 index 0000000000..0e4473aaad --- /dev/null +++ b/app/Mail/UnacceptedAssetReminderMail.php @@ -0,0 +1,68 @@ +count = $count; + $this->target = $checkout_info?->assignedTo; + $this->acceptance = $checkout_info; + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address'), config('mail.from.name')); + + return new Envelope( + from: $from, + subject: trans('mail.unaccepted_asset_reminder'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + $accept_url = route('account.accept'); + + return new Content( + markdown: 'notifications.markdown.asset-reminder', + with: [ + 'count' => $this->count, + 'assigned_to' => $this->target?->present()->fullName, + 'link' => route('account.accept'), + 'accept_url' => $accept_url, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/Accessory.php b/app/Models/Accessory.php index a234b1e570..2f13a130d3 100755 --- a/app/Models/Accessory.php +++ b/app/Models/Accessory.php @@ -61,9 +61,10 @@ class Accessory extends SnipeModel 'qty' => 'required|integer|min:1', 'category_id' => 'required|integer|exists:categories,id', 'company_id' => 'integer|nullable', + 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'min_amt' => 'integer|min:0|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0', - 'purchase_date' => 'date_format:Y-m-d|nullable', + 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', + 'purchase_date' => 'date_format:Y-m-d|nullable', ]; @@ -253,9 +254,22 @@ class Accessory extends SnipeModel * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function users() + public function checkouts() { - return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->withPivot('id', 'created_at', 'note')->withTrashed(); + return $this->hasMany(\App\Models\AccessoryCheckout::class, 'accessory_id') + ->with('assignedTo'); + } + + /** + * Establishes the accessory -> admin user relationship + * + * @author A. Gianotto + * @since [v7.0.13] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); } /** @@ -267,7 +281,9 @@ class Accessory extends SnipeModel */ public function hasUsers() { - return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->count(); + return $this->hasMany(\App\Models\AccessoryCheckout::class, 'accessory_id') + ->where('assigned_type', User::class) + ->count(); } /** @@ -292,7 +308,7 @@ class Accessory extends SnipeModel */ public function checkin_email() { - return $this->category->checkin_email; + return $this->category?->checkin_email; } /** @@ -329,11 +345,24 @@ class Accessory extends SnipeModel } + /** + * Check how many items within an accessory are checked out + * + * @author [A. Gianotto] [] + * @since [v5.0] + * @return int + */ + public function numCheckedOut() + { + return $this->checkouts_count ?? $this->checkouts()->count(); + } + + /** * Check how many items of an accessory remain. * - * In order to use this model method, you MUST call withCount('users as users_count') - * on the eloquent query in the controller, otherwise $this->>users_count will be null and + * In order to use this model method, you MUST call withCount('checkouts as checkouts_count') + * on the eloquent query in the controller, otherwise $this->checkouts_count will be null and * bad things happen. * * @author [A. Gianotto] [] @@ -342,11 +371,11 @@ class Accessory extends SnipeModel */ public function numRemaining() { - $checkedout = $this->users_count; + $checkedout = $this->numCheckedOut(); $total = $this->qty; $remaining = $total - $checkedout; - return (int) $remaining; + return $remaining; } /** @@ -357,12 +386,12 @@ class Accessory extends SnipeModel */ public function declinedCheckout(User $declinedBy, $signature) { - if (is_null($accessory_user = \DB::table('accessories_users')->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) { + if (is_null($accessory_checkout = AccessoryCheckout::userAssigned()->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) { // Redirect to the accessory management page with error return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); } - $accessory_user->limit(1)->delete(); + $accessory_checkout->limit(1)->delete(); } /** @@ -394,6 +423,16 @@ class Accessory extends SnipeModel * ----------------------------------------------- **/ + + /** + * Query builder scope to order on created_by name + * + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'accessories.created_by', '=', 'admin_sort.id')->select('accessories.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + /** * Query builder scope to order on company * diff --git a/app/Models/AccessoryCheckout.php b/app/Models/AccessoryCheckout.php new file mode 100755 index 0000000000..61ffcd08e5 --- /dev/null +++ b/app/Models/AccessoryCheckout.php @@ -0,0 +1,179 @@ + accessory relationship + * + * @author [A. Kroeger] + * @since [v7.0.9] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function accessory() + { + return $this->hasOne(Accessory::class, 'id', 'accessory_id'); + } + + public function accessories() + { + return $this->hasMany(Accessory::class, 'id', 'accessory_id'); + } + /** + * Establishes the accessory checkout -> user relationship + * + * @author [A. Kroeger] + * @since [v7.0.9] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->hasOne(\App\Models\User::class, 'id', 'created_by'); + } + + /** + * Get the target this asset is checked out to + * + * @author [A. Kroeger] + * @since [v7.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function assignedTo() + { + return $this->morphTo('assigned', 'assigned_type', 'assigned_to')->withTrashed(); + } + + /** + * Gets the lowercased name of the type of target the asset is assigned to + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return string + */ + public function assignedType() + { + return $this->assigned_type ? strtolower(class_basename($this->assigned_type)) : null; + } + + /** + * Determines whether the accessory is checked out to a user + * + * Even though we allow for checkout to things beyond users + * this method is an easy way of seeing if we are checked out to a user. + * + * @author [A. Kroeger] + * @since [v7.0] + */ + public function checkedOutToUser(): bool + { + return $this->assigned_type == User::class; + } + + public function checkedOutToLocation(): bool + { + return $this->assigned_type == Location::class; + } + + public function checkedOutToAsset(): bool + { + return $this->assigned_type == Asset::class; + } + + public function scopeUserAssigned(Builder $query): void + { + $query->where('assigned_type', '=', User::class); + } + + public function scopeLocationAssigned(Builder $query): void + { + $query->where('assigned_type', '=', Location::class); + } + + public function scopeAssetsAssigned(Builder $query): void + { + $query->where('assigned_type', '=', Asset::class); + } + + /** + * Run additional, advanced searches. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $terms The search terms + * @return \Illuminate\Database\Eloquent\Builder + */ + public function advancedTextSearch(Builder $query, array $terms) + { + + $userQuery = User::where(function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('first_name', 'like', $search_str) + ->orWhere('last_name', 'like', $search_str) + ->orWhere('note', 'like', $search_str); + } + })->select('id'); + + $locationQuery = Location::where(function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('name', 'like', $search_str); + } + })->select('id'); + + $assetQuery = Asset::where(function ($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('name', 'like', $search_str); + } + })->select('id'); + + $query->where(function ($query) use ($userQuery) { + $query->where('assigned_type', User::class) + ->whereIn('assigned_to', $userQuery); + })->orWhere(function($query) use ($locationQuery) { + $query->where('assigned_type', Location::class) + ->whereIn('assigned_to', $locationQuery); + })->orWhere(function($query) use ($assetQuery) { + $query->where('assigned_type', Asset::class) + ->whereIn('assigned_to', $assetQuery); + })->orWhere(function($query) use ($terms) { + foreach ($terms as $term) { + $search_str = '%' . $term . '%'; + $query->where('note', 'like', $search_str); + } + }); + + return $query; + } + + +} diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index 90e0e884f8..ea80a21bfa 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -4,10 +4,9 @@ namespace App\Models; use App\Models\Traits\Searchable; use App\Presenters\Presentable; -use Carbon; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Support\Facades\Auth; /** * Model for the Actionlog (the table that keeps a historical log of @@ -17,10 +16,12 @@ use Illuminate\Support\Facades\Auth; */ class Actionlog extends SnipeModel { + use CompanyableTrait; use HasFactory; // This is to manually set the source (via setActionSource()) for determineActionSource() protected ?string $source = null; + protected $with = ['adminuser']; protected $presenter = \App\Presenters\ActionlogPresenter::class; use SoftDeletes; @@ -31,7 +32,7 @@ class Actionlog extends SnipeModel protected $fillable = [ 'created_at', 'item_type', - 'user_id', + 'created_by', 'item_id', 'action_type', 'note', @@ -51,10 +52,14 @@ class Actionlog extends SnipeModel 'action_type', 'note', 'log_meta', - 'user_id', + 'created_by', 'remote_ip', 'user_agent', - 'action_source' + 'item_type', + 'target_type', + 'action_source', + 'created_at', + 'action_date', ]; /** @@ -63,10 +68,28 @@ class Actionlog extends SnipeModel * @var array */ protected $searchableRelations = [ - 'company' => ['name'], - 'admin' => ['first_name','last_name','username', 'email'], - 'user' => ['first_name','last_name','username', 'email'], - 'assets' => ['asset_tag','name'], + 'company' => ['name'], + 'adminuser' => ['first_name','last_name','username', 'email'], + 'user' => ['first_name','last_name','username', 'email'], + 'assets' => ['asset_tag','name', 'serial', 'order_number', 'notes', 'purchase_date'], + 'assets.model' => ['name', 'model_number', 'eol', 'notes'], + 'assets.model.category' => ['name', 'notes'], + 'assets.model.manufacturer' => ['name', 'notes'], + 'licenses' => ['name', 'serial', 'notes', 'order_number', 'license_email', 'license_name', 'purchase_order', 'purchase_date'], + 'licenses.category' => ['name', 'notes'], + 'licenses.supplier' => ['name'], + 'consumables' => ['name', 'notes', 'order_number', 'model_number', 'item_no', 'purchase_date'], + 'consumables.category' => ['name', 'notes'], + 'consumables.location' => ['name', 'notes'], + 'consumables.supplier' => ['name', 'notes'], + 'components' => ['name', 'notes', 'purchase_date'], + 'components.category' => ['name', 'notes'], + 'components.location' => ['name', 'notes'], + 'components.supplier' => ['name', 'notes'], + 'accessories' => ['name', 'purchase_date'], + 'accessories.category' => ['name'], + 'accessories.location' => ['name', 'notes'], + 'accessories.supplier' => ['name', 'notes'], ]; /** @@ -81,16 +104,22 @@ class Actionlog extends SnipeModel parent::boot(); static::creating(function (self $actionlog) { // If the admin is a superadmin, let's see if the target instead has a company. - if (Auth::user() && Auth::user()->isSuperUser()) { + if (auth()->user() && auth()->user()->isSuperUser()) { if ($actionlog->target) { $actionlog->company_id = $actionlog->target->company_id; } elseif ($actionlog->item) { $actionlog->company_id = $actionlog->item->company_id; } - } elseif (Auth::user() && Auth::user()->company) { - $actionlog->company_id = Auth::user()->company_id; + } elseif (auth()->user() && auth()->user()->company) { + $actionlog->company_id = auth()->user()->company_id; } + + if ($actionlog->action_date == '') { + $actionlog->action_date = Carbon::now(); + } + }); + } @@ -131,6 +160,54 @@ class Actionlog extends SnipeModel return $this->hasMany(\App\Models\Asset::class, 'id', 'item_id'); } + /** + * Establishes the actionlog -> license relationship + * + * @author [A. Gianotto] [] + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function licenses() + { + return $this->hasMany(\App\Models\License::class, 'id', 'item_id'); + } + + /** + * Establishes the actionlog -> consumable relationship + * + * @author [A. Gianotto] [] + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function consumables() + { + return $this->hasMany(\App\Models\Consumable::class, 'id', 'item_id'); + } + + /** + * Establishes the actionlog -> consumable relationship + * + * @author [A. Gianotto] [] + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function accessories() + { + return $this->hasMany(\App\Models\Accessory::class, 'id', 'item_id'); + } + + /** + * Establishes the actionlog -> components relationship + * + * @author [A. Gianotto] [] + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function components() + { + return $this->hasMany(\App\Models\Component::class, 'id', 'item_id'); + } + /** * Establishes the actionlog -> item type relationship * @@ -197,9 +274,9 @@ class Actionlog extends SnipeModel * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { - return $this->belongsTo(User::class, 'user_id') + return $this->belongsTo(User::class, 'created_by') ->withTrashed(); } @@ -290,16 +367,22 @@ class Actionlog extends SnipeModel public function daysUntilNextAudit($monthInterval = 12, $asset = null) { $now = Carbon::now(); - $last_audit_date = $this->created_at; - $next_audit = $last_audit_date->addMonth($monthInterval); - $next_audit_days = $now->diffInDays($next_audit); + $last_audit_date = $this->created_at; // this is the action log's created at, not the asset itself + $next_audit = $last_audit_date->addMonth($monthInterval); // this actually *modifies* the $last_audit_date + $next_audit_days = (int) round($now->diffInDays($next_audit, true)); + $override_default_next = $next_audit; // Override the default setting for interval if the asset has its own next audit date if (($asset) && ($asset->next_audit_date)) { - $override_default_next = \Carbon::parse($asset->next_audit_date); - $next_audit_days = $override_default_next->diffInDays($now); + $override_default_next = Carbon::parse($asset->next_audit_date); + $next_audit_days = (int) round($override_default_next->diffInDays($now, true)); } + // Show as negative number if the next audit date is before the audit date we're looking at + if ($this->created_at > $override_default_next) { + $next_audit_days = '-'.$next_audit_days; + } + return $next_audit_days; } @@ -372,4 +455,9 @@ class Actionlog extends SnipeModel { $this->source = $source; } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'action_logs.created_by', '=', 'admin_sort.id')->select('action_logs.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Asset.php b/app/Models/Asset.php index a3d603551f..28b3a90337 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Events\AssetCheckedOut; use App\Events\CheckoutableCheckedOut; use App\Exceptions\CheckoutNotAllowed; use App\Helpers\Helper; @@ -31,6 +30,7 @@ class Asset extends Depreciable { protected $presenter = AssetPresenter::class; + protected $with = ['model', 'adminuser']; use CompanyableTrait; use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait; @@ -43,16 +43,16 @@ class Asset extends Depreciable /** * Run after the checkout acceptance was declined by the user - * + * * @param User $acceptedBy * @param string $signature - */ + */ public function declinedCheckout(User $declinedBy, $signature) { $this->assigned_to = null; $this->assigned_type = null; - $this->accepted = null; - $this->save(); + $this->accepted = null; + $this->save(); } /** @@ -97,35 +97,38 @@ class Asset extends Depreciable ]; protected $rules = [ - 'model_id' => 'required|integer|exists:models,id,deleted_at,NULL|not_array', - 'status_id' => 'required|integer|exists:status_labels,id', - 'asset_tag' => 'required|min:1|max:255|unique_undeleted:assets,asset_tag|not_array', - 'name' => 'nullable|max:255', - 'company_id' => 'nullable|integer|exists:companies,id', - 'warranty_months' => 'nullable|numeric|digits_between:0,240', - 'last_checkout' => 'nullable|date_format:Y-m-d H:i:s', - 'last_checkin' => 'nullable|date_format:Y-m-d H:i:s', - 'expected_checkin' => 'nullable|date', - 'last_audit_date' => 'nullable|date_format:Y-m-d H:i:s', - // 'next_audit_date' => 'nullable|date|after:last_audit_date', - 'next_audit_date' => 'nullable|date', - 'location_id' => 'nullable|exists:locations,id', - 'rtd_location_id' => 'nullable|exists:locations,id', - 'purchase_date' => 'nullable|date|date_format:Y-m-d', - 'serial' => 'nullable|unique_undeleted:assets,serial', - 'purchase_cost' => 'nullable|numeric|gte:0', - 'supplier_id' => 'nullable|exists:suppliers,id', - 'asset_eol_date' => 'nullable|date', - 'eol_explicit' => 'nullable|boolean', - 'byod' => 'nullable|boolean', - 'order_number' => 'nullable|string|max:191', - 'notes' => 'nullable|string|max:65535', - 'assigned_to' => 'nullable|integer', - 'requestable' => 'nullable|boolean', + 'model_id' => ['required', 'integer', 'exists:models,id,deleted_at,NULL', 'not_array'], + 'status_id' => ['required', 'integer', 'exists:status_labels,id'], + 'asset_tag' => ['required', 'min:1', 'max:255', 'unique_undeleted:assets,asset_tag', 'not_array'], + 'name' => ['nullable', 'max:255'], + 'company_id' => ['nullable', 'integer', 'exists:companies,id'], + 'warranty_months' => ['nullable', 'numeric', 'digits_between:0,240'], + 'last_checkout' => ['nullable', 'date_format:Y-m-d H:i:s'], + 'last_checkin' => ['nullable', 'date_format:Y-m-d H:i:s'], + 'expected_checkin' => ['nullable', 'date'], + 'last_audit_date' => ['nullable', 'date_format:Y-m-d H:i:s'], + 'next_audit_date' => ['nullable', 'date'], + 'location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], + 'rtd_location_id' => ['nullable', 'exists:locations,id', 'fmcs_location'], + 'purchase_date' => ['nullable', 'date', 'date_format:Y-m-d'], + 'serial' => ['nullable', 'unique_undeleted:assets,serial'], + 'purchase_cost' => ['nullable', 'numeric', 'gte:0', 'max:9999999999999'], + 'supplier_id' => ['nullable', 'exists:suppliers,id'], + 'asset_eol_date' => ['nullable', 'date'], + 'eol_explicit' => ['nullable', 'boolean'], + 'byod' => ['nullable', 'boolean'], + 'order_number' => ['nullable', 'string', 'max:191'], + 'notes' => ['nullable', 'string', 'max:65535'], + 'assigned_to' => ['nullable', 'integer', 'required_with:assigned_type'], + 'assigned_type' => ['nullable', 'required_with:assigned_to', 'in:'.User::class.",".Location::class.",".Asset::class], + 'requestable' => ['nullable', 'boolean'], + 'assigned_user' => ['integer', 'nullable', 'exists:users,id,deleted_at,NULL'], + 'assigned_location' => ['integer', 'nullable', 'exists:locations,id,deleted_at,NULL', 'fmcs_location'], + 'assigned_asset' => ['integer', 'nullable', 'exists:assets,id,deleted_at,NULL'] ]; - /** + /** * The attributes that are mass assignable. * * @var array @@ -211,6 +214,31 @@ class Asset extends Depreciable $this->attributes['expected_checkin'] = $value; } + + + public function customFieldValidationRules() + { + + $customFieldValidationRules = []; + + if (($this->model) && ($this->model->fieldset)) { + + foreach ($this->model->fieldset->fields as $field) { + + if ($field->format == 'BOOLEAN'){ + $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); + } + } + + $customFieldValidationRules += $this->model->fieldset->validation_rules(); + } + + return $customFieldValidationRules; + + } + + + /** * This handles the custom field validation for assets * @@ -218,29 +246,7 @@ class Asset extends Depreciable */ public function save(array $params = []) { - if ($this->model_id != '') { - $model = AssetModel::find($this->model_id); - - if (($model) && ($model->fieldset)) { - - foreach ($model->fieldset->fields as $field){ - if($field->format == 'BOOLEAN'){ - $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); - } - } - - $this->rules += $model->fieldset->validation_rules(); - - if ($this->model->fieldset){ - foreach ($this->model->fieldset->fields as $field){ - if($field->format == 'BOOLEAN'){ - $this->{$field->db_column} = filter_var($this->{$field->db_column}, FILTER_VALIDATE_BOOLEAN); - } - } - } - } - } - + $this->rules += $this->customFieldValidationRules(); return parent::save($params); } @@ -252,7 +258,7 @@ class Asset extends Depreciable /** * Returns the warranty expiration date as Carbon object - * @return \Carbon|null + * @return \Carbon\Carbon|null */ public function getWarrantyExpiresAttribute() { @@ -366,10 +372,10 @@ class Asset extends Depreciable if ($this->save()) { if (is_int($admin)) { $checkedOutBy = User::findOrFail($admin); - } elseif (get_class($admin) === \App\Models\User::class) { + } elseif ($admin && get_class($admin) === \App\Models\User::class) { $checkedOutBy = $admin; } else { - $checkedOutBy = Auth::user(); + $checkedOutBy = auth()->user(); } event(new CheckoutableCheckedOut($this, $target, $checkedOutBy, $note, $originalValues)); @@ -411,6 +417,17 @@ class Asset extends Depreciable return $this->rules; } + public function customFieldsForCheckinCheckout($checkin_checkout) { + // Check to see if any of the custom fields were included on the form and if they have any values + if (($this->model) && ($this->model->fieldset) && ($this->model->fieldset->fields)) { + foreach ($this->model->fieldset->fields as $field) { + if (($field->{$checkin_checkout} == 1) && (request()->has($field->db_column))){ + $this->{$field->db_column} = request()->get($field->db_column); + } + } + } + } + /** * Establishes the asset -> depreciation relationship @@ -521,6 +538,18 @@ class Asset extends Depreciable return $this->morphMany(self::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed(); } + /** + * Establishes the accessory -> asset assignment relationship + * + * @author A. Gianotto + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function assignedAccessories() + { + return $this->morphMany(\App\Models\AccessoryCheckout::class, 'assigned', 'assigned_type', 'assigned_to'); + } + /** * Get the asset's location based on the assigned user @@ -627,6 +656,8 @@ class Asset extends Depreciable return Storage::disk('public')->url(app('assets_upload_path').e($this->image)); } elseif ($this->model && ! empty($this->model->image)) { return Storage::disk('public')->url(app('models_upload_path').e($this->model->image)); + } elseif ($this->model?->category && ! empty($this->model->category->image)) { + return Storage::disk('public')->url(app('categories_upload_path').e($this->model->category->image)); } return false; @@ -662,6 +693,21 @@ class Asset extends Depreciable ->withTrashed(); } + + /** + * Get the list of audits for this asset + * + * @author [A. Gianotto] [] + * @since [v2.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function audits() + { + return $this->assetlog()->where('action_type', '=', 'audit') + ->orderBy('created_at', 'desc') + ->withTrashed(); + } + /** * Get the list of checkins for this asset * @@ -707,7 +753,7 @@ class Asset extends Depreciable } /** - * Get action logs history for this asset + * Get user who created the item * * @author [A. Gianotto] [] * @since [v1.0] @@ -715,7 +761,7 @@ class Asset extends Depreciable */ public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(\App\Models\User::class, 'created_by'); } @@ -760,7 +806,8 @@ class Asset extends Depreciable ->whereNotNull('warranty_months') ->whereNotNull('purchase_date') ->whereNull('deleted_at') - ->whereRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH) <= DATE(NOW() + INTERVAL ' + ->NotArchived() + ->whereRaw('DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) <= DATE_ADD(NOW(), INTERVAL ' . $days . ' DAY) AND DATE_ADD(`purchase_date`, INTERVAL `warranty_months` MONTH) > NOW()') ->orderByRaw('DATE_ADD(`purchase_date`,INTERVAL `warranty_months` MONTH)') @@ -916,6 +963,7 @@ class Asset extends Depreciable return $this->model->category->require_acceptance; } + return false; } @@ -928,9 +976,20 @@ class Asset extends Depreciable * */ public function checkInvalidNextAuditDate() { - if (($this->last_audit_date) && ($this->next_audit_date) && ($this->last_audit_date > $this->next_audit_date)) { + + // Deliberately parse the dates as Y-m-d (without H:i:s) to compare them + if ($this->last_audit_date) { + $last = Carbon::parse($this->last_audit_date)->format('Y-m-d'); + } + + if ($this->next_audit_date) { + $next = Carbon::parse($this->next_audit_date)->format('Y-m-d'); + } + + if ((isset($last) && (isset($next))) && ($last > $next)) { return true; } + return false; } @@ -947,11 +1006,12 @@ class Asset extends Depreciable { if (($this->model) && ($this->model->category)) { - if ($this->model->category->eula_text) { + if (($this->model->category->eula_text) && ($this->model->category->use_default_eula === 0)) { return Helper::parseEscapedMarkedown($this->model->category->eula_text); - } elseif ($this->model->category->use_default_eula == '1') { + } elseif ($this->model->category->use_default_eula === 1) { return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text); } else { + return false; } } @@ -1244,7 +1304,7 @@ class Asset extends Depreciable public function scopeDueForAudit($query, $settings) { - $interval = $settings->audit_warning_days ?? 0; + $interval = (int) $settings->audit_warning_days ?? 0; $today = Carbon::now(); $interval_date = $today->copy()->addDays($interval)->format('Y-m-d'); @@ -1312,7 +1372,7 @@ class Asset extends Depreciable public function scopeDueForCheckin($query, $settings) { - $interval = $settings->audit_warning_days ?? 0; + $interval = (int) $settings->due_checkin_days ?? 0; $today = Carbon::now(); $interval_date = $today->copy()->addDays($interval)->format('Y-m-d'); @@ -1419,7 +1479,7 @@ class Asset extends Depreciable * @return \Illuminate\Database\Query\Builder Modified query builder */ - public function scopeRequestableAssets($query) + public function scopeRequestableAssets($query): Builder { $table = $query->getModel()->getTable(); @@ -1558,7 +1618,7 @@ class Asset extends Depreciable $leftJoin->on('assets_dept_users.id', '=', 'assets.assigned_to') ->where('assets.assigned_type', '=', User::class); })->where(function ($query) use ($search) { - $query->where('assets_dept_users.department_id', '=', $search); + $query->whereIn('assets_dept_users.department_id', $search); })->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug } @@ -1691,7 +1751,7 @@ class Asset extends Depreciable }); }); } - + /** * THIS CLUNKY BIT IS VERY IMPORTANT @@ -1712,7 +1772,7 @@ class Asset extends Depreciable * assets.location would fail, as that field doesn't exist -- plus we're already searching * against those relationships earlier in this method. * - * - snipe + * - snipe * */ @@ -1757,6 +1817,20 @@ class Asset extends Depreciable } + /** + * Query builder scope to order on created_by name + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'assets.created_by', '=', 'admin_sort.id')->select('assets.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + + /** * Query builder scope to order on assigned user * @@ -1808,7 +1882,9 @@ class Asset extends Depreciable public function scopeInCategory($query, $category_id) { return $query->join('models as category_models', 'assets.model_id', '=', 'category_models.id') - ->join('categories', 'category_models.category_id', '=', 'categories.id')->where('category_models.category_id', '=', $category_id); + ->join('categories', 'category_models.category_id', '=', 'categories.id') + ->whereIn('category_models.category_id', (!is_array($category_id) ? explode(',',$category_id): $category_id)); + //->whereIn('category_models.category_id', $category_id); } /** @@ -1822,7 +1898,7 @@ class Asset extends Depreciable public function scopeByManufacturer($query, $manufacturer_id) { return $query->join('models', 'assets.model_id', '=', 'models.id') - ->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->where('models.manufacturer_id', '=', $manufacturer_id); + ->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->whereIn('models.manufacturer_id', (!is_array($manufacturer_id) ? explode(',',$manufacturer_id): $manufacturer_id)); } diff --git a/app/Models/AssetMaintenance.php b/app/Models/AssetMaintenance.php index 5f66783cbb..1c07496225 100644 --- a/app/Models/AssetMaintenance.php +++ b/app/Models/AssetMaintenance.php @@ -26,12 +26,12 @@ class AssetMaintenance extends Model implements ICompanyableChild protected $table = 'asset_maintenances'; protected $rules = [ 'asset_id' => 'required|integer', - 'supplier_id' => 'required|integer', + 'supplier_id' => 'nullable|integer', 'asset_maintenance_type' => 'required', 'title' => 'required|max:100', 'is_warranty' => 'boolean', 'start_date' => 'required|date_format:Y-m-d', - 'completion_date' => 'date_format:Y-m-d|nullable', + 'completion_date' => 'date_format:Y-m-d|nullable|after_or_equal:start_date', 'notes' => 'string|nullable', 'cost' => 'numeric|nullable', ]; @@ -166,6 +166,7 @@ class AssetMaintenance extends Model implements ICompanyableChild return $this->belongsTo(\App\Models\Asset::class, 'asset_id') ->withTrashed(); } + /** * Get the admin who created the maintenance @@ -174,9 +175,9 @@ class AssetMaintenance extends Model implements ICompanyableChild * @author A. Gianotto * @version v3.0 */ - public function admin() + public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id') + return $this->belongsTo(\App\Models\User::class, 'created_by') ->withTrashed(); } @@ -207,20 +208,6 @@ class AssetMaintenance extends Model implements ICompanyableChild } - /** - * Query builder scope to order on admin user - * - * @param \Illuminate\Database\Query\Builder $query Query builder instance - * @param string $order Order - * - * @return \Illuminate\Database\Query\Builder Modified query builder - */ - public function scopeOrderAdmin($query, $order) - { - return $query->leftJoin('users', 'asset_maintenances.user_id', '=', 'users.id') - ->orderBy('users.first_name', $order) - ->orderBy('users.last_name', $order); - } /** * Query builder scope to order on asset tag @@ -278,4 +265,12 @@ class AssetMaintenance extends Model implements ICompanyableChild ->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id') ->orderBy('maintained_asset_status.name', $order); } + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'asset_maintenances.created_by', '=', 'admin_sort.id')->select('asset_maintenances.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index c5fb9284aa..8d60474b96 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; +use \App\Presenters\AssetModelPresenter; +use App\Http\Traits\TwoColumnUniqueUndeletedTrait; /** * Model for Asset Models. Asset Models contain higher level @@ -20,31 +22,35 @@ class AssetModel extends SnipeModel { use HasFactory; use SoftDeletes; - protected $presenter = \App\Presenters\AssetModelPresenter::class; use Loggable, Requestable, Presentable; + use TwoColumnUniqueUndeletedTrait; + /** + * Whether the model should inject its identifier to the unique + * validation rules before attempting validation. If this property + * is not set in the model it will default to true. + * + * @var bool + */ + + protected $injectUniqueIdentifier = true; + use ValidatingTrait; protected $table = 'models'; - protected $hidden = ['user_id', 'deleted_at']; + protected $presenter = AssetModelPresenter::class; // Declare the rules for the model validation + + protected $rules = [ - 'name' => 'required|min:1|max:255', - 'model_number' => 'max:255|nullable', + 'name' => 'string|required|min:1|max:255|two_column_unique_undeleted:model_number', + 'model_number' => 'string|max:255|nullable|two_column_unique_undeleted:name', 'min_amt' => 'integer|min:0|nullable', 'category_id' => 'required|integer|exists:categories,id', 'manufacturer_id' => 'integer|exists:manufacturers,id|nullable', 'eol' => 'integer:min:0|max:240|nullable', ]; - /** - * Whether the model should inject it's identifier to the unique - * validation rules before attempting validation. If this property - * is not set in the model it will default to true. - * - * @var bool - */ - protected $injectUniqueIdentifier = true; - use ValidatingTrait; + /** * The attributes that are mass assignable. @@ -62,7 +68,7 @@ class AssetModel extends SnipeModel 'model_number', 'name', 'notes', - 'user_id', + 'requestable', ]; use Searchable; @@ -72,7 +78,12 @@ class AssetModel extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name', 'model_number', 'notes', 'eol']; + protected $searchableAttributes = [ + 'name', + 'model_number', + 'notes', + 'eol' + ]; /** * The relations and their attributes that should be included when searching the model. @@ -85,6 +96,9 @@ class AssetModel extends SnipeModel 'manufacturer' => ['name'], ]; + + + /** * Establishes the model -> assets relationship * @@ -211,6 +225,18 @@ class AssetModel extends SnipeModel ->orderBy('created_at', 'desc'); } + /** + * Get user who created the item + * + * @author [A. Gianotto] [] + * @since [v1.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by')->withTrashed(); + } + /** * ----------------------------------------------- @@ -303,4 +329,14 @@ class AssetModel extends SnipeModel { return $query->leftJoin('custom_fieldsets', 'models.fieldset_id', '=', 'custom_fieldsets.id')->orderBy('custom_fieldsets.name', $order); } + + /** + * Query builder scope to order on created_by name + * + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'models.created_by', '=', 'admin_sort.id')->select('models.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + } diff --git a/app/Models/Category.php b/app/Models/Category.php index 17e41da1fc..0321dfdb26 100755 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -29,17 +29,17 @@ class Category extends SnipeModel use SoftDeletes; protected $table = 'categories'; - protected $hidden = ['user_id', 'deleted_at']; + protected $hidden = ['created_by', 'deleted_at']; protected $casts = [ - 'user_id' => 'integer', + 'created_by' => 'integer', ]; /** * Category validation rules */ public $rules = [ - 'user_id' => 'numeric|nullable', + 'created_by' => 'numeric|nullable', 'name' => 'required|min:1|max:255|two_column_unique_undeleted:category_type', 'require_acceptance' => 'boolean', 'use_default_eula' => 'boolean', @@ -70,7 +70,8 @@ class Category extends SnipeModel 'name', 'require_acceptance', 'use_default_eula', - 'user_id', + 'created_by', + 'notes', ]; use Searchable; @@ -80,7 +81,7 @@ class Category extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name', 'category_type']; + protected $searchableAttributes = ['name', 'category_type', 'notes']; /** * The relations and their attributes that should be included when searching the model. @@ -99,6 +100,14 @@ class Category extends SnipeModel public function isDeletable() { + // We have to check for models as well if the category type is asset + if ($this->category_type == 'asset') { + return Gate::allows('delete', $this) + && ($this->itemCount() == 0) + && ($this->models_count == 0) + && ($this->deleted_at == ''); + } + return Gate::allows('delete', $this) && ($this->itemCount() == 0) && ($this->deleted_at == ''); @@ -171,15 +180,15 @@ class Category extends SnipeModel switch ($this->category_type) { case 'asset': - return $this->assets()->count(); + return $this->assets->count(); case 'accessory': - return $this->accessories()->count(); + return $this->accessories->count(); case 'component': - return $this->components()->count(); + return $this->components->count(); case 'consumable': - return $this->consumables()->count(); + return $this->consumables->count(); case 'license': - return $this->licenses()->count(); + return $this->licenses->count(); default: return 0; } @@ -228,6 +237,11 @@ class Category extends SnipeModel return $this->hasMany(\App\Models\AssetModel::class, 'category_id'); } + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Checks for a category-specific EULA, and if that doesn't exist, * checks for a settings level EULA @@ -286,4 +300,9 @@ class Category extends SnipeModel { return $query->where('require_acceptance', '=', true); } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'categories.created_by', '=', 'admin_sort.id')->select('categories.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/CheckoutAcceptance.php b/app/Models/CheckoutAcceptance.php index 9c501aaf63..e44a330ebc 100644 --- a/app/Models/CheckoutAcceptance.php +++ b/app/Models/CheckoutAcceptance.php @@ -35,7 +35,7 @@ class CheckoutAcceptance extends Model /** * The resource that was is out * - * @return Illuminate\Database\Eloquent\Relations\MorphTo + * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function checkoutable() { @@ -70,7 +70,7 @@ class CheckoutAcceptance extends Model */ public function isCheckedOutTo(User $user) { - return $this->assignedTo->is($user); + return $this->assignedTo?->is($user); } /** diff --git a/app/Models/CheckoutRequest.php b/app/Models/CheckoutRequest.php index b717a332aa..d6a85f2972 100644 --- a/app/Models/CheckoutRequest.php +++ b/app/Models/CheckoutRequest.php @@ -13,7 +13,7 @@ class CheckoutRequest extends Model public function user() { - return $this->belongsTo(User::class); + return $this->belongsTo(User::class, 'user_id', 'id'); } public function requestingUser() diff --git a/app/Models/Company.php b/app/Models/Company.php index ea8a28b7e1..29fa99a4b9 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -67,6 +67,8 @@ final class Company extends SnipeModel 'phone', 'fax', 'email', + 'created_by', + 'notes', ]; private static function isFullMultipleCompanySupportEnabled() @@ -106,7 +108,7 @@ final class Company extends SnipeModel if (! static::isFullMultipleCompanySupportEnabled()) { return static::getIdFromInput($unescaped_input); } else { - $current_user = Auth::user(); + $current_user = auth()->user(); // Super users should be able to set a company to whatever they need if ($current_user->isSuperUser()) { @@ -115,7 +117,7 @@ final class Company extends SnipeModel if ($current_user->company_id != null) { return $current_user->company_id; } else { - return static::getIdFromInput($unescaped_input); + return null; } } } @@ -157,24 +159,24 @@ final class Company extends SnipeModel } - if (Auth::user()) { - Log::warning('Companyable is '.$companyable); - $current_user_company_id = Auth::user()->company_id; + if (auth()->user()) { + // Log::warning('Companyable is '.$companyable); + $current_user_company_id = auth()->user()->company_id; $companyable_company_id = $companyable->company_id; - return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || Auth::user()->isSuperUser(); + return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || auth()->user()->isSuperUser(); } } public static function isCurrentUserAuthorized() { - return (! static::isFullMultipleCompanySupportEnabled()) || (Auth::user()->isSuperUser()); + return (! static::isFullMultipleCompanySupportEnabled()) || (auth()->user()->isSuperUser()); } public static function canManageUsersCompanies() { - return ! static::isFullMultipleCompanySupportEnabled() || Auth::user()->isSuperUser() || - Auth::user()->company_id == null; + return ! static::isFullMultipleCompanySupportEnabled() || auth()->user()->isSuperUser() || + auth()->user()->company_id == null; } /** @@ -186,12 +188,15 @@ final class Company extends SnipeModel */ public function isDeletable() { + return Gate::allows('delete', $this) - && ($this->assets()->count() === 0) - && ($this->accessories()->count() === 0) - && ($this->consumables()->count() === 0) - && ($this->components()->count() === 0) - && ($this->users()->count() === 0); + && (($this->assets_count ?? $this->assets()->count()) === 0) + && (($this->accessories_count ?? $this->accessories()->count()) === 0) + && (($this->licenses_count ?? $this->licenses()->count()) === 0) + && (($this->components_count ?? $this->components()->count()) === 0) + && (($this->consumables_count ?? $this->consumables()->count()) === 0) + && (($this->accessories_count ?? $this->accessories()->count()) === 0) + && (($this->users_count ?? $this->users()->count()) === 0); } /** @@ -200,7 +205,7 @@ final class Company extends SnipeModel */ public static function getIdForUser($unescaped_input) { - if (! static::isFullMultipleCompanySupportEnabled() || Auth::user()->isSuperUser()) { + if (! static::isFullMultipleCompanySupportEnabled() || auth()->user()->isSuperUser()) { return static::getIdFromInput($unescaped_input); } else { return static::getIdForCurrentUser($unescaped_input); @@ -259,7 +264,7 @@ final class Company extends SnipeModel public static function scopeCompanyables($query, $column = 'company_id', $table_name = null) { // If not logged in and hitting this, assume we are on the command line and don't scope?' - if (! static::isFullMultipleCompanySupportEnabled() || (Auth::hasUser() && Auth::user()->isSuperUser()) || (! Auth::hasUser())) { + if (! static::isFullMultipleCompanySupportEnabled() || (Auth::hasUser() && auth()->user()->isSuperUser()) || (! Auth::hasUser())) { return $query; } else { return static::scopeCompanyablesDirectly($query, $column, $table_name); @@ -277,7 +282,7 @@ final class Company extends SnipeModel // Get the company ID of the logged-in user, or set it to null if there is no company associated with the user if (Auth::hasUser()) { - $company_id = Auth::user()->company_id; + $company_id = auth()->user()->company_id; } else { $company_id = null; } @@ -294,6 +299,12 @@ final class Company extends SnipeModel } + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + /** * I legit do not know what this method does, but we can't remove it (yet). * @@ -309,7 +320,7 @@ final class Company extends SnipeModel if (count($companyable_names) == 0) { throw new Exception('No Companyable Children to scope'); - } elseif (! static::isFullMultipleCompanySupportEnabled() || (Auth::hasUser() && Auth::user()->isSuperUser())) { + } elseif (! static::isFullMultipleCompanySupportEnabled() || (Auth::hasUser() && auth()->user()->isSuperUser())) { return $query; } else { $f = function ($q) { @@ -329,4 +340,13 @@ final class Company extends SnipeModel } } + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'companies.created_by', '=', 'admin_sort.id')->select('companies.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + } diff --git a/app/Models/CompanyableTrait.php b/app/Models/CompanyableTrait.php index df67f2be4f..c4b9b89fbf 100644 --- a/app/Models/CompanyableTrait.php +++ b/app/Models/CompanyableTrait.php @@ -8,14 +8,18 @@ trait CompanyableTrait * This trait is used to scope models to the current company. To use this scope on companyable models, * we use the "use Companyable;" statement at the top of the mode. * - * We CANNOT USE THIS ON USERS, as it causes an infinite loop and prevents users from logging in, since this scope will be - * applied to the currently logged in (or logging in) user in addition to the user model for viewing lists of users. - * * @see \App\Models\Company\Company::scopeCompanyables() * @return void */ public static function bootCompanyableTrait() { - static::addGlobalScope(new CompanyableScope); + // In Version 7.0 and before locations weren't scoped by companies, so add a check for the backward compatibility setting + if (__CLASS__ != 'App\Models\Location') { + static::addGlobalScope(new CompanyableScope); + } else { + if (Setting::getSettings()->scope_locations_fmcs == 1) { + static::addGlobalScope(new CompanyableScope); + } + } } } diff --git a/app/Models/Component.php b/app/Models/Component.php index 671b0101c4..0208fb9f68 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -6,6 +6,7 @@ use App\Models\Traits\Searchable; use App\Presenters\Presentable; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\Gate; use Watson\Validating\ValidatingTrait; /** @@ -30,14 +31,16 @@ class Component extends SnipeModel * Category validation rules */ public $rules = [ - 'name' => 'required|min:3|max:255', + 'name' => 'required|min:3|max:191', 'qty' => 'required|integer|min:1', 'category_id' => 'required|integer|exists:categories,id', 'supplier_id' => 'nullable|integer|exists:suppliers,id', 'company_id' => 'integer|nullable|exists:companies,id', + 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'min_amt' => 'integer|min:0|nullable', 'purchase_date' => 'date_format:Y-m-d|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0', + 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', + 'manufacturer_id' => 'integer|exists:manufacturers,id|nullable', ]; /** @@ -60,6 +63,8 @@ class Component extends SnipeModel 'company_id', 'supplier_id', 'location_id', + 'manufacturer_id', + 'model_number', 'name', 'purchase_cost', 'purchase_date', @@ -77,7 +82,15 @@ class Component extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name', 'order_number', 'serial', 'purchase_cost', 'purchase_date', 'notes']; + protected $searchableAttributes = [ + 'name', + 'order_number', + 'serial', + 'purchase_cost', + 'purchase_date', + 'notes', + 'model_number', + ]; /** * The relations and their attributes that should be included when searching the model. @@ -89,9 +102,17 @@ class Component extends SnipeModel 'company' => ['name'], 'location' => ['name'], 'supplier' => ['name'], + 'manufacturer' => ['name'], ]; + public function isDeletable() + { + return Gate::allows('delete', $this) + && ($this->numCheckedOut() === 0) + && ($this->deleted_at == ''); + } + /** * Establishes the components -> action logs -> uploads relationship * @@ -130,7 +151,7 @@ class Component extends SnipeModel */ public function assets() { - return $this->belongsToMany(\App\Models\Asset::class, 'components_assets')->withPivot('id', 'assigned_qty', 'created_at', 'user_id', 'note'); + return $this->belongsToMany(\App\Models\Asset::class, 'components_assets')->withPivot('id', 'assigned_qty', 'created_at', 'created_by', 'note'); } /** @@ -142,9 +163,9 @@ class Component extends SnipeModel * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(\App\Models\User::class, 'created_by'); } /** @@ -183,6 +204,19 @@ class Component extends SnipeModel return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id'); } + + /** + * Establishes the item -> manufacturer relationship + * + * @author [A. Gianotto] [] + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function manufacturer() + { + return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id'); + } + /** * Establishes the component -> action logs relationship * @@ -205,13 +239,28 @@ class Component extends SnipeModel public function numCheckedOut() { $checkedout = 0; - foreach ($this->assets as $checkout) { - $checkedout += $checkout->pivot->assigned_qty; - } - return $checkedout; + // In case there are elements checked out to assets that belong to a different company + // than this asset and full multiple company support is on we'll remove the global scope, + // so they are included in the count. + return $this->uncontrainedAssets->sum('pivot.assigned_qty'); } + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + * + * This allows us to get the assets with assigned components without the company restriction + */ + public function uncontrainedAssets() { + + return $this->belongsToMany(\App\Models\Asset::class, 'components_assets') + ->withPivot('id', 'assigned_qty', 'created_at', 'created_by', 'note') + ->withoutGlobalScope(new CompanyableScope); + + } + + /** * Check how many items within a component are remaining * @@ -306,4 +355,22 @@ class Component extends SnipeModel { return $query->leftJoin('suppliers', 'components.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order); } + + /** + * Query builder scope to order on manufacturer + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderManufacturer($query, $order) + { + return $query->leftJoin('manufacturers', 'components.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order); + } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'components.created_by', '=', 'admin_sort.id')->select('components.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Consumable.php b/app/Models/Consumable.php index 008dffa4ca..b881ec65f3 100644 --- a/app/Models/Consumable.php +++ b/app/Models/Consumable.php @@ -10,12 +10,21 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; +use Illuminate\Database\Eloquent\Relations\Relation; +use App\Presenters\ConsumablePresenter; +use App\Models\Actionlog; +use App\Models\ConsumableAssignment; +use App\Models\User; +use App\Models\Location; +use App\Models\Manufacturer; +use App\Models\Supplier; +use App\Models\Category; class Consumable extends SnipeModel { use HasFactory; - protected $presenter = \App\Presenters\ConsumablePresenter::class; + protected $presenter = ConsumablePresenter::class; use CompanyableTrait; use Loggable, Presentable; use SoftDeletes; @@ -37,11 +46,12 @@ class Consumable extends SnipeModel */ public $rules = [ 'name' => 'required|min:3|max:255', - 'qty' => 'required|integer|min:0', + 'qty' => 'required|integer|min:0|max:99999', 'category_id' => 'required|integer', 'company_id' => 'integer|nullable', - 'min_amt' => 'integer|min:0|nullable', - 'purchase_cost' => 'numeric|nullable|gte:0', + 'location_id' => 'exists:locations,id|nullable|fmcs_location', + 'min_amt' => 'integer|min:0|max:99999|nullable', + 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', 'purchase_date' => 'date_format:Y-m-d|nullable', ]; @@ -66,6 +76,7 @@ class Consumable extends SnipeModel 'item_no', 'location_id', 'manufacturer_id', + 'supplier_id', 'name', 'order_number', 'model_number', @@ -109,7 +120,7 @@ class Consumable extends SnipeModel */ public function uploads() { - return $this->hasMany(\App\Models\Actionlog::class, 'item_id') + return $this->hasMany(Actionlog::class, 'item_id') ->where('item_type', '=', self::class) ->where('action_type', '=', 'uploaded') ->whereNotNull('filename') @@ -145,9 +156,9 @@ class Consumable extends SnipeModel * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(User::class, 'created_by'); } /** @@ -159,7 +170,7 @@ class Consumable extends SnipeModel */ public function consumableAssignments() { - return $this->hasMany(\App\Models\ConsumableAssignment::class); + return $this->hasMany(ConsumableAssignment::class); } /** @@ -183,7 +194,7 @@ class Consumable extends SnipeModel */ public function manufacturer() { - return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id'); + return $this->belongsTo(Manufacturer::class, 'manufacturer_id'); } /** @@ -195,7 +206,7 @@ class Consumable extends SnipeModel */ public function location() { - return $this->belongsTo(\App\Models\Location::class, 'location_id'); + return $this->belongsTo(Location::class, 'location_id'); } /** @@ -207,7 +218,7 @@ class Consumable extends SnipeModel */ public function category() { - return $this->belongsTo(\App\Models\Category::class, 'category_id'); + return $this->belongsTo(Category::class, 'category_id'); } @@ -220,7 +231,7 @@ class Consumable extends SnipeModel */ public function assetlog() { - return $this->hasMany(\App\Models\Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed(); + return $this->hasMany(Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed(); } /** @@ -244,11 +255,10 @@ class Consumable extends SnipeModel * * @author [A. Gianotto] [] * @since [v3.0] - * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function users() + public function users() : Relation { - return $this->belongsToMany(\App\Models\User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('user_id')->withTrashed()->withTimestamps(); + return $this->belongsToMany(User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('created_by')->withTrashed()->withTimestamps(); } /** @@ -260,7 +270,7 @@ class Consumable extends SnipeModel */ public function supplier() { - return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id'); + return $this->belongsTo(Supplier::class, 'supplier_id'); } @@ -274,7 +284,7 @@ class Consumable extends SnipeModel */ public function checkin_email() { - return $this->category->checkin_email; + return $this->category?->checkin_email; } /** @@ -317,10 +327,7 @@ class Consumable extends SnipeModel */ public function numCheckedOut() { - $checkedout = 0; - $checkedout = $this->users->count(); - - return $checkedout; + return $this->consumables_users_count ?? $this->users()->count(); } /** @@ -332,7 +339,7 @@ class Consumable extends SnipeModel */ public function numRemaining() { - $checkedout = $this->users->count(); + $checkedout = $this->numCheckedOut(); $total = $this->qty; $remaining = $total - $checkedout; @@ -420,6 +427,20 @@ class Consumable extends SnipeModel return $query->leftJoin('companies', 'consumables.company_id', '=', 'companies.id')->orderBy('companies.name', $order); } + /** + * Query builder scope to order on remaining + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param string $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderRemaining($query, $order) + { + $order_by = 'consumables.qty - consumables_users_count ' . $order; + return $query->orderByRaw($order_by); + } + /** * Query builder scope to order on supplier * @@ -432,4 +453,9 @@ class Consumable extends SnipeModel { return $query->leftJoin('suppliers', 'consumables.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order); } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as users_sort', 'consumables.created_by', '=', 'users_sort.id')->select('consumables.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order); + } } diff --git a/app/Models/ConsumableAssignment.php b/app/Models/ConsumableAssignment.php index 1e21a7f7d2..4c9a19703e 100644 --- a/app/Models/ConsumableAssignment.php +++ b/app/Models/ConsumableAssignment.php @@ -3,13 +3,19 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Watson\Validating\ValidatingTrait; class ConsumableAssignment extends Model { use CompanyableTrait; + use ValidatingTrait; protected $table = 'consumables_users'; + public $rules = [ + 'assigned_to' => 'required|exists:users,id', + ]; + public function consumable() { return $this->belongsTo(\App\Models\Consumable::class); @@ -20,8 +26,8 @@ class ConsumableAssignment extends Model return $this->belongsTo(\App\Models\User::class, 'assigned_to'); } - public function admin() + public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(\App\Models\User::class, 'created_by'); } } diff --git a/app/Models/CustomField.php b/app/Models/CustomField.php index c1826a94d8..b3742882a2 100644 --- a/app/Models/CustomField.php +++ b/app/Models/CustomField.php @@ -7,7 +7,7 @@ use EasySlugger\Utf8Slugger; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\Rule; -use Schema; +use Illuminate\Support\Facades\Schema; use Watson\Validating\ValidatingTrait; class CustomField extends Model { @@ -79,6 +79,9 @@ class CustomField extends Model 'auto_add_to_fieldsets', 'show_in_listview', 'show_in_email', + 'display_checkout', + 'display_checkin', + 'display_audit', 'show_in_requestable_list', ]; @@ -149,11 +152,6 @@ class CustomField extends Model return true; } - // This is just a dumb thing we have to include because Laraval/Doctrine doesn't - // play well with enums or a table that EVER had enums. :( - $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); - $platform->registerDoctrineTypeMapping('enum', 'string'); - // Rename the field if the name has changed Schema::table(self::$table_name, function ($table) use ($custom_field) { $table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal('name')), $custom_field->convertUnicodeDbSlug()); @@ -188,7 +186,44 @@ class CustomField extends Model { return $this->belongsToMany(\App\Models\CustomFieldset::class); } - + + public function displayFieldInCheckinForm() + { + if ($this->display_checkin == '1') { + return true; + } + return false; + } + + public function displayFieldInCheckoutForm() + { + if ($this->display_checkout == '1') { + return true; + } + return false; + } + + public function displayFieldInAuditForm() + { + if ($this->display_audit == '1') { + return true; + } + return false; + } + + public function displayFieldInCurrentForm($form_type = null) + { + switch ($form_type) { + case 'audit': + return $this->displayFieldInAuditForm(); + case 'checkin': + return $this->displayFieldInCheckinForm(); + case 'checkout': + return $this->displayFieldInCheckoutForm(); + } + } + + public function assetModels() { return $this->fieldset()->with('models')->get()->pluck('models')->flatten()->unique('id'); @@ -307,7 +342,7 @@ class CustomField extends Model public function formatFieldValuesAsArray() { $result = []; - $arr = preg_split('/\\r\\n|\\r|\\n/', $this->field_values); + $arr = preg_split('/\\r\\n|\\r|\\n/', $this->field_values ?? ''); if (($this->element != 'checkbox') && ($this->element != 'radio')) { $result[''] = 'Select '.strtolower($this->format); @@ -383,7 +418,7 @@ class CustomField extends Model /** * Check to see if there is a custom regex format type - * @see https://github.com/snipe/snipe-it/issues/5896 + * @see https://github.com/grokability/snipe-it/issues/5896 * * @author Wes Hulette * diff --git a/app/Models/CustomFieldset.php b/app/Models/CustomFieldset.php index 71be28e8a3..e5490b49b3 100644 --- a/app/Models/CustomFieldset.php +++ b/app/Models/CustomFieldset.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Rules\AlphaEncrypted; +use App\Rules\NumericEncrypted; use Gate; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -69,6 +71,25 @@ class CustomFieldset extends Model return $this->belongsTo(\App\Models\User::class); //WARNING - not all CustomFieldsets have a User!! } + public function displayAnyFieldsInForm($form_type = null) + { + if ($this->fields) { + + switch ($form_type) { + case 'audit': + return $this->fields->where('display_audit', '1')->count() > 0; + case 'checkin': + return $this->fields->where('display_checkin', '1')->count() > 0; + case 'checkout': + return $this->fields->where('display_checkout', '1')->count() > 0; + default: + return true; + } + } + + return false; + } + /** * Determine the validation rules we should apply based on the * custom field format @@ -92,9 +113,25 @@ class CustomFieldset extends Model $rule[] = 'unique_undeleted'; } - array_push($rule, $field->attributes['format']); + if ($field->attributes['format']!='') { + array_push($rule, $field->attributes['format']); + } + $rules[$field->db_column_name()] = $rule; + + // these are to replace the standard 'numeric' and 'alpha' rules if the custom field is also encrypted. + // the values need to be decrypted first, because encrypted strings are alphanumeric + if ($field->format === 'NUMERIC' && $field->field_encrypted) { + $numericKey = array_search('numeric', $rules[$field->db_column_name()]); + $rules[$field->db_column_name()][$numericKey] = new NumericEncrypted; + } + + if ($field->format === 'ALPHA' && $field->field_encrypted) { + $alphaKey = array_search('alpha', $rules[$field->db_column_name()]); + $rules[$field->db_column_name()][$alphaKey] = new AlphaEncrypted; + } + // add not_array to rules for all fields but checkboxes if ($field->element != 'checkbox') { $rules[$field->db_column_name()][] = 'not_array'; diff --git a/app/Models/Department.php b/app/Models/Department.php index 62755d2aa0..592fd840b1 100644 --- a/app/Models/Department.php +++ b/app/Models/Department.php @@ -42,7 +42,7 @@ class Department extends SnipeModel * @var array */ protected $fillable = [ - 'user_id', + 'created_by', 'name', 'phone', 'fax', @@ -141,4 +141,17 @@ class Department extends SnipeModel { return $query->leftJoin('users as department_user', 'departments.manager_id', '=', 'department_user.id')->orderBy('department_user.first_name', $order)->orderBy('department_user.last_name', $order); } + + /** + * Query builder scope to order on company + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderCompany($query, $order) + { + return $query->leftJoin('companies as company_sort', 'departments.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order); + } } diff --git a/app/Models/Depreciable.php b/app/Models/Depreciable.php index 7211358739..0c513a3d33 100644 --- a/app/Models/Depreciable.php +++ b/app/Models/Depreciable.php @@ -76,9 +76,9 @@ class Depreciable extends SnipeModel if ($months_passed >= $this->get_depreciation()->months){ //if there is a floor use it - if(!$this->get_depreciation()->depreciation_min == null) { + if($this->get_depreciation()->depreciation_min) { - $current_value = $this->get_depreciation()->depreciation_min; + $current_value = $this->calculateDepreciation(); }else{ $current_value = 0; @@ -86,7 +86,7 @@ class Depreciable extends SnipeModel } else { // The equation here is (Purchase_Cost-Floor_min)*(Months_passed/Months_til_depreciated) - $current_value = round(($this->purchase_cost-($this->purchase_cost - ($this->get_depreciation()->depreciation_min)) * ($months_passed / $this->get_depreciation()->months)), 2); + $current_value = round(($this->purchase_cost-($this->purchase_cost - ($this->calculateDepreciation())) * ($months_passed / $this->get_depreciation()->months)), 2); } @@ -95,7 +95,7 @@ class Depreciable extends SnipeModel public function getMonthlyDepreciation(){ - return ($this->purchase_cost-$this->get_depreciation()->depreciation_min)/$this->get_depreciation()->months; + return ($this->purchase_cost-$this->calculateDepreciation())/$this->get_depreciation()->months; } @@ -158,17 +158,20 @@ class Depreciable extends SnipeModel public function time_until_depreciated() { - // @link http://www.php.net/manual/en/class.datetime.php - $d1 = new \DateTime(); - $d2 = $this->depreciated_date(); + if ($this->depreciated_date()) { + // @link http://www.php.net/manual/en/class.datetime.php + $d1 = new \DateTime(); + $d2 = $this->depreciated_date(); - // @link http://www.php.net/manual/en/class.dateinterval.php - $interval = $d1->diff($d2); - if (! $interval->invert) { - return $interval; - } else { - return new \DateInterval('PT0S'); //null interval (zero seconds from now) + // @link http://www.php.net/manual/en/class.dateinterval.php + $interval = $d1->diff($d2); + if (! $interval->invert) { + return $interval; + } else { + return new \DateInterval('PT0S'); //null interval (zero seconds from now) + } } + return false; } public function depreciated_date() @@ -188,4 +191,16 @@ class Depreciable extends SnipeModel { return new \DateTime($time); } + + private function calculateDepreciation() + { + if($this->get_depreciation()->depreciation_type === 'percent') { + $depreciation_percent= $this->get_depreciation()->depreciation_min / 100; + $depreciation_min= $this->purchase_cost * $depreciation_percent; + return $depreciation_min; + } + + $depreciation_min = $this->get_depreciation()->depreciation_min; + return $depreciation_min; + } } diff --git a/app/Models/Depreciation.php b/app/Models/Depreciation.php index 9faa1b86e2..11ee82c16a 100755 --- a/app/Models/Depreciation.php +++ b/app/Models/Depreciation.php @@ -75,4 +75,40 @@ class Depreciation extends SnipeModel { return $this->hasMany(\App\Models\License::class, 'depreciation_id'); } + + /** + * Establishes the depreciation -> assets relationship + * + * @author A. Gianotto + * @since [v5.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function assets() + { + return $this->hasManyThrough(\App\Models\Asset::class, \App\Models\AssetModel::class, 'depreciation_id', 'model_id'); + } + + /** + * Get the user that created the depreciation + * + * @author A. Gianotto + * @since [v7.0.13] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + + /** + * ----------------------------------------------- + * BEGIN QUERY SCOPES + * ----------------------------------------------- + **/ + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'depreciations.created_by', '=', 'admin_sort.id')->select('depreciations.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Group.php b/app/Models/Group.php index c6e6e56039..6dae273998 100755 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -18,7 +18,8 @@ class Group extends SnipeModel protected $fillable = [ 'name', - 'permissions' + 'permissions', + 'notes', ]; /** @@ -37,7 +38,7 @@ class Group extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name', 'created_at']; + protected $searchableAttributes = ['name', 'created_at', 'notes']; /** * The relations and their attributes that should be included when searching the model. @@ -65,7 +66,7 @@ class Group extends SnipeModel * @since [v6.3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation */ - public function admin() + public function adminuser() { return $this->belongsTo(\App\Models\User::class, 'created_by'); } @@ -75,10 +76,43 @@ class Group extends SnipeModel * * @author A. Gianotto * @since [v1.0] - * @return array + * @return array | \stdClass */ public function decodePermissions() { - return json_decode($this->permissions, true); + // If the permissions are an array, convert it to JSON + if (is_array($this->permissions)) { + $this->permissions = json_encode($this->permissions); + } + + $permissions = json_decode($this->permissions ?? '{}', JSON_OBJECT_AS_ARRAY); + + // Otherwise, loop through the permissions and cast the values as integers + if ((is_array($permissions)) && ($permissions)) { + foreach ($permissions as $permission => $value) { + + if (!is_integer($permission)) { + $permissions[$permission] = (int) $value; + } else { + \Log::info('Weird data here - skipping it'); + unset($permissions[$permission]); + } + } + return $permissions ?: new \stdClass; + } + return new \stdClass; + + } + + /** + * ----------------------------------------------- + * BEGIN QUERY SCOPES + * ----------------------------------------------- + **/ + + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'permission_groups.created_by', '=', 'admin_sort.id')->select('permission_groups.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); } } diff --git a/app/Models/Import.php b/app/Models/Import.php index 81728c8a5e..d824a3840c 100644 --- a/app/Models/Import.php +++ b/app/Models/Import.php @@ -2,13 +2,28 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Import extends Model { + use HasFactory; + protected $casts = [ 'header_row' => 'array', 'first_row' => 'array', 'field_map' => 'json', ]; + + /** + * Establishes the license -> admin user relationship + * + * @author A. Gianotto + * @since [v2.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } } diff --git a/app/Models/Labels/DefaultLabel.php b/app/Models/Labels/DefaultLabel.php index 9f7059bcd5..f869110138 100644 --- a/app/Models/Labels/DefaultLabel.php +++ b/app/Models/Labels/DefaultLabel.php @@ -43,8 +43,8 @@ class DefaultLabel extends RectangleSheet $this->textSize = Helper::convertUnit($settings->labels_fontsize, 'pt', 'in'); - $this->labelWidth = $settings->labels_width; - $this->labelHeight = $settings->labels_height; + $this->labelWidth = $this->setLabelWidth($settings); + $this->labelHeight = $this->setLabelHeight($settings); $this->labelSpacingH = $settings->labels_display_sgutter; $this->labelSpacingV = $settings->labels_display_bgutter; @@ -181,6 +181,25 @@ class DefaultLabel extends RectangleSheet } } -} + private function setLabelWidth(Setting $settings) + { + $labelWidth = $settings->labels_width; -?> \ No newline at end of file + if ($labelWidth == 0) { + $labelWidth = 0.1; + } + + return $labelWidth; + } + + private function setLabelHeight(?Setting $settings) + { + $labelHeight = $settings->labels_height; + + if ($labelHeight == 0) { + $labelHeight = 0.1; + } + + return $labelHeight; + } +} diff --git a/app/Models/Labels/Label.php b/app/Models/Labels/Label.php index 8ea92d2e42..2405da5401 100644 --- a/app/Models/Labels/Label.php +++ b/app/Models/Labels/Label.php @@ -25,6 +25,17 @@ abstract class Label */ public abstract function getUnit(); + /** + * Returns the PDF rotation. + * 0, 90, 180, 270 + * 0 is a sane default. Override when necessary. + * + * @return int + */ + public function getRotation() { + return 0; + } + /** * Returns the label's width in getUnit() units * @@ -400,14 +411,14 @@ abstract class Label /** * Checks the template is internally valid */ - public final function validate() { + public final function validate() : void { $this->validateUnits(); $this->validateSize(); $this->validateMargins(); $this->validateSupport(); } - private function validateUnits() { + private function validateUnits() : void { $validUnits = [ 'pt', 'mm', 'cm', 'in' ]; $unit = $this->getUnit(); if (!in_array(strtolower($unit), $validUnits)) { @@ -419,7 +430,7 @@ abstract class Label } } - private function validateSize() { + private function validateSize() : void { $width = $this->getWidth(); if (!is_numeric($width) || is_string($width)) { throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ @@ -439,7 +450,7 @@ abstract class Label } } - private function validateMargins() { + private function validateMargins() : void { $marginTop = $this->getMarginTop(); if (!is_numeric($marginTop) || is_string($marginTop)) { throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ @@ -477,7 +488,7 @@ abstract class Label } } - private function validateSupport() { + private function validateSupport() : void { $support1D = $this->getSupport1DBarcode(); if (!is_bool($support1D)) { throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [ diff --git a/app/Models/Labels/Sheets/Avery/L7162_B.php b/app/Models/Labels/Sheets/Avery/L7162_B.php index 268754e04f..9f24d58c2f 100644 --- a/app/Models/Labels/Sheets/Avery/L7162_B.php +++ b/app/Models/Labels/Sheets/Avery/L7162_B.php @@ -55,7 +55,7 @@ class L7162_B extends L7162 $pdf, $record->get('logo'), $pa->x1, $pa->y1, self::LOGO_MAX_WIDTH, $usableHeight, - 'L', 'T', 300, true, false, 0.1 + 'L', 'T', 300, true, false, 0 ); $currentX += $logoSize[0] + self::LOGO_MARGIN; $usableWidth -= $logoSize[0] + self::LOGO_MARGIN; @@ -100,4 +100,4 @@ class L7162_B extends L7162 } -?> \ No newline at end of file +?> diff --git a/app/Models/Labels/Sheets/Avery/_3490.php b/app/Models/Labels/Sheets/Avery/_3490.php new file mode 100644 index 0000000000..2782549fd3 --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/_3490.php @@ -0,0 +1,71 @@ +getUnit(), 2); + $this->pageWidth = $paperSize->width; + $this->pageHeight = $paperSize->height; + + $this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit()); + $this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit()); + + $columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W; + $this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit()); + $rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H; + $this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit()); + + $this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit()); + $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit()); + } + + public function getPageWidth() { return $this->pageWidth; } + public function getPageHeight() { return $this->pageHeight; } + + public function getPageMarginTop() { return $this->pageMarginTop; } + public function getPageMarginBottom() { return $this->pageMarginTop; } + public function getPageMarginLeft() { return $this->pageMarginLeft; } + public function getPageMarginRight() { return $this->pageMarginLeft; } + + public function getColumns() { return 3; } + public function getRows() { return 10; } + + public function getLabelColumnSpacing() { return $this->columnSpacing; } + public function getLabelRowSpacing() { return $this->rowSpacing; } + + public function getLabelWidth() { return $this->labelWidth; } + public function getLabelHeight() { return $this->labelHeight; } + + public function getLabelBorder() { return 0; } +} + +?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/_3490_A.php b/app/Models/Labels/Sheets/Avery/_3490_A.php new file mode 100644 index 0000000000..c558e22292 --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/_3490_A.php @@ -0,0 +1,85 @@ +getLabelPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + $usableHeight = $pa->h; + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $pa->x1, $pa->y1, + 'freesans', '', self::TITLE_SIZE, 'C', + $pa->w, self::TITLE_SIZE, true, 0 + ); + $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; + $usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN; + } + + $barcodeSize = $usableHeight; + if ($record->has('barcode2d')) { + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $currentX, $currentY, + $barcodeSize, $barcodeSize + ); + $currentX += $barcodeSize + self::BARCODE_MARGIN; + $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; + } + + foreach ($record->get('fields') as $field) { + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freesans', '', self::LABEL_SIZE, 'L', + $usableWidth, self::LABEL_SIZE, true, 0 + ); + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $usableWidth, self::FIELD_SIZE, true, 0, 0.01 + ); + $currentY += self::FIELD_SIZE + self::FIELD_MARGIN; + } + + } +} + + +?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/_5520_B.php b/app/Models/Labels/Sheets/Avery/_5520_B.php new file mode 100644 index 0000000000..eb6494e0aa --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/_5520_B.php @@ -0,0 +1,84 @@ +getLabelPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + $usableHeight = $pa->h; + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $pa->x1, $pa->y1, + 'freesans', '', self::TITLE_SIZE, 'C', + $pa->w, self::TITLE_SIZE, true, 0 + ); + $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; + $usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN; + } + + if ($record->has('barcode1d')) { + static::write1DBarcode( + $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type, + $pa->x1, $pa->y2 - self::BARCODE_SIZE, + $usableWidth, self::BARCODE_SIZE + ); + $usableHeight -= self::BARCODE_SIZE + self::BARCODE_MARGIN; + } + + foreach ($record->get('fields') as $field) { + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freesans', '', self::LABEL_SIZE, 'L', + $usableWidth, self::LABEL_SIZE, true, 0 + ); + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $usableWidth, self::FIELD_SIZE, true, 0, 0.01 + ); + $currentY += self::FIELD_SIZE + self::FIELD_MARGIN; + } + + } +} + + +?> \ No newline at end of file diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_D.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_D.php new file mode 100644 index 0000000000..990a615e53 --- /dev/null +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_D.php @@ -0,0 +1,118 @@ +getPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + $usableWidth = $pa->w; + + // Reserve space at bottom for 1D barcode + $usableHeight = $pa->h - self::BARCODE1D_SIZE; + $barcodeSize = $usableHeight - self::TAG_SIZE; + + if ($record->has('barcode2d')) { + static::writeText( + $pdf, $record->get('tag'), + $pa->x1, $pa->y2 - self::TAG_SIZE - self::BARCODE1D_SIZE, + 'freemono', 'b', self::TAG_SIZE, 'C', + $barcodeSize, self::TAG_SIZE, true, 0 + ); + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $currentX, $currentY, + $barcodeSize, $barcodeSize + ); + $currentX += $barcodeSize + self::BARCODE_MARGIN; + $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; + } else { + static::writeText( + $pdf, $record->get('tag'), + $pa->x1, $pa->y2 - self::TAG_SIZE - self::BARCODE1D_SIZE, + 'freemono', 'b', self::TAG_SIZE, 'R', + $usableWidth, self::TAG_SIZE, true, 0 + ); + } + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $currentX, $currentY, + 'freesans', '', self::TITLE_SIZE, 'L', + $usableWidth, self::TITLE_SIZE, true, 0 + ); + $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; + } + + foreach ($record->get('fields') as $field) { + if (!empty($field['label']) && $field['label'] !== "\u{200B}") { + // Write label and value on the same line + // Calculate label width with proportional character spacing + $labelWidth = $pdf->GetStringWidth($field['label'], 'freemono', '', self::LABEL_SIZE); + $charCount = strlen($field['label']); + $spacingPerChar = 0.5; + $totalSpacing = $charCount * $spacingPerChar; + $adjustedWidth = $labelWidth + $totalSpacing; + + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freemono', 'B', self::LABEL_SIZE, 'L', + $adjustedWidth, self::LABEL_SIZE, true, 0, $spacingPerChar + ); + + static::writeText( + $pdf, $field['value'], + $currentX + $adjustedWidth + 2, $currentY, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $usableWidth - $adjustedWidth - 2, self::FIELD_SIZE, true, 0, 0.3 + ); + } else { + + // Label is empty, so write value only. + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, // No offset + 'freemono', 'B', self::FIELD_SIZE, 'L', + $usableWidth, self::FIELD_SIZE, true, 0, 0.3 + ); + } + + $currentY += max(self::LABEL_SIZE, self::FIELD_SIZE) + self::FIELD_MARGIN; + } + + // Add C128 barcode at the bottom + if ($record->has('barcode1d')) { + static::write1DBarcode( + $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type, + $pa->x1, $pa->y2 - self::BARCODE1D_SIZE, + $pa->w, self::BARCODE1D_SIZE + ); + } + } +} diff --git a/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape.php b/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape.php new file mode 100644 index 0000000000..2069927a34 --- /dev/null +++ b/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape.php @@ -0,0 +1,23 @@ +getUnit()); } + public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); } + public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());} + public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } + public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); } + public function getRotation() { return 90; } +} diff --git a/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape_A.php b/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape_A.php new file mode 100644 index 0000000000..3a4d6da2c7 --- /dev/null +++ b/app/Models/Labels/Tapes/Brother/TZe_62mm_Landscape_A.php @@ -0,0 +1,106 @@ +getPrintableArea(); + + $currentX = $pa->x1; + $currentY = $pa->y1; + + // Wide 1D barcode on top + + if ($record->has('barcode1d')) { + static::write1DBarcode( + $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type, + $currentX, $currentY, $pa->w, self::BARCODE1D_HEIGHT + ); + $currentY = self::BARCODE1D_HEIGHT + self::BARCODE1D_MARGIN; + } + + // Left column + + if ($record->has('barcode2d')) { + $columnY = $currentY; + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $currentX, $columnY, + self::BARCODE2D_SIZE, self::BARCODE2D_SIZE + ); + $columnY += self::BARCODE2D_SIZE + self::BARCODE2D_MARGIN; + static::writeText( + $pdf, $record->get('tag'), + $currentX, $columnY, + 'freemono', 'b', self::TAG_SIZE, 'C', + self::BARCODE2D_SIZE, self::TAG_SIZE, true, 0 + ); + $currentX += self::BARCODE2D_SIZE + self::BARCODE2D_MARGIN; + } + + // Right column + if ($record->get('logo')) { + static::writeImage( + $pdf, $record->get('logo'), + $currentX, $currentY, + $pa->w - $currentX, self::LOGO_HEIGHT, + 'L', 'T', 300, true, false, 0 + ); + $currentY += self::LOGO_HEIGHT + self::LOGO_MARGIN; + } + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $currentX, $currentY, + 'freesans', '', self::TITLE_SIZE, 'L', + $pa->w - $currentX, self::TITLE_SIZE, true, 0 + ); + $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; + } + + foreach ($record->get('fields') as $field) { + static::writeText( + $pdf, $field['label'], + $currentX, $currentY, + 'freesans', '', self::LABEL_SIZE, 'L', + $pa->w - $currentX, self::LABEL_SIZE, true, 0, 0 + ); + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $field['value'], + $currentX, $currentY, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $pa->w - $currentX, self::FIELD_SIZE, true, 0, 0.3 + ); + $currentY += self::FIELD_SIZE + self::FIELD_MARGIN; + } + } +} diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php index 9b56012f7a..9f5fa735e4 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php @@ -49,14 +49,7 @@ class LabelWriter_1933081 extends LabelWriter ); $currentX += $barcodeSize + self::BARCODE_MARGIN; $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; - } else { - static::writeText( - $pdf, $record->get('tag'), - $pa->x1, $pa->y2 - self::TAG_SIZE, - 'freesans', 'b', self::TAG_SIZE, 'R', - $usableWidth, self::TAG_SIZE, true, 0 - ); - } + } if ($record->has('title')) { static::writeText( diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php index e1305bd068..117486a8e5 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php @@ -49,13 +49,6 @@ class LabelWriter_2112283 extends LabelWriter ); $currentX += $barcodeSize + self::BARCODE_MARGIN; $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; - } else { - static::writeText( - $pdf, $record->get('tag'), - $pa->x1, $pa->y2 - self::TAG_SIZE, - 'freesans', 'b', self::TAG_SIZE, 'R', - $usableWidth, self::TAG_SIZE, true, 0 - ); } if ($record->has('title')) { diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php index d5f0e8d122..ed8074547b 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php @@ -50,13 +50,6 @@ class LabelWriter_30252 extends LabelWriter ); $currentX += $barcodeSize + self::BARCODE_MARGIN; $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; - } else { - static::writeText( - $pdf, $record->get('tag'), - $pa->x1, $pa->y2 - self::TAG_SIZE, - 'freemono', 'b', self::TAG_SIZE, 'R', - $usableWidth, self::TAG_SIZE, true, 0 - ); } if ($record->has('title')) { diff --git a/app/Models/Ldap.php b/app/Models/Ldap.php index dd3920b56a..f71f926a93 100644 --- a/app/Models/Ldap.php +++ b/app/Models/Ldap.php @@ -229,6 +229,7 @@ class Ldap extends Model $item['department'] = $ldapattributes[$ldap_result_dept][0] ?? ''; $item['manager'] = $ldapattributes[$ldap_result_manager][0] ?? ''; $item['location'] = $ldapattributes[$ldap_result_location][0] ?? ''; + $item['locale'] = app()->getLocale(); return $item; } @@ -239,7 +240,7 @@ class Ldap extends Model * @author [A. Gianotto] [] * @since [v3.0] * @param $ldapatttibutes - * @return array|bool + * @return User | bool */ public static function createUserFromLdap($ldapatttibutes, $password) { @@ -252,6 +253,7 @@ class Ldap extends Model $user->last_name = $item['lastname']; $user->username = $item['username']; $user->email = $item['email']; + $user->locale = $item['locale']; $user->password = $user->noPassword(); if (Setting::getSettings()->ldap_pw_sync == '1') { @@ -281,9 +283,10 @@ class Ldap extends Model * @param $base_dn * @param $count * @param $filter + * @param $attributes * @return array|bool */ - public static function findLdapUsers($base_dn = null, $count = -1, $filter = null) + public static function findLdapUsers($base_dn = null, $count = -1, $filter = null, $attributes = []) { $ldapconn = self::connectToLdap(); self::bindAdminToLdap($ldapconn); @@ -317,7 +320,7 @@ class Ldap extends Model //if($count == -1) { //count is -1 means we have to employ paging to query the entire directory $ldap_controls = [['oid' => LDAP_CONTROL_PAGEDRESULTS, 'iscritical' => false, 'value' => ['size'=> $count == -1||$count>$page_size ? $page_size : $count, 'cookie' => $cookie]]]; //} - $search_results = ldap_search($ldapconn, $base_dn, $filter, [], 0, /* $page_size */ -1, -1, LDAP_DEREF_NEVER, $ldap_controls); // TODO - I hate the @, and I hate that we get a full page even if we ask for 10 records. Can we use an ldap_control? + $search_results = ldap_search($ldapconn, $base_dn, $filter, $attributes, 0, /* $page_size */ -1, -1, LDAP_DEREF_NEVER, $ldap_controls); // TODO - I hate the @, and I hate that we get a full page even if we ask for 10 records. Can we use an ldap_control? Log::debug("LDAP search executed successfully."); if (! $search_results) { return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_search').ldap_error($ldapconn)); // TODO this is never called in any routed context - only from the Artisan command. So this redirect will never work. @@ -338,7 +341,7 @@ class Ldap extends Model $cookie = ''; } // Empty cookie means last page - + // Get results from page $results = ldap_get_entries($ldapconn, $search_results); if (! $results) { diff --git a/app/Models/License.php b/app/Models/License.php index d8bc3f03b1..0997c1e57b 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -50,7 +50,7 @@ class License extends Depreciable 'category_id' => 'required|exists:categories,id', 'company_id' => 'integer|nullable', 'purchase_cost'=> 'numeric|nullable|gte:0', - 'purchase_date' => 'date_format:Y-m-d|nullable|max:10', + 'purchase_date' => 'date_format:Y-m-d|nullable|max:10|required_with:depreciation_id', 'expiration_date' => 'date_format:Y-m-d|nullable|max:10', 'termination_date' => 'date_format:Y-m-d|nullable|max:10', 'min_amt' => 'numeric|nullable|gte:0', @@ -81,8 +81,7 @@ class License extends Depreciable 'serial', 'supplier_id', 'termination_date', - 'free_seat_count', - 'user_id', + 'created_by', 'min_amt', ]; @@ -184,8 +183,8 @@ class License extends Depreciable $logAction = new Actionlog; $logAction->item_type = self::class; $logAction->item_id = $license->id; - $logAction->user_id = Auth::id() ?: 1; // We don't have an id while running the importer from CLI. - $logAction->note = "deleted ${change} seats"; + $logAction->created_by = auth()->id() ?: 1; // We don't have an id while running the importer from CLI. + $logAction->note = "deleted {$change} seats"; $logAction->target_id = null; $logAction->logaction('delete seats'); @@ -196,7 +195,7 @@ class License extends Depreciable $licenseInsert = []; for ($i = $oldSeats; $i < $newSeats; $i++) { $licenseInsert[] = [ - 'user_id' => Auth::id(), + 'created_by' => auth()->id(), 'license_id' => $license->id, 'created_at' => now(), 'updated_at' => now() @@ -216,8 +215,8 @@ class License extends Depreciable $logAction = new Actionlog(); $logAction->item_type = self::class; $logAction->item_id = $license->id; - $logAction->user_id = Auth::id() ?: 1; // Importer. - $logAction->note = "added ${change} seats"; + $logAction->created_by = auth()->id() ?: 1; // Importer. + $logAction->note = "added {$change} seats"; $logAction->target_id = null; $logAction->logaction('add seats'); } @@ -434,7 +433,7 @@ class License extends Depreciable */ public function adminuser() { - return $this->belongsTo(\App\Models\User::class, 'user_id'); + return $this->belongsTo(\App\Models\User::class, 'created_by'); } /** @@ -736,4 +735,12 @@ class License extends Depreciable return $query->leftJoin('companies as companies', 'licenses.company_id', '=', 'companies.id')->select('licenses.*') ->orderBy('companies.name', $order); } -} \ No newline at end of file + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'licenses.created_by', '=', 'admin_sort.id')->select('licenses.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } +} diff --git a/app/Models/LicenseSeat.php b/app/Models/LicenseSeat.php index 397a141468..d4f2b13ad5 100755 --- a/app/Models/LicenseSeat.php +++ b/app/Models/LicenseSeat.php @@ -30,6 +30,7 @@ class LicenseSeat extends SnipeModel implements ICompanyableChild protected $fillable = [ 'assigned_to', 'asset_id', + 'notes', ]; use Acceptable; diff --git a/app/Models/Location.php b/app/Models/Location.php index 67131902a1..52236092d3 100755 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Http\Traits\UniqueUndeletedTrait; use App\Models\Asset; +use App\Models\Setting; use App\Models\SnipeModel; use App\Models\Traits\Searchable; use App\Models\User; @@ -18,6 +19,8 @@ use Watson\Validating\ValidatingTrait; class Location extends SnipeModel { use HasFactory; + use CompanyableTrait; + use Loggable; protected $presenter = \App\Presenters\LocationPresenter::class; use Presentable; @@ -33,16 +36,18 @@ class Location extends SnipeModel 'country' => 'min:2|max:191|nullable', 'zip' => 'max:10|nullable', 'manager_id' => 'exists:users,id|nullable', - 'parent_id' => 'non_circular:locations,id', + 'parent_id' => 'nullable|exists:locations,id|non_circular:locations,id', + 'company_id' => 'integer|nullable|exists:companies,id', ]; protected $casts = [ 'parent_id' => 'integer', 'manager_id' => 'integer', + 'company_id' => 'integer', ]; /** - * Whether the model should inject it's identifier to the unique + * Whether the model should inject its identifier to the unique * validation rules before attempting validation. If this property * is not set in the model it will default to true. * @@ -72,6 +77,8 @@ class Location extends SnipeModel 'currency', 'manager_id', 'image', + 'company_id', + 'notes', ]; protected $hidden = ['user_id']; @@ -82,7 +89,7 @@ class Location extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name', 'address', 'city', 'state', 'zip', 'created_at', 'ldap_ou', 'phone', 'fax']; + protected $searchableAttributes = ['name', 'address', 'city', 'state', 'zip', 'created_at', 'ldap_ou', 'phone', 'fax', 'notes']; /** * The relations and their attributes that should be included when searching the model. @@ -90,7 +97,8 @@ class Location extends SnipeModel * @var array */ protected $searchableRelations = [ - 'parent' => ['name'], + 'parent' => ['name'], + 'company' => ['name'] ]; @@ -108,10 +116,11 @@ class Location extends SnipeModel { return Gate::allows('delete', $this) - && ($this->assets_count === 0) - && ($this->assigned_assets_count === 0) - && ($this->children_count === 0) - && ($this->users_count === 0); + && ($this->assets_count == 0) + && ($this->assigned_assets_count == 0) + && ($this->children_count == 0) + && ($this->accessories_count == 0) + && ($this->users_count == 0); } /** @@ -126,6 +135,17 @@ class Location extends SnipeModel return $this->hasMany(\App\Models\User::class, 'location_id'); } + /** + * Establishes the location -> admin user relationship + * + * @author A. Gianotto + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Find assets with this location as their location_id * @@ -213,6 +233,17 @@ class Location extends SnipeModel ->with('parent'); } + /** + * Establishes the locations -> company relationship + * + * @author [T. Regnery] [] + * @since [v7.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function company() + { + return $this->belongsTo(\App\Models\Company::class, 'company_id'); + } /** * Find the manager of a location @@ -252,11 +283,40 @@ class Location extends SnipeModel return $this->morphMany(\App\Models\Asset::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed(); } + /** + * Establishes the accessory -> location assignment relationship + * + * @author A. Gianotto + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function assignedAccessories() + { + return $this->morphMany(\App\Models\AccessoryCheckout::class, 'assigned', 'assigned_type', 'assigned_to'); + } + public function setLdapOuAttribute($ldap_ou) { return $this->attributes['ldap_ou'] = empty($ldap_ou) ? null : $ldap_ou; } + /** + * Get uploads for this location + * + * @author [A. Gianotto] [] + * @since [v4.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function uploads() + { + return $this->hasMany('\App\Models\Actionlog', 'item_id') + ->where('item_type', '=', Location::class) + ->where('action_type', '=', 'uploaded') + ->whereNotNull('filename') + ->orderBy('created_at', 'desc'); + } + + /** * Query builder scope to order on parent * @@ -312,4 +372,26 @@ class Location extends SnipeModel { return $query->leftJoin('users as location_user', 'locations.manager_id', '=', 'location_user.id')->orderBy('location_user.first_name', $order)->orderBy('location_user.last_name', $order); } + + /** + * Query builder scope to order on company + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderCompany($query, $order) + { + return $query->leftJoin('companies as company_sort', 'locations.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order); + } + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'locations.created_by', '=', 'admin_sort.id')->select('locations.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + } diff --git a/app/Models/Loggable.php b/app/Models/Loggable.php index f855b41253..54aeac5a44 100644 --- a/app/Models/Loggable.php +++ b/app/Models/Loggable.php @@ -6,6 +6,8 @@ use App\Models\Setting; use App\Notifications\AuditNotification; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; +use Osama\LaravelTeamsNotification\TeamsNotification; trait Loggable { @@ -34,10 +36,14 @@ trait Loggable */ public function logCheckout($note, $target, $action_date = null, $originalValues = []) { + $log = new Actionlog; + + $fields_array = []; + $log = $this->determineLogItemType($log); - if (Auth::user()) { - $log->user_id = Auth::user()->id; + if (auth()->user()) { + $log->created_by = auth()->id(); } if (! isset($target)) { @@ -55,6 +61,7 @@ trait Loggable $log->target_type = get_class($target); $log->target_id = $target->id; + // Figure out what the target is if ($log->target_type == Location::class) { $log->location_id = $target->id; @@ -64,24 +71,41 @@ trait Loggable $log->location_id = $target->location_id; } + if (static::class == Asset::class) { + if ($asset = Asset::find($log->item_id)) { + + // add the custom fields that were changed + if ($asset->model->fieldset) { + $fields_array = []; + foreach ($asset->model->fieldset->fields as $field) { + if ($field->display_checkout == 1) { + $fields_array[$field->db_column] = $asset->{$field->db_column}; + } + } + } + } + } + $log->note = $note; $log->action_date = $action_date; - if (! $log->action_date) { - $log->action_date = date('Y-m-d H:i:s'); - } $changed = []; - $originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','expected_checkin'])); + $array_to_flip = array_keys($fields_array); + $array_to_flip = array_merge($array_to_flip, ['name','status_id','location_id','expected_checkin']); + $originalValues = array_intersect_key($originalValues, array_flip($array_to_flip)); + foreach ($originalValues as $key => $value) { + // TODO - action_date isn't a valid attribute of any first-class object, so we might want to remove this? if ($key == 'action_date' && $value != $action_date) { $changed[$key]['old'] = $value; $changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s'); - } elseif ($value != $this->getAttributes()[$key]) { + } elseif (array_key_exists($key, $this->getAttributes()) && $value != $this->getAttributes()[$key]) { $changed[$key]['old'] = $value; $changed[$key]['new'] = $this->getAttributes()[$key]; } + // NOTE - if the attribute exists in $originalValues, but *not* in ->getAttributes(), it isn't added to $changed } if (!empty($changed)){ @@ -117,9 +141,10 @@ trait Loggable */ public function logCheckin($target, $note, $action_date = null, $originalValues = []) { - $settings = Setting::getSettings(); $log = new Actionlog; + $fields_array = []; + if($target != null){ $log->target_type = get_class($target); $log->target_id = $target->id; @@ -136,6 +161,16 @@ trait Loggable if (static::class == Asset::class) { if ($asset = Asset::find($log->item_id)) { $asset->increment('checkin_counter', 1); + + // add the custom fields that were changed + if ($asset->model->fieldset) { + $fields_array = []; + foreach ($asset->model->fieldset->fields as $field) { + if ($field->display_checkin == 1) { + $fields_array[$field->db_column] = $asset->{$field->db_column}; + } + } + } } } } @@ -144,18 +179,23 @@ trait Loggable $log->note = $note; $log->action_date = $action_date; - if (! $log->action_date) { + if (!$action_date) { $log->action_date = date('Y-m-d H:i:s'); } - if (Auth::user()) { - $log->user_id = Auth::user()->id; + if (auth()->user()) { + $log->created_by = auth()->id(); } $changed = []; - $originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','rtd_location_id','expected_checkin'])); + + $array_to_flip = array_keys($fields_array); + $array_to_flip = array_merge($array_to_flip, ['name','status_id','location_id','expected_checkin']); + + $originalValues = array_intersect_key($originalValues, array_flip($array_to_flip)); foreach ($originalValues as $key => $value) { + if ($key == 'action_date' && $value != $action_date) { $changed[$key]['old'] = $value; $changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s'); @@ -171,39 +211,6 @@ trait Loggable $log->logaction('checkin from'); -// $params = [ -// 'target' => $target, -// 'item' => $log->item, -// 'admin' => $log->user, -// 'note' => $note, -// 'target_type' => $log->target_type, -// 'settings' => $settings, -// ]; -// -// -// $checkinClass = null; -// -// if (method_exists($target, 'notify')) { -// try { -// $target->notify(new static::$checkinClass($params)); -// } catch (\Exception $e) { -// Log::debug($e); -// } -// -// } -// -// // Send to the admin, if settings dictate -// $recipient = new \App\Models\Recipients\AdminRecipient(); -// -// if (($settings->admin_cc_email!='') && (static::$checkinClass!='')) { -// try { -// $recipient->notify(new static::$checkinClass($params)); -// } catch (\Exception $e) { -// Log::debug($e); -// } -// -// } - return $log; } @@ -212,9 +219,41 @@ trait Loggable * @since [v4.0] * @return \App\Models\Actionlog */ - public function logAudit($note, $location_id, $filename = null) + public function logAudit($note, $location_id, $filename = null, $originalValues = []) { + $log = new Actionlog; + + if (static::class == Asset::class) { + if ($asset = Asset::find($log->item_id)) { + // add the custom fields that were changed + if ($asset->model->fieldset) { + $fields_array = []; + foreach ($asset->model->fieldset->fields as $field) { + if ($field->display_audit == 1) { + $fields_array[$field->db_column] = $asset->{$field->db_column}; + } + } + } + } + } + + $changed = []; + + unset($originalValues['updated_at'], $originalValues['last_audit_date']); + foreach ($originalValues as $key => $value) { + + if ($value != $this->getAttributes()[$key]) { + $changed[$key]['old'] = $value; + $changed[$key]['new'] = $this->getAttributes()[$key]; + } + } + + if (!empty($changed)){ + $log->log_meta = json_encode($changed); + } + + $location = Location::find($location_id); if (static::class == LicenseSeat::class) { $log->item_type = License::class; @@ -225,18 +264,26 @@ trait Loggable } $log->location_id = ($location_id) ? $location_id : null; $log->note = $note; - $log->user_id = Auth::user()->id; + $log->created_by = auth()->id(); $log->filename = $filename; + $log->action_date = date('Y-m-d H:i:s'); $log->logaction('audit'); $params = [ 'item' => $log->item, 'filename' => $log->filename, - 'admin' => $log->admin, + 'admin' => $log->adminuser, 'location' => ($location) ? $location->name : '', 'note' => $note, ]; - Setting::getSettings()->notify(new AuditNotification($params)); + if(Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')){ + $message = AuditNotification::toMicrosoftTeams($params); + $notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint); + $notification->success()->sendMessage($message[0], $message[1]); + } + else { + Setting::getSettings()->notify(new AuditNotification($params)); + } return $log; } @@ -248,9 +295,9 @@ trait Loggable */ public function logCreate($note = null) { - $user_id = -1; - if (Auth::user()) { - $user_id = Auth::user()->id; + $created_by = -1; + if (auth()->user()) { + $created_by = auth()->id(); } $log = new Actionlog; if (static::class == LicenseSeat::class) { @@ -261,8 +308,9 @@ trait Loggable $log->item_id = $this->id; } $log->location_id = null; + $log->action_date = date('Y-m-d H:i:s'); $log->note = $note; - $log->user_id = $user_id; + $log->created_by = $created_by; $log->logaction('create'); $log->save(); @@ -284,13 +332,34 @@ trait Loggable $log->item_type = static::class; $log->item_id = $this->id; } - $log->user_id = Auth::user()->id; + $log->created_by = auth()->id(); $log->note = $note; $log->target_id = null; $log->created_at = date('Y-m-d H:i:s'); + $log->action_date = date('Y-m-d H:i:s'); $log->filename = $filename; $log->logaction('uploaded'); return $log; } + + /** + * Get latest signature from a specific user + * + * This just makes the print view a bit cleaner + * Returns the latest acceptance ActionLog that contains a signature + * from $user or null if there is none + * + * @param User $user + * @return null|Actionlog + **/ + public function getLatestSignedAcceptance(User $user) + { + return $this->log->where('target_type', User::class) + ->where('target_id', $user->id) + ->where('action_type', 'accepted') + ->where('accept_signature', '!=', null) + ->sortByDesc('created_at') + ->first(); + } } diff --git a/app/Models/Manufacturer.php b/app/Models/Manufacturer.php index 5408d50c36..6d7b010677 100755 --- a/app/Models/Manufacturer.php +++ b/app/Models/Manufacturer.php @@ -22,9 +22,9 @@ class Manufacturer extends SnipeModel // Declare the rules for the form validation protected $rules = [ 'name' => 'required|min:2|max:255|unique:manufacturers,name,NULL,id,deleted_at,NULL', - 'url' => 'url|nullable', + 'url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://', 'support_email' => 'email|nullable', - 'support_url' => 'nullable|url', + 'support_url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://', 'warranty_lookup_url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://' ]; @@ -53,6 +53,7 @@ class Manufacturer extends SnipeModel 'support_url', 'url', 'warranty_lookup_url', + 'notes', ]; use Searchable; @@ -62,7 +63,7 @@ class Manufacturer extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name', 'created_at']; + protected $searchableAttributes = ['name', 'created_at', 'notes']; /** * The relations and their attributes that should be included when searching the model. @@ -74,10 +75,11 @@ class Manufacturer extends SnipeModel public function isDeletable() { return Gate::allows('delete', $this) - && ($this->assets()->count() === 0) - && ($this->licenses()->count() === 0) - && ($this->consumables()->count() === 0) - && ($this->accessories()->count() === 0) + && (($this->assets_count ?? $this->assets()->count()) === 0) + && (($this->licenses_count ?? $this->licenses()->count()) === 0) + && (($this->consumables_count ?? $this->consumables()->count()) === 0) + && (($this->accessories_count ?? $this->accessories()->count()) === 0) + && (($this->components_count ?? $this->components()->count()) === 0) && ($this->deleted_at == ''); } @@ -105,4 +107,23 @@ class Manufacturer extends SnipeModel { return $this->hasMany(\App\Models\Consumable::class, 'manufacturer_id'); } + + public function components() + { + return $this->hasMany(\App\Models\Component::class, 'manufacturer_id'); + } + + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'manufacturers.created_by', '=', 'admin_sort.id')->select('manufacturers.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/PredefinedKit.php b/app/Models/PredefinedKit.php index 1bf6cb098b..36790a1fc7 100644 --- a/app/Models/PredefinedKit.php +++ b/app/Models/PredefinedKit.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Models\Traits\Searchable; use App\Presenters\Presentable; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Validation\Rule; use Watson\Validating\ValidatingTrait; @@ -16,6 +17,7 @@ use Watson\Validating\ValidatingTrait; class PredefinedKit extends SnipeModel { protected $presenter = \App\Presenters\PredefinedKitPresenter::class; + use HasFactory; use Presentable; protected $table = 'kits'; @@ -133,6 +135,13 @@ class PredefinedKit extends SnipeModel */ protected $searchableRelations = []; + + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + /** * Establishes the kits -> models relationship * @return \Illuminate\Database\Eloquent\Relations\Relation @@ -179,4 +188,9 @@ class PredefinedKit extends SnipeModel * BEGIN QUERY SCOPES * ----------------------------------------------- **/ + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'kits.created_by', '=', 'admin_sort.id')->select('kits.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Recipients/AdminRecipient.php b/app/Models/Recipients/AdminRecipient.php index 433bd00209..90e39d4ee5 100644 --- a/app/Models/Recipients/AdminRecipient.php +++ b/app/Models/Recipients/AdminRecipient.php @@ -6,9 +6,15 @@ use App\Models\Setting; class AdminRecipient extends Recipient { + + protected $email; public function __construct() { $settings = Setting::getSettings(); $this->email = trim($settings->admin_cc_email); } + + public function getEmail(){ + return $this->email; + } } diff --git a/app/Models/ReportTemplate.php b/app/Models/ReportTemplate.php new file mode 100644 index 0000000000..cdd05b6f50 --- /dev/null +++ b/app/Models/ReportTemplate.php @@ -0,0 +1,203 @@ + 'array', + ]; + + protected $fillable = [ + 'created_by', + 'name', + 'options', + ]; + + protected $rules = [ + 'name' => [ + 'required', + 'string', + ], + 'options' => [ + 'required', + 'array', + ], + ]; + + protected static function booted() + { + // Scope to current user + static::addGlobalScope('current_user', function (Builder $builder) { + if (auth()->check()) { + $builder->where('created_by', auth()->id()); + } + }); + } + + /** + * Establishes the report template -> creator relationship. + * + */ + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * Get the value of a checkbox field for the given field name. + * + * @param string $fieldName + * @param string $fallbackValue The value to return if the report template is not saved yet. + * + */ + public function checkmarkValue(string $fieldName, string $fallbackValue = '1'): string + { + // Assuming we're using the null object pattern, and an empty model + // was passed to the view when showing the default report page, + // return the fallback value so that checkboxes are checked by default. + if (is_null($this->id)) { + return $fallbackValue; + } + + // If the model does exist then return the value of the field + // or return 0 so the checkbox is unchecked. + // Falling back to 0 here is because checkboxes are not sent + // in the request when unchecked so they are not + // actually saved in the model's options. + return $this->options[$fieldName] ?? '0'; + } + + /** + * Get the value of a radio field for the given field name. + * + * @param string $fieldName + * @param string $value The value to check against. + * @param bool $isDefault Whether the radio input being checked is the default. + * + */ + public function radioValue(string $fieldName, string $value, bool $isDefault = false): bool + { + $fieldExists = array_has($this->options, $fieldName); + + // If the field doesn't exist but the radio input + // being checked is the default then return true. + if (!$fieldExists && $isDefault) { + return true; + } + + // If the field exists and matches what we're checking then return true. + if ($fieldExists && $this->options[$fieldName] === $value) { + return true; + } + + // Otherwise return false. + return false; + } + + /** + * Get the value of a select field for the given field name. + * + * @param string $fieldName + * @param string|null $model The Eloquent model to check against. + * + * @return mixed|null + * + */ + public function selectValue(string $fieldName, string $model = null) + { + // If the field does not exist then return null. + if (!isset($this->options[$fieldName])) { + return null; + } + + $value = $this->options[$fieldName]; + + // If the value was stored as an array, most likely + // due to a previously being a multi-select, + // then return the first value. + if (is_array($value)) { + $value = $value[0]; + } + + // If a model is provided then we should ensure we only return + // the value if the model still exists. + // Note: It is possible $value is an id that no longer exists and this will return null. + if ($model) { + $foundModel = $model::find($value); + + return $foundModel ? $foundModel->id : null; + } + + return $value; + } + + /** + * Get the values of a multi-select field for the given field name. + * + * @param string $fieldName + * @param string|null $model The Eloquent model to check against. + * + * @return iterable + * + */ + public function selectValues(string $fieldName, string $model = null): iterable + { + // If the field does not exist then return an empty array. + if (!isset($this->options[$fieldName])) { + return []; + } + + // If a model is provided then we should ensure we only return + // the ids of models that exist and are not deleted. + if ($model) { + return $model::findMany($this->options[$fieldName])->pluck('id'); + } + + // Wrap the value in an array if needed. This is to ensure + // values previously stored as a single value, + // most likely from a single select, are returned as an array. + if (!is_array($this->options[$fieldName])) { + return [$this->options[$fieldName]]; + } + + return $this->options[$fieldName]; + } + + /** + * Get the value of a text field for the given field name. + * + * @param string $fieldName + * @param string|null $fallbackValue + * + * @return string + */ + public function textValue(string $fieldName, string|null $fallbackValue = ''): string + { + // Assuming we're using the null object pattern, + // return the default value if the object is not saved yet. + if (is_null($this->id)) { + return (string) $fallbackValue; + } + + // Return the field's value if it exists + // and return the default value if not. + return $this->options[$fieldName] ?? ''; + } + + public function getDisplayNameAttribute() + { + return $this->name; + } +} diff --git a/app/Models/Requestable.php b/app/Models/Requestable.php index bf5c9c427b..4dead82bb3 100644 --- a/app/Models/Requestable.php +++ b/app/Models/Requestable.php @@ -29,19 +29,19 @@ trait Requestable public function request($qty = 1) { $this->requests()->save( - new CheckoutRequest(['user_id' => Auth::id(), 'qty' => $qty]) + new CheckoutRequest(['user_id' => auth()->id(), 'qty' => $qty]) ); } public function deleteRequest() { - $this->requests()->where('user_id', Auth::id())->delete(); + $this->requests()->where('user_id', auth()->id())->delete(); } public function cancelRequest($user_id = null) { if (!$user_id){ - $user_id = Auth::id(); + $user_id = auth()->id(); } $this->requests()->where('user_id', $user_id)->update(['canceled_at' => \Carbon\Carbon::now()]); diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 30600aea56..199aee33dc 100755 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -2,6 +2,7 @@ namespace App\Models; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; @@ -9,6 +10,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; use App\Helpers\Helper; +use Illuminate\Support\Facades\Storage; use Watson\Validating\ValidatingTrait; use Illuminate\Support\Facades\Log; @@ -50,33 +52,7 @@ class Setting extends Model */ protected $rules = [ 'brand' => 'required|min:1|numeric', - 'qr_text' => 'max:31|nullable', - 'alert_email' => 'email_array|nullable', - 'admin_cc_email' => 'email|nullable', - 'default_currency' => 'required', - 'locale' => 'required', - 'labels_per_page' => 'numeric', - 'labels_width' => 'numeric', - 'labels_height' => 'numeric', - 'labels_pmargin_left' => 'numeric|nullable', - 'labels_pmargin_right' => 'numeric|nullable', - 'labels_pmargin_top' => 'numeric|nullable', - 'labels_pmargin_bottom' => 'numeric|nullable', - 'labels_display_bgutter' => 'numeric|nullable', - 'labels_display_sgutter' => 'numeric|nullable', - 'labels_fontsize' => 'numeric|min:5', - 'labels_pagewidth' => 'numeric|nullable', - 'labels_pageheight' => 'numeric|nullable', - 'login_remote_user_enabled' => 'numeric|nullable', - 'login_common_disabled' => 'numeric|nullable', - 'login_remote_user_custom_logout_url' => 'string|nullable', - 'login_remote_user_header_name' => 'string|nullable', 'thumbnail_max_h' => 'numeric|max:500|min:25', - 'pwd_secure_min' => 'numeric|required|min:8', - 'audit_warning_days' => 'numeric|nullable', - 'audit_interval' => 'numeric|nullable', - 'custom_forgot_pass_url' => 'url|nullable', - 'privacy_policy_link' => 'nullable|url', 'google_client_id' => 'nullable|ends_with:apps.googleusercontent.com' ]; @@ -95,6 +71,7 @@ class Setting extends Model protected $casts = [ 'label2_asset_logo' => 'boolean', + 'require_checkinout_notes' => 'boolean', ]; /** @@ -270,7 +247,7 @@ class Setting extends Model * * @return string */ - public function routeNotificationForSlack(): string + public function routeNotificationForSlack(): ?string { // At this point the endpoint is the same for everything. // In the future this may want to be adapted for individual notifications. @@ -282,7 +259,7 @@ class Setting extends Model * * @return string */ - public function routeNotificationForMail(): string + public function routeNotificationForMail(): ?string { // At this point the endpoint is the same for everything. // In the future this may want to be adapted for individual notifications. @@ -363,6 +340,33 @@ class Setting extends Model return collect($ldapSettings); } + /** + * For a particular cache-file, refresh it if the settings have + * been updated more recently than the file. Then return the + * full filepath + */ + public static function get_fresh_file_path($attribute, $path) + { + $full_path = storage_path().'/'.$path; + $file_exists = file_exists($full_path); + if ($file_exists) { + $statblock = stat($full_path); + } + if (!$file_exists || Carbon::createFromTimestamp($statblock['mtime']) < Setting::getSettings()->updated_at) { + if (Setting::getSettings()->{$attribute}) { + file_put_contents($full_path, Setting::getSettings()->{$attribute}); + } else { + //this doesn't fire when you might expect it to because a lot of the time we do something like: + // if ($settings->ldap_client_tls_cert && ... + // so we never get a chance to 'uncache' the file. + if ($file_exists) { + unlink($full_path); + } + } + } + return $full_path; + } + /** * Return the filename for the client-side SSL cert * @@ -370,7 +374,7 @@ class Setting extends Model */ public static function get_client_side_cert_path() { - return storage_path().'/ldap_client_tls.cert'; + return self::get_fresh_file_path('ldap_client_tls_cert', 'ldap_client_tls.cert'); } /** @@ -380,36 +384,7 @@ class Setting extends Model */ public static function get_client_side_key_path() { - return storage_path().'/ldap_client_tls.key'; + return self::get_fresh_file_path('ldap_client_tls_key', 'ldap_client_tls.key'); } - public function update_client_side_cert_files() - { - /** - * I'm not sure if it makes sense to have a cert but no key - * nor vice versa, but for now I'm just leaving it like this. - * - * Also, we could easily set this up with an event handler and - * self::saved() or something like that but there's literally only - * one place where we will do that, so I'll just explicitly call - * this method at that spot instead. It'll be easier to debug and understand. - */ - if ($this->ldap_client_tls_cert) { - file_put_contents(self::get_client_side_cert_path(), $this->ldap_client_tls_cert); - } else { - if (file_exists(self::get_client_side_cert_path())) { - unlink(self::get_client_side_cert_path()); - } - } - - if ($this->ldap_client_tls_key) { - file_put_contents(self::get_client_side_key_path(), $this->ldap_client_tls_key); - } else { - if (file_exists(self::get_client_side_key_path())) { - unlink(self::get_client_side_key_path()); - } - } - } - - } diff --git a/app/Models/SnipeModel.php b/app/Models/SnipeModel.php index af12c3d29b..f26946d22a 100644 --- a/app/Models/SnipeModel.php +++ b/app/Models/SnipeModel.php @@ -21,6 +21,11 @@ class SnipeModel extends Model */ public function setPurchaseCostAttribute($value) { + if (is_float($value)) { + //value is *already* a floating-point number. Just assign it directly + $this->attributes['purchase_cost'] = $value; + return; + } $value = Helper::ParseCurrency($value); if ($value == 0) { diff --git a/app/Models/Statuslabel.php b/app/Models/Statuslabel.php index 0f8a0b6075..c1bcc3042d 100755 --- a/app/Models/Statuslabel.php +++ b/app/Models/Statuslabel.php @@ -64,6 +64,11 @@ class Statuslabel extends SnipeModel return $this->hasMany(\App\Models\Asset::class, 'status_id'); } + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Gets the status label type * @@ -161,4 +166,9 @@ class Statuslabel extends SnipeModel return $statustype; } + + public function scopeOrderByCreatedBy($query, $order) + { + return $query->leftJoin('users as admin_sort', 'status_labels.created_by', '=', 'admin_sort.id')->select('status_labels.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } } diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php index e198d10c10..0166229f89 100755 --- a/app/Models/Supplier.php +++ b/app/Models/Supplier.php @@ -48,7 +48,7 @@ class Supplier extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name']; + protected $searchableAttributes = ['name', 'notes', 'phone', 'fax', 'url', 'email', 'contact', 'address', 'address2', 'city', 'state', 'country', 'zip']; /** * The relations and their attributes that should be included when searching the model. @@ -128,6 +128,18 @@ class Supplier extends SnipeModel return $this->hasMany(\App\Models\Consumable::class, 'supplier_id'); } + + /** + * Establishes the supplier -> admin user relationship + * + * @author A. Gianotto + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + /** * Establishes the supplier -> asset maintenances relationship * @@ -197,4 +209,13 @@ class Supplier extends SnipeModel return $url; } + + /** + * Query builder scope to order on the user that created it + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'suppliers.created_by', '=', 'admin_sort.id')->select('suppliers.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + } diff --git a/app/Models/Traits/Searchable.php b/app/Models/Traits/Searchable.php index 06e05348fb..1430ce649a 100644 --- a/app/Models/Traits/Searchable.php +++ b/app/Models/Traits/Searchable.php @@ -77,7 +77,7 @@ trait Searchable /** * Making sure to only search in date columns if the search term consists of characters that can make up a MySQL timestamp! * - * @see https://github.com/snipe/snipe-it/issues/4590 + * @see https://github.com/grokability/snipe-it/issues/4590 */ if (! preg_match('/^[0-9 :-]++$/', $term) && in_array($column, $this->getDates())) { continue; diff --git a/app/Models/User.php b/app/Models/User.php index 30e32061ed..278c801bed 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -13,12 +13,14 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Facades\Gate; use Laravel\Passport\HasApiTokens; use Watson\Validating\ValidatingTrait; +use Illuminate\Database\Eloquent\Casts\Attribute; class User extends SnipeModel implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, HasLocalePreference { @@ -93,7 +95,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo 'locale' => 'max:10|nullable', 'website' => 'url|nullable|max:191', 'manager_id' => 'nullable|exists:users,id|cant_manage_self', - 'location_id' => 'exists:locations,id|nullable', + 'location_id' => 'exists:locations,id|nullable|fmcs_location', 'start_date' => 'nullable|date_format:Y-m-d', 'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date', 'autoassign_licenses' => 'boolean', @@ -113,15 +115,21 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * @var array */ protected $searchableAttributes = [ - 'first_name', - 'last_name', + 'address', + 'city', + 'country', 'email', - 'username', + 'employee_num', + 'first_name', + 'jobtitle', + 'last_name', + 'locale', 'notes', 'phone', - 'jobtitle', - 'employee_num', + 'state', + 'username', 'website', + 'zip', ]; /** @@ -130,13 +138,36 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo * @var array */ protected $searchableRelations = [ - 'userloc' => ['name'], + 'userloc' => ['name', 'address', 'address2', 'city', 'state', 'zip'], 'department' => ['name'], 'groups' => ['name'], 'company' => ['name'], 'manager' => ['first_name', 'last_name', 'username'], ]; + + /** + * This sets the name property on the user. It's not a real field in the database + * (since we use first_name and last_name), but the Laravel mailable method + * uses this to determine the name of the user to send emails to. + * + * We only have to do this on the User model and no other models because other + * first-class objects have a name field. + * @return void + */ + + public $name; + + protected static function boot() + { + parent::boot(); + + static::retrieved(function($user){ + $user->name = $user->getFullNameAttribute(); + }); + } + + /** * Internally check the user permission for the given section * @@ -277,6 +308,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $this->activated == 1; } + /** * Returns the full name attribute * @@ -288,7 +320,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo { $setting = Setting::getSettings(); - if ($setting->name_display_format=='last_first') { + if ($setting?->name_display_format == 'last_first') { return ($this->last_name) ? $this->last_name.' '.$this->first_name : $this->first_name; } return $this->last_name ? $this->first_name.' '.$this->last_name : $this->first_name; @@ -331,7 +363,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo */ public function accessories() { - return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_users', 'assigned_to', 'accessory_id') + return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_checkout', 'assigned_to', 'accessory_id') + ->where('assigned_type', '=', 'App\Models\User') ->withPivot('id', 'created_at', 'note')->withTrashed()->orderBy('accessory_id'); } @@ -359,6 +392,15 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id', 'created_at', 'updated_at'); } + /** + * Establishes the user -> reportTemplates relationship + * + */ + public function reportTemplates(): HasMany + { + return $this->hasMany(ReportTemplate::class, 'created_by'); + } + /** * Establishes a count of all items assigned * @@ -481,8 +523,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo /** * Establishes the user -> uploads relationship * - * @todo I don't think we use this? - * * @author A. Gianotto * @since [v3.0] * @return \Illuminate\Database\Eloquent\Relations\Relation @@ -496,6 +536,40 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo ->orderBy('created_at', 'desc'); } + /** + * Establishes the user -> acceptances relationship + * + * @author A. Gianotto + * @since [v7.0.7] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function acceptances() + { + return $this->hasMany(\App\Models\Actionlog::class, 'target_id') + ->where('target_type', self::class) + ->where('action_type', '=', 'accepted') + ->orderBy('created_at', 'desc'); + } + + /** + * Establishes the user -> eula relationship + * + * @return \Illuminate\Database\Eloquent\Relations\Relation + * @since [v8.1.16] + * @author [Godfrey Martinez] [] + */ + public function eulas() + { + return $this->hasMany(Actionlog::class, 'target_id') + ->with('item') + ->select(['id', 'target_id', 'target_type', 'action_type', 'filename', 'accept_signature', 'created_at', 'note', 'item_id', 'item_type']) + ->where('target_type', self::class) + ->where('action_type', 'accepted') + ->whereNotNull('filename') + ->whereNotNull('accept_signature') + ->orderBy('created_at', 'desc'); + } + /** * Establishes the user -> requested assets relationship * @@ -597,10 +671,14 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo $username = str_slug($first_name).'_'.str_slug($last_name); } elseif ($format == 'firstname') { $username = str_slug($first_name); + } elseif ($format == 'lastname') { + $username = str_slug($last_name); } elseif ($format == 'firstinitial.lastname') { $username = str_slug(substr($first_name, 0, 1).'.'.str_slug($last_name)); } elseif ($format == 'lastname_firstinitial') { $username = str_slug($last_name).'_'.str_slug(substr($first_name, 0, 1)); + } elseif ($format == 'lastname.firstinitial') { + $username = str_slug($last_name).'.'.str_slug(substr($first_name, 0, 1)); } elseif ($format == 'firstnamelastname') { $username = str_slug($first_name).str_slug($last_name); } elseif ($format == 'firstnamelastinitial') { @@ -687,10 +765,36 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo } - + /** + * Decode JSON permissions into array + * + * @author A. Gianotto + * @since [v1.0] + * @return array | \stdClass + */ public function decodePermissions() { - return json_decode($this->permissions, true); + // If the permissions are an array, convert it to JSON + if (is_array($this->permissions)) { + $this->permissions = json_encode($this->permissions); + } + + $permissions = json_decode($this->permissions ?? '{}', JSON_OBJECT_AS_ARRAY); + + // Otherwise, loop through the permissions and cast the values as integers + if ((is_array($permissions)) && ($permissions)) { + foreach ($permissions as $permission => $value) { + + if (!is_integer($permission)) { + $permissions[$permission] = (int) $value; + } else { + \Log::info('Weird data here - skipping it'); + unset($permissions[$permission]); + } + } + return $permissions ?: new \stdClass; + } + return new \stdClass; } /** @@ -815,10 +919,22 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $query->leftJoin('companies as companies_user', 'users.company_id', '=', 'companies_user.id')->orderBy('companies_user.name', $order); } - public function preferredLocale() + + + + /** + * Get the preferred locale for the user. + * + * This uses the HasLocalePreference contract to determine the user's preferred locale, + * used by Laravel's mail system to determine the locale for sending emails. + * https://laravel.com/docs/11.x/mail#user-preferred-locales + * + */ + public function preferredLocale(): string { - return $this->locale; + return $this->locale ?? Setting::getSettings()->locale ?? config('app.locale'); } + public function getUserTotalCost(){ $asset_cost= 0; $license_cost= 0; diff --git a/app/Notifications/AcceptanceAssetAcceptedNotification.php b/app/Notifications/AcceptanceAssetAcceptedNotification.php index db1555b574..7798dbc0d5 100644 --- a/app/Notifications/AcceptanceAssetAcceptedNotification.php +++ b/app/Notifications/AcceptanceAssetAcceptedNotification.php @@ -24,6 +24,7 @@ class AcceptanceAssetAcceptedNotification extends Notification $this->item_tag = $params['item_tag']; $this->item_model = $params['item_model']; $this->item_serial = $params['item_serial']; + $this->item_status = $params['item_status']; $this->accepted_date = Helper::getFormattedDateObject($params['accepted_date'], 'date', false); $this->assigned_to = $params['assigned_to']; $this->note = $params['note']; @@ -65,6 +66,7 @@ class AcceptanceAssetAcceptedNotification extends Notification 'item_tag' => $this->item_tag, 'item_model' => $this->item_model, 'item_serial' => $this->item_serial, + 'item_status' => $this->item_status, 'note' => $this->note, 'accepted_date' => $this->accepted_date, 'assigned_to' => $this->assigned_to, diff --git a/app/Notifications/AcceptanceAssetDeclinedNotification.php b/app/Notifications/AcceptanceAssetDeclinedNotification.php index abdfbbf0c0..0a9d6c211f 100644 --- a/app/Notifications/AcceptanceAssetDeclinedNotification.php +++ b/app/Notifications/AcceptanceAssetDeclinedNotification.php @@ -24,6 +24,7 @@ class AcceptanceAssetDeclinedNotification extends Notification $this->item_tag = $params['item_tag']; $this->item_model = $params['item_model']; $this->item_serial = $params['item_serial']; + $this->item_status = $params['item_status']; $this->declined_date = Helper::getFormattedDateObject($params['declined_date'], 'date', false); $this->note = $params['note']; $this->assigned_to = $params['assigned_to']; @@ -63,6 +64,7 @@ class AcceptanceAssetDeclinedNotification extends Notification 'item_tag' => $this->item_tag, 'item_model' => $this->item_model, 'item_serial' => $this->item_serial, + 'item_status' => $this->item_status, 'note' => $this->note, 'declined_date' => $this->declined_date, 'assigned_to' => $this->assigned_to, diff --git a/app/Notifications/AuditNotification.php b/app/Notifications/AuditNotification.php index 1377c29e7e..a7056e8cbd 100644 --- a/app/Notifications/AuditNotification.php +++ b/app/Notifications/AuditNotification.php @@ -2,12 +2,18 @@ namespace App\Notifications; +use AllowDynamicProperties; use App\Models\Setting; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; +use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel; +use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage; -class AuditNotification extends Notification +#[AllowDynamicProperties] class AuditNotification extends Notification { use Queueable; /** @@ -35,10 +41,14 @@ class AuditNotification extends Notification public function via() { $notifyBy = []; - if (Setting::getSettings()->webhook_endpoint) { - $notifyBy[] = 'slack'; + if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { + Log::debug('use webhook'); + $notifyBy[] = SlackWebhookChannel::class; } + if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) { + $notifyBy[] = MicrosoftTeamsChannel::class; + } return $notifyBy; } @@ -63,4 +73,30 @@ class AuditNotification extends Notification ->fields($fields); }); } + + public static function toMicrosoftTeams($params) + { + $item = $params['item']; + $admin_user = $params['admin']; + $note = $params['note']; + $location = $params['location']; + $setting = Setting::getSettings(); + + if(!Str::contains($setting->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($setting->webhook_endpoint) + ->type('success') + ->title(class_basename(get_class($params['item'])) . ' Audited') + ->addStartGroupToSection('activityText') + ->fact(trans('mail.asset'), $item) + ->fact(trans('general.administrator'), $admin_user->present()->viewUrl() . '|' . $admin_user->present()->fullName()); + } + $message = class_basename(get_class($params['item'])) . ' Audited By '.$admin_user->present()->fullName(); + $details = [ + trans('mail.asset') => htmlspecialchars_decode($item->present()->name), + trans('mail.notes') => $note ?: '', + trans('general.location') => $location ?: '', + ]; + return [$message, $details]; + } } diff --git a/app/Notifications/CheckinAccessoryNotification.php b/app/Notifications/CheckinAccessoryNotification.php index 7e033f1870..5354775d7e 100644 --- a/app/Notifications/CheckinAccessoryNotification.php +++ b/app/Notifications/CheckinAccessoryNotification.php @@ -6,9 +6,11 @@ use App\Models\Accessory; use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -55,22 +57,9 @@ class CheckinAccessoryNotification extends Notification } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { - $notifyBy[] = 'slack'; + $notifyBy[] = SlackWebhookChannel::class; } - /** - * Only send notifications to users that have email addresses - */ - if ($this->target instanceof User && $this->target->email != '') { - Log::debug('The target is a user'); - - if ($this->item->checkin_email()) { - $notifyBy[] = 'mail'; - } - } - - Log::debug('checkin_email on this category is '.$this->item->checkin_email()); - return $notifyBy; } @@ -84,10 +73,18 @@ class CheckinAccessoryNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - 'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', ]; + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + return (new SlackMessage) ->content(':arrow_down: :keyboard: '.trans('mail.Accessory_Checkin_Notification')) ->from($botname) @@ -103,18 +100,29 @@ class CheckinAccessoryNotification extends Notification $admin = $this->admin; $item = $this->item; $note = $this->note; + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('Accessory_Checkin_Notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') + ->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '') + ->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) + ->fact(trans('mail.notes'), $note ?: ''); + } - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->addStartGroupToSection('activityTitle') - ->title(trans('Accessory_Checkin_Notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '') - ->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->present()->fullName()) - ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) - ->fact(trans('mail.notes'), $note ?: ''); + $message = trans('mail.Accessory_Checkin_Notification'); + $details = [ + trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name), + trans('mail.checked_into') => $item->location->name ? $item->location->name : '', + trans('mail.Accessory_Checkin_Notification'). ' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining')=> $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + return array($message, $details); } public function toGoogleChat() { @@ -142,24 +150,4 @@ class CheckinAccessoryNotification extends Notification ); } - - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - Log::debug('to email called'); - - return (new MailMessage)->markdown('notifications.markdown.checkin-accessory', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - ]) - ->subject(trans('mail.Accessory_Checkin_Notification')); - } } diff --git a/app/Notifications/CheckinAssetNotification.php b/app/Notifications/CheckinAssetNotification.php index 54b96777fe..5de3ff1be8 100644 --- a/app/Notifications/CheckinAssetNotification.php +++ b/app/Notifications/CheckinAssetNotification.php @@ -7,9 +7,11 @@ use App\Models\Asset; use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -50,7 +52,6 @@ class CheckinAssetNotification extends Notification */ public function via() { - $notifyBy = []; if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { $notifyBy[] = GoogleChatChannel::class; @@ -62,15 +63,7 @@ class CheckinAssetNotification extends Notification } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { Log::debug('use webhook'); - $notifyBy[] = 'slack'; - } - - /** - * Only send checkin notifications to users if the category - * has the corresponding checkbox checked. - */ - if ($this->item->checkin_email() && $this->target instanceof User && $this->target->email != '') { - $notifyBy[] = 'mail'; + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; @@ -86,10 +79,19 @@ class CheckinAssetNotification extends Notification $fields = [ trans('general.administrator') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', - trans('general.status') => $item->assetstatus->name, + trans('general.status') => $item->assetstatus?->name, trans('general.location') => ($item->location) ? $item->location->name : '', ]; + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + + return (new SlackMessage) ->content(':arrow_down: :computer: '.trans('mail.Asset_Checkin_Notification')) ->from($botname) @@ -106,16 +108,30 @@ class CheckinAssetNotification extends Notification $item = $this->item; $note = $this->note; - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->title(trans('mail.Asset_Checkin_Notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') - ->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '') - ->fact(trans('mail.Asset_Checkin_Notification')." by ", $admin->present()->fullName()) - ->fact(trans('admin/hardware/form.status'), $item->assetstatus->name) - ->fact(trans('mail.notes'), $note ?: ''); + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->title(trans('mail.Asset_Checkin_Notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') + ->fact(trans('mail.checked_into'), ($item->location) ? $item->location->name : '') + ->fact(trans('mail.Asset_Checkin_Notification') . " by ", $admin->present()->fullName()) + ->fact(trans('admin/hardware/form.status'), $item->assetstatus?->name) + ->fact(trans('mail.notes'), $note ?: ''); + } + + + $message = trans('mail.Asset_Checkin_Notification'); + $details = [ + trans('mail.asset') => htmlspecialchars_decode($item->present()->name), + trans('mail.checked_into') => ($item->location) ? $item->location->name : '', + trans('mail.Asset_Checkin_Notification')." by " => $admin->present()->fullName(), + trans('admin/hardware/form.status') => $item->assetstatus?->name, + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); } public function toGoogleChat() { @@ -135,41 +151,12 @@ class CheckinAssetNotification extends Notification Section::create( KeyValue::create( trans('mail.checked_into') ?: '', - $item->location->name ? $item->location->name : '', - trans('admin/hardware/form.status').": ".$item->assetstatus->name, + ($item->location) ? $item->location->name : '', + trans('admin/hardware/form.status').": ".$item->assetstatus?->name, ) ->onClick(route('hardware.show', $item->id)) ) ) ); - - } - - /** - * Get the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - $fields = []; - - // Check if the item has custom fields associated with it - if (($this->item->model) && ($this->item->model->fieldset)) { - $fields = $this->item->model->fieldset->fields; - } - - $message = (new MailMessage)->markdown('notifications.markdown.checkin-asset', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - 'fields' => $fields, - 'expected_checkin' => $this->expected_checkin, - ]) - ->subject(trans('mail.Asset_Checkin_Notification')); - - return $message; } } diff --git a/app/Notifications/CheckinLicenseSeatNotification.php b/app/Notifications/CheckinLicenseSeatNotification.php index 289e63a162..07c3308dbc 100644 --- a/app/Notifications/CheckinLicenseSeatNotification.php +++ b/app/Notifications/CheckinLicenseSeatNotification.php @@ -6,9 +6,11 @@ use App\Models\LicenseSeat; use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -58,15 +60,7 @@ class CheckinLicenseSeatNotification extends Notification } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { - $notifyBy[] = 'slack'; - } - - /** - * Only send checkin notifications to users if the category - * has the corresponding checkbox checked. - */ - if ($this->item->checkin_email() && $this->target instanceof User && $this->target->email != '') { - $notifyBy[] = 'mail'; + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; @@ -83,9 +77,18 @@ class CheckinLicenseSeatNotification extends Notification if ($admin) { $fields = [ - 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - 'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.from') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', ]; + + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + } else { $fields = [ 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', @@ -109,18 +112,30 @@ class CheckinLicenseSeatNotification extends Notification $admin = $this->admin; $item = $this->item; $note = $this->note; + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.License_Checkin_Notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'header') + ->fact(trans('mail.License_Checkin_Notification')." by ", $admin->present()->fullName() ?: 'CLI tool') + ->fact(trans('mail.checkedin_from'), $target->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) + ->fact(trans('mail.notes'), $note ?: ''); + } - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->addStartGroupToSection('activityTitle') - ->title(trans('mail.License_Checkin_Notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'header') - ->fact(trans('mail.License_Checkin_Notification')." by ", $admin->present()->fullName() ?: 'CLI tool') - ->fact(trans('mail.checkedin_from'), $target->present()->fullName()) - ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) - ->fact(trans('mail.notes'), $note ?: ''); + $message = trans('mail.License_Checkin_Notification'); + $details = [ + trans('mail.checkedin_from')=> $target->present()->fullName(), + trans('mail.license_for') => htmlspecialchars_decode($item->present()->name), + trans('mail.License_Checkin_Notification')." by " => $admin->present()->fullName() ?: 'CLI tool', + trans('admin/consumables/general.remaining') => $item->availCount()->count(), + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); } public function toGoogleChat() { @@ -149,23 +164,4 @@ class CheckinLicenseSeatNotification extends Notification ); } - - - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - return (new MailMessage)->markdown('notifications.markdown.checkin-license', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - ]) - ->subject(trans('mail.License_Checkin_Notification')); - } } diff --git a/app/Notifications/CheckoutAccessoryNotification.php b/app/Notifications/CheckoutAccessoryNotification.php index 803b697e86..016bfc526c 100644 --- a/app/Notifications/CheckoutAccessoryNotification.php +++ b/app/Notifications/CheckoutAccessoryNotification.php @@ -9,6 +9,7 @@ use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -30,6 +31,7 @@ class CheckoutAccessoryNotification extends Notification $this->item = $accessory; $this->admin = $checkedOutBy; $this->note = $note; + $this->checkout_qty = $accessory->checkout_qty; $this->target = $checkedOutTo; $this->acceptance = $acceptance; $this->settings = Setting::getSettings(); @@ -98,16 +100,24 @@ class CheckoutAccessoryNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - 'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', ]; + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + return (new SlackMessage) ->content(':arrow_up: :keyboard: Accessory Checked Out') ->from($botname) ->to($channel) ->attachment(function ($attachment) use ($item, $note, $admin, $fields) { - $attachment->title(htmlspecialchars_decode($item->present()->name), $item->present()->viewUrl()) + $attachment->title(htmlspecialchars_decode($this->checkout_qty.' x '.$item->present()->name), $item->present()->viewUrl()) ->fields($fields) ->content($note); }); @@ -119,6 +129,7 @@ class CheckoutAccessoryNotification extends Notification $item = $this->item; $note = $this->note; + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { return MicrosoftTeamsMessage::create() ->to($this->settings->webhook_endpoint) ->type('success') @@ -127,11 +138,24 @@ class CheckoutAccessoryNotification extends Notification ->addStartGroupToSection('activityText') ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') ->fact(trans('mail.assigned_to'), $target->present()->name) + ->fact(trans('general.qty'), $this->checkout_qty) ->fact(trans('mail.checkedout_from'), $item->location->name ? $item->location->name : '') ->fact(trans('mail.Accessory_Checkout_Notification') . " by ", $admin->present()->fullName()) ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) ->fact(trans('mail.notes'), $note ?: ''); + } + $message = trans('mail.Accessory_Checkout_Notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->name, + trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name), + trans('general.qty') => $this->checkout_qty, + trans('mail.checkedout_from') => $item->location->name ? $item->location->name : '', + trans('mail.Accessory_Checkout_Notification'). ' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining')=> $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + return array($message, $details); } public function toGoogleChat() { @@ -184,6 +208,7 @@ class CheckoutAccessoryNotification extends Notification 'eula' => $eula, 'req_accept' => $req_accept, 'accept_url' => $accept_url, + 'checkout_qty' => $this->checkout_qty, ]) ->subject(trans('mail.Confirm_accessory_delivery')); } diff --git a/app/Notifications/CheckoutAssetNotification.php b/app/Notifications/CheckoutAssetNotification.php index 1c8c901b52..8cdfbdd046 100644 --- a/app/Notifications/CheckoutAssetNotification.php +++ b/app/Notifications/CheckoutAssetNotification.php @@ -8,9 +8,10 @@ use App\Models\Setting; use App\Models\User; use Exception; use Illuminate\Bus\Queueable; -use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\Enums\Icon; use NotificationChannels\GoogleChat\Enums\ImageStyle; @@ -21,6 +22,9 @@ use NotificationChannels\GoogleChat\Widgets\KeyValue; use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel; use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage; use Illuminate\Support\Facades\Log; +use Osama\LaravelTeamsNotification\Logging\TeamsLoggingChannel; +use Osama\LaravelTeamsNotification\TeamsNotification; + class CheckoutAssetNotification extends Notification { use Queueable; @@ -32,14 +36,11 @@ class CheckoutAssetNotification extends Notification */ public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note) { + $this->settings = Setting::getSettings(); $this->item = $asset; $this->admin = $checkedOutBy; $this->note = $note; $this->target = $checkedOutTo; - $this->acceptance = $acceptance; - - $this->settings = Setting::getSettings(); - $this->last_checkout = ''; $this->expected_checkin = ''; @@ -53,7 +54,6 @@ class CheckoutAssetNotification extends Notification false); } } - /** * Get the notification's delivery channels. * @@ -62,70 +62,51 @@ class CheckoutAssetNotification extends Notification public function via() { $notifyBy = []; - if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { + + if (Setting::getSettings()->webhook_selected === 'google' && Setting::getSettings()->webhook_endpoint) { $notifyBy[] = GoogleChatChannel::class; } - if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) { + if (Setting::getSettings()->webhook_selected === 'microsoft' && Setting::getSettings()->webhook_endpoint) { $notifyBy[] = MicrosoftTeamsChannel::class; } - if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { + if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general' ) { Log::debug('use webhook'); - $notifyBy[] = 'slack'; - } - - /** - * Only send notifications to users that have email addresses - */ - if ($this->target instanceof User && $this->target->email != '') { - - /** - * Send an email if the asset requires acceptance, - * so the user can accept or decline the asset - */ - if ($this->item->requireAcceptance()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if the item has a EULA, since the user should always receive it - */ - if ($this->item->getEula()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if an email should be sent at checkin/checkout - */ - if ($this->item->checkin_email()) { - $notifyBy[1] = 'mail'; - } + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; } - public function toSlack() + public function toSlack() :SlackMessage { $target = $this->target; $admin = $this->admin; $item = $this->item; $note = $this->note; - $botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot'; + $botname = ($this->settings->webhook_botname) ?: 'Snipe-Bot'; $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - 'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', ]; - if (($this->expected_checkin) && ($this->expected_checkin != '')) { - $fields['Expected Checkin'] = $this->expected_checkin; + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + + if (($this->expected_checkin) && ($this->expected_checkin !== '')) { + $fields[trans('general.expected_checkin')] = $this->expected_checkin; } return (new SlackMessage) @@ -138,6 +119,7 @@ class CheckoutAssetNotification extends Notification ->content($note); }); } + public function toMicrosoftTeams() { $target = $this->target; @@ -145,17 +127,26 @@ class CheckoutAssetNotification extends Notification $item = $this->item; $note = $this->note; - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->title(trans('mail.Asset_Checkout_Notification')) - ->addStartGroupToSection('activityText') - ->fact(trans('mail.assigned_to'), $target->present()->name) - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') - ->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->present()->fullName()) - ->fact(trans('mail.notes'), $note ?: ''); - + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->title(trans('mail.Asset_Checkout_Notification')) + ->addStartGroupToSection('activityText') + ->fact(trans('mail.assigned_to'), $target->present()->name) + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') + ->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->present()->fullName()) + ->fact(trans('mail.notes'), $note ?: ''); + } + $message = trans('mail.Asset_Checkout_Notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->name, + trans('mail.asset') => htmlspecialchars_decode($item->present()->name), + trans('mail.Asset_Checkout_Notification'). ' by' => $admin->present()->fullName(), + trans('mail.notes') => $note ?: '', + ]; + return array($message, $details); } public function toGoogleChat() { @@ -184,42 +175,4 @@ public function toGoogleChat() ); } - - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; - $req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; - - $fields = []; - - // Check if the item has custom fields associated with it - if (($this->item->model) && ($this->item->model->fieldset)) { - $fields = $this->item->model->fieldset->fields; - } - - $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); - - $message = (new MailMessage)->markdown('notifications.markdown.checkout-asset', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - 'fields' => $fields, - 'eula' => $eula, - 'req_accept' => $req_accept, - 'accept_url' => $accept_url, - 'last_checkout' => $this->last_checkout, - 'expected_checkin' => $this->expected_checkin, - ]) - ->subject(trans('mail.Confirm_asset_delivery')); - - return $message; - } } diff --git a/app/Notifications/CheckoutConsumableNotification.php b/app/Notifications/CheckoutConsumableNotification.php index 6746795f2c..e8db8b8bd1 100644 --- a/app/Notifications/CheckoutConsumableNotification.php +++ b/app/Notifications/CheckoutConsumableNotification.php @@ -6,9 +6,11 @@ use App\Models\Consumable; use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -38,6 +40,7 @@ class CheckoutConsumableNotification extends Notification $this->note = $note; $this->target = $checkedOutTo; $this->acceptance = $acceptance; + $this->qty = $consumable->checkout_qty; $this->settings = Setting::getSettings(); } @@ -61,35 +64,7 @@ class CheckoutConsumableNotification extends Notification } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { - $notifyBy[] = 'slack'; - } - - /** - * Only send notifications to users that have email addresses - */ - if ($this->target instanceof User && $this->target->email != '') { - - /** - * Send an email if the asset requires acceptance, - * so the user can accept or decline the asset - */ - if ($this->item->requireAcceptance()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if the item has a EULA, since the user should always receive it - */ - if ($this->item->getEula()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if an email should be sent at checkin/checkout - */ - if ((method_exists($this->item, 'checkin_email')) && ($this->item->checkin_email())) { - $notifyBy[1] = 'mail'; - } + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; @@ -105,10 +80,18 @@ class CheckoutConsumableNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - 'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', ]; + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + return (new SlackMessage) ->content(':arrow_up: :paperclip: Consumable Checked Out') ->from($botname) @@ -126,17 +109,30 @@ class CheckoutConsumableNotification extends Notification $item = $this->item; $note = $this->note; - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->addStartGroupToSection('activityTitle') - ->title(trans('mail.Consumable_checkout_notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->present()->fullName()) - ->fact(trans('mail.assigned_to'), $target->present()->fullName()) - ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) - ->fact(trans('mail.notes'), $note ?: ''); + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.Consumable_checkout_notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') + ->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->present()->fullName()) + ->fact(trans('mail.assigned_to'), $target->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) + ->fact(trans('mail.notes'), $note ?: ''); + } + + $message = trans('mail.Consumable_checkout_notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->fullName(), + trans('mail.item') => htmlspecialchars_decode($item->present()->name), + trans('mail.Consumable_checkout_notification').' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining') => $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); } public function toGoogleChat() { @@ -165,30 +161,4 @@ class CheckoutConsumableNotification extends Notification ); } - - /** - * Get the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - Log::debug($this->item->getImageUrl()); - $eula = $this->item->getEula(); - $req_accept = $this->item->requireAcceptance(); - - $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); - - return (new MailMessage)->markdown('notifications.markdown.checkout-consumable', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - 'eula' => $eula, - 'req_accept' => $req_accept, - 'accept_url' => $accept_url, - ]) - ->subject(trans('mail.Confirm_consumable_delivery')); - } } diff --git a/app/Notifications/CheckoutLicenseSeatNotification.php b/app/Notifications/CheckoutLicenseSeatNotification.php index 8e0273c66e..fa00421885 100644 --- a/app/Notifications/CheckoutLicenseSeatNotification.php +++ b/app/Notifications/CheckoutLicenseSeatNotification.php @@ -6,9 +6,11 @@ use App\Models\LicenseSeat; use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -60,35 +62,7 @@ class CheckoutLicenseSeatNotification extends Notification } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { - $notifyBy[] = 'slack'; - } - - /** - * Only send notifications to users that have email addresses - */ - if ($this->target instanceof User && $this->target->email != '') { - - /** - * Send an email if the asset requires acceptance, - * so the user can accept or decline the asset - */ - if ($this->item->requireAcceptance()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if the item has a EULA, since the user should always receive it - */ - if ($this->item->getEula()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if an email should be sent at checkin/checkout - */ - if ($this->item->checkin_email()) { - $notifyBy[1] = 'mail'; - } + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; @@ -104,10 +78,18 @@ class CheckoutLicenseSeatNotification extends Notification $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ - 'To' => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', - 'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', + trans('general.to') => '<'.$target->present()->viewUrl().'|'.$target->present()->fullName().'>', + trans('general.by') => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', ]; + if ($item->location) { + $fields[trans('general.location')] = $item->location->name; + } + + if ($item->company) { + $fields[trans('general.company')] = $item->company->name; + } + return (new SlackMessage) ->content(':arrow_up: :floppy_disk: License Checked Out') ->from($botname) @@ -125,17 +107,29 @@ class CheckoutLicenseSeatNotification extends Notification $item = $this->item; $note = $this->note; - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->addStartGroupToSection('activityTitle') - ->title(trans('mail.License_Checkout_Notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.License_Checkout_Notification')." by ", $admin->present()->fullName()) - ->fact(trans('mail.assigned_to'), $target->present()->fullName()) - ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) - ->fact(trans('mail.notes'), $note ?: ''); + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.License_Checkout_Notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') + ->fact(trans('mail.License_Checkout_Notification')." by ", $admin->present()->fullName()) + ->fact(trans('mail.assigned_to'), $target->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) + ->fact(trans('mail.notes'), $note ?: ''); + } + + $message = trans('mail.License_Checkout_Notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->fullName(), + trans('mail.license_for') => htmlspecialchars_decode($item->present()->name), + trans('mail.License_Checkout_Notification').' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining') => $item->availCount()->count(), + trans('mail.notes') => $note ?: '', + ]; + return array($message, $details); } public function toGoogleChat() { @@ -164,29 +158,4 @@ class CheckoutLicenseSeatNotification extends Notification ); } - - /** - * Get the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; - $req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; - - $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); - - return (new MailMessage)->markdown('notifications.markdown.checkout-license', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - 'eula' => $eula, - 'req_accept' => $req_accept, - 'accept_url' => $accept_url, - ]) - ->subject(trans('mail.Confirm_license_delivery')); - } } diff --git a/app/Observers/AccessoryObserver.php b/app/Observers/AccessoryObserver.php index 661d00b4c2..0f8b2492cd 100644 --- a/app/Observers/AccessoryObserver.php +++ b/app/Observers/AccessoryObserver.php @@ -20,7 +20,7 @@ class AccessoryObserver $logAction->item_type = Accessory::class; $logAction->item_id = $accessory->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('update'); } @@ -37,7 +37,7 @@ class AccessoryObserver $logAction->item_type = Accessory::class; $logAction->item_id = $accessory->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($accessory->imported) { $logAction->setActionSource('importer'); } @@ -56,7 +56,7 @@ class AccessoryObserver $logAction->item_type = Accessory::class; $logAction->item_id = $accessory->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } } diff --git a/app/Observers/AssetObserver.php b/app/Observers/AssetObserver.php index f77c4cc00f..6c07a355ff 100644 --- a/app/Observers/AssetObserver.php +++ b/app/Observers/AssetObserver.php @@ -40,15 +40,16 @@ class AssetObserver // If the asset isn't being checked out or audited, log the update. // (Those other actions already create log entries.) - if (($attributes['assigned_to'] == $attributesOriginal['assigned_to']) - && ($same_checkout_counter) && ($same_checkin_counter) + if (array_key_exists('assigned_to', $attributes) && array_key_exists('assigned_to', $attributesOriginal) + && ($attributes['assigned_to'] == $attributesOriginal['assigned_to']) + && ($same_checkout_counter) && ($same_checkin_counter) && ((isset( $attributes['next_audit_date']) ? $attributes['next_audit_date'] : null) == (isset($attributesOriginal['next_audit_date']) ? $attributesOriginal['next_audit_date']: null)) && ($attributes['last_checkout'] == $attributesOriginal['last_checkout']) && (!$restoring_or_deleting)) { $changed = []; foreach ($asset->getRawOriginal() as $key => $value) { - if ($asset->getRawOriginal()[$key] != $asset->getAttributes()[$key]) { + if ((array_key_exists($key, $asset->getAttributes())) && ($asset->getRawOriginal()[$key] != $asset->getAttributes()[$key])) { $changed[$key]['old'] = $asset->getRawOriginal()[$key]; $changed[$key]['new'] = $asset->getAttributes()[$key]; } @@ -61,8 +62,9 @@ class AssetObserver $logAction = new Actionlog(); $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->log_meta = json_encode($changed); $logAction->logaction('update'); } @@ -80,7 +82,7 @@ class AssetObserver { if ($settings = Setting::getSettings()) { $tag = $asset->asset_tag; - $prefix = $settings->auto_increment_prefix; + $prefix = (string)($settings->auto_increment_prefix ?? ''); $number = substr($tag, strlen($prefix)); // IF - auto_increment_assets is on, AND (there is no prefix OR the prefix matches the start of the tag) // AND the rest of the string after the prefix is all digits, THEN... @@ -107,8 +109,9 @@ class AssetObserver $logAction = new Actionlog(); $logAction->item_type = Asset::class; // can we instead say $logAction->item = $asset ? $logAction->item_id = $asset->id; + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($asset->imported) { $logAction->setActionSource('importer'); } @@ -127,7 +130,8 @@ class AssetObserver $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->action_date = date('Y-m-d H:i:s'); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } @@ -142,8 +146,9 @@ class AssetObserver $logAction = new Actionlog(); $logAction->item_type = Asset::class; $logAction->item_id = $asset->id; + $logAction->action_date = date('Y-m-d H:i:s'); $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('restore'); } @@ -158,7 +163,7 @@ class AssetObserver * is used in this observer, it doesn't actually exist yet and the migration will break unless we * use saveQuietly() in the migration which skips this observer. * - * @see https://github.com/snipe/snipe-it/issues/13723#issuecomment-1761315938 + * @see https://github.com/grokability/snipe-it/issues/13723#issuecomment-1761315938 */ public function saving(Asset $asset) { @@ -171,7 +176,7 @@ class AssetObserver // determine if explicit and set eol_explicit to true if (!is_null($asset->asset_eol_date) && !is_null($asset->purchase_date)) { if($asset->model->eol > 0) { - $months = Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date); + $months = (int) Carbon::parse($asset->asset_eol_date)->diffInMonths($asset->purchase_date, true); if($months != $asset->model->eol) { $asset->eol_explicit = true; } diff --git a/app/Observers/ComponentObserver.php b/app/Observers/ComponentObserver.php index 44bf819353..cd2c58c367 100644 --- a/app/Observers/ComponentObserver.php +++ b/app/Observers/ComponentObserver.php @@ -20,7 +20,7 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('update'); } @@ -37,7 +37,7 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($component->imported) { $logAction->setActionSource('importer'); } @@ -56,7 +56,7 @@ class ComponentObserver $logAction->item_type = Component::class; $logAction->item_id = $component->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } } diff --git a/app/Observers/ConsumableObserver.php b/app/Observers/ConsumableObserver.php index 08c56d2ee2..57471cee9c 100644 --- a/app/Observers/ConsumableObserver.php +++ b/app/Observers/ConsumableObserver.php @@ -5,6 +5,8 @@ namespace App\Observers; use App\Models\Actionlog; use App\Models\Consumable; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; class ConsumableObserver { @@ -16,12 +18,26 @@ class ConsumableObserver */ public function updated(Consumable $consumable) { - $logAction = new Actionlog(); - $logAction->item_type = Consumable::class; - $logAction->item_id = $consumable->id; - $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); - $logAction->logaction('update'); + + $changed = []; + + foreach ($consumable->getRawOriginal() as $key => $value) { + // Check and see if the value changed + if ($consumable->getRawOriginal()[$key] != $consumable->getAttributes()[$key]) { + $changed[$key]['old'] = $consumable->getRawOriginal()[$key]; + $changed[$key]['new'] = $consumable->getAttributes()[$key]; + } + } + + if (count($changed) > 0) { + $logAction = new Actionlog(); + $logAction->item_type = Consumable::class; + $logAction->item_id = $consumable->id; + $logAction->created_at = date('Y-m-d H:i:s'); + $logAction->created_by = auth()->id(); + $logAction->log_meta = json_encode($changed); + $logAction->logaction('update'); + } } /** @@ -37,7 +53,7 @@ class ConsumableObserver $logAction->item_type = Consumable::class; $logAction->item_id = $consumable->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($consumable->imported) { $logAction->setActionSource('importer'); } @@ -52,11 +68,37 @@ class ConsumableObserver */ public function deleting(Consumable $consumable) { + + $consumable->users()->detach(); + $uploads = $consumable->uploads; + + foreach ($uploads as $file) { + try { + Storage::delete('private_uploads/consumables/'.$file->filename); + $file->delete(); + } catch (\Exception $e) { + Log::info($e); + } + } + + + + try { + Storage::disk('public')->delete('consumables/'.$consumable->image); + } catch (\Exception $e) { + Log::info($e); + } + + $consumable->image = null; + $consumable->save(); + + + $logAction = new Actionlog(); $logAction->item_type = Consumable::class; $logAction->item_id = $consumable->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } } diff --git a/app/Observers/LicenseObserver.php b/app/Observers/LicenseObserver.php index de4863fafa..4e355bf639 100644 --- a/app/Observers/LicenseObserver.php +++ b/app/Observers/LicenseObserver.php @@ -20,7 +20,7 @@ class LicenseObserver $logAction->item_type = License::class; $logAction->item_id = $license->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('update'); } @@ -37,7 +37,7 @@ class LicenseObserver $logAction->item_type = License::class; $logAction->item_id = $license->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); if($license->imported) { $logAction->setActionSource('importer'); } @@ -56,7 +56,7 @@ class LicenseObserver $logAction->item_type = License::class; $logAction->item_id = $license->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } } diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index c7c2a460cb..acde9ceaed 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -83,7 +83,7 @@ class UserObserver $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ? $logAction->target_id = $user->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->log_meta = json_encode($changed); $logAction->logaction('update'); } @@ -105,7 +105,7 @@ class UserObserver $logAction->item_type = User::class; // can we instead say $logAction->item = $asset ? $logAction->item_id = $user->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('create'); } @@ -123,7 +123,7 @@ class UserObserver $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ? $logAction->target_id = $user->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('delete'); } @@ -141,7 +141,7 @@ class UserObserver $logAction->target_type = User::class; // can we instead say $logAction->item = $asset ? $logAction->target_id = $user->id; $logAction->created_at = date('Y-m-d H:i:s'); - $logAction->user_id = Auth::id(); + $logAction->created_by = auth()->id(); $logAction->logaction('restore'); } diff --git a/app/Policies/CustomFieldsetPolicy.php b/app/Policies/CustomFieldsetPolicy.php index a6c06a7007..4bb7e2dab6 100644 --- a/app/Policies/CustomFieldsetPolicy.php +++ b/app/Policies/CustomFieldsetPolicy.php @@ -11,7 +11,7 @@ class CustomFieldsetPolicy extends SnipePermissionsPolicy * This allows us to use the existing permissions in use and have more * semantically correct authorization checks for custom fieldsets. * - * See: https://github.com/snipe/snipe-it/pull/5795 + * See: https://github.com/grokability/snipe-it/pull/5795 */ return 'customfields'; } diff --git a/app/Policies/LicensePolicy.php b/app/Policies/LicensePolicy.php index 544835c3b8..38f5700ad1 100644 --- a/app/Policies/LicensePolicy.php +++ b/app/Policies/LicensePolicy.php @@ -22,7 +22,7 @@ class LicensePolicy extends CheckoutablePermissionsPolicy * 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 + * @see https://github.com/grokability/snipe-it/issues/6956 * @param \App\Models\User $user * @param \App\Models\License $license * @return mixed diff --git a/app/Presenters/AccessoryPresenter.php b/app/Presenters/AccessoryPresenter.php index fd6122cab7..86b4d1bc0f 100644 --- a/app/Presenters/AccessoryPresenter.php +++ b/app/Presenters/AccessoryPresenter.php @@ -78,28 +78,36 @@ class AccessoryPresenter extends Presenter 'sortable' => true, 'title' => trans('general.location'), 'formatter' => 'locationsLinkObjFormatter', + ], [ + 'field' => 'min_amt', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.min_amt'), + 'formatter' => 'minAmtFormatter', + 'class' => 'text-right text-padding-number-cell', ], [ 'field' => 'qty', 'searchable' => false, 'sortable' => true, 'title' => trans('admin/accessories/general.total'), + 'footerFormatter' => 'qtySumFormatter', + 'class' => 'text-right text-padding-number-cell', ], [ - 'field' => 'remaining_qty', + 'field' => 'remaining', 'searchable' => false, 'sortable' => false, 'visible' => false, 'title' => trans('admin/accessories/general.remaining'), + 'footerFormatter' => 'qtySumFormatter', + 'class' => 'text-right text-padding-number-cell', ],[ - 'field' => 'users_count', + 'field' => 'checkouts_count', 'searchable' => false, 'sortable' => true, 'visible' => true, 'title' => trans('general.checked_out'), - ], [ - 'field' => 'min_qty', - 'searchable' => false, - 'sortable' => true, - 'title' => trans('general.min_amt'), + 'footerFormatter' => 'qtySumFormatter', + 'class' => 'text-right text-padding-number-cell', ], [ 'field' => 'purchase_date', 'searchable' => true, @@ -113,7 +121,7 @@ class AccessoryPresenter extends Presenter 'sortable' => true, 'title' => trans('general.purchase_cost'), 'footerFormatter' => 'sumFormatterQuantity', - 'class' => 'text-right', + 'class' => 'text-right text-padding-number-cell', ], [ 'field' => 'order_number', 'searchable' => true, @@ -127,6 +135,29 @@ class AccessoryPresenter extends Presenter 'visible' => false, 'title' => trans('general.notes'), 'formatter' => 'notesFormatter' + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'change', 'searchable' => false, @@ -135,7 +166,7 @@ class AccessoryPresenter extends Presenter 'title' => trans('general.change'), 'formatter' => 'accessoriesInOutFormatter', ], [ - 'field' => 'actions', + 'field' => 'available_actions', 'searchable' => false, 'sortable' => false, 'switchable' => false, @@ -147,6 +178,74 @@ class AccessoryPresenter extends Presenter return json_encode($layout); } + + public static function assignedDataTableLayout() + { + $layout = [ + [ + 'field' => 'id', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + ], + [ + 'field' => 'assigned_to.image', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.image'), + 'visible' => true, + 'formatter' => 'imageFormatter', + ], + [ + 'field' => 'assigned_to', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.checked_out_to'), + 'visible' => true, + 'formatter' => 'polymorphicItemFormatter', + ], + [ + 'field' => 'note', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.notes'), + 'visible' => true, + ], + [ + 'field' => 'created_at', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('admin/hardware/table.checkout_date'), + 'visible' => true, + 'formatter' => 'dateDisplayFormatter', + ], + [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => false, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], + [ + 'field' => 'available_actions', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('table.actions'), + 'formatter' => 'accessoriesInOutFormatter', + ], + ]; + + return json_encode($layout); + } + /** * Pregenerated link to this accessories view page. * @return string diff --git a/app/Presenters/ActionlogPresenter.php b/app/Presenters/ActionlogPresenter.php index 2794b6c5fb..4b7aefc87a 100644 --- a/app/Presenters/ActionlogPresenter.php +++ b/app/Presenters/ActionlogPresenter.php @@ -7,7 +7,7 @@ namespace App\Presenters; */ class ActionlogPresenter extends Presenter { - public function admin() + public function adminuser() { if ($user = $this->model->user) { if (empty($user->deleted_at)) { @@ -42,27 +42,27 @@ class ActionlogPresenter extends Presenter // User related icons if ($this->itemType() == 'user') { - if ($this->actionType()=='2fa reset') { + if ($this->action_type == '2fa reset') { return 'fa-solid fa-mobile-screen'; } - if ($this->actionType()=='create new') { + if ($this->action_type == 'create') { return 'fa-solid fa-user-plus'; } - if ($this->actionType()=='merged') { + if ($this->action_type == 'merged') { return 'fa-solid fa-people-arrows'; } - if ($this->actionType()=='delete') { + if ($this->action_type == 'delete') { return 'fa-solid fa-user-minus'; } - if ($this->actionType()=='delete') { + if ($this->action_type == 'delete') { return 'fa-solid fa-user-minus'; } - if ($this->actionType()=='update') { + if ($this->action_type == 'update') { return 'fa-solid fa-user-pen'; } @@ -70,34 +70,42 @@ class ActionlogPresenter extends Presenter } // Everything else - if ($this->actionType()=='create new') { + if ($this->action_type == 'create') { return 'fa-solid fa-plus'; } - if ($this->actionType()=='delete') { - return 'fa-solid fa-user-xmark'; + if ($this->action_type == 'delete') { + return 'fa-solid fa-trash'; } - if ($this->actionType()=='update') { + if ($this->action_type == 'update') { return 'fa-solid fa-pen'; } - if ($this->actionType()=='restore') { + if ($this->action_type == 'restore') { return 'fa-solid fa-trash-arrow-up'; } - if ($this->actionType()=='upload') { + if ($this->action_type == 'upload') { return 'fas fa-paperclip'; } - if ($this->actionType()=='checkout') { + if ($this->action_type == 'checkout') { return 'fa-solid fa-rotate-left'; } - if ($this->actionType()=='checkin from') { + if ($this->action_type == 'checkin from') { return 'fa-solid fa-rotate-right'; } + if ($this->action_type == 'note_added') { + return 'fas fa-sticky-note'; + } + + if ($this->action_type == 'audit') { + return 'fas fa-clipboard-check'; + } + return 'fa-solid fa-rotate-right'; } diff --git a/app/Presenters/AssetAuditPresenter.php b/app/Presenters/AssetAuditPresenter.php index a332950de1..a4d208fc0e 100644 --- a/app/Presenters/AssetAuditPresenter.php +++ b/app/Presenters/AssetAuditPresenter.php @@ -20,6 +20,7 @@ class AssetAuditPresenter extends Presenter [ 'field' => 'checkbox', 'checkbox' => true, + 'titleTooltip' => trans('general.select_all_none'), ], [ 'field' => 'id', diff --git a/app/Presenters/AssetMaintenancesPresenter.php b/app/Presenters/AssetMaintenancesPresenter.php index 3908720dc3..0648613adb 100644 --- a/app/Presenters/AssetMaintenancesPresenter.php +++ b/app/Presenters/AssetMaintenancesPresenter.php @@ -22,6 +22,15 @@ class AssetMaintenancesPresenter extends Presenter 'title' => trans('general.id'), 'visible' => false, ], [ + 'field' => 'title', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.name'), + 'visible' => true, + 'formatter' => 'maintenancesLinkFormatter', + ], + [ 'field' => 'company', 'searchable' => true, 'sortable' => true, @@ -81,12 +90,6 @@ class AssetMaintenancesPresenter extends Presenter 'searchable' => true, 'sortable' => true, 'title' => trans('admin/asset_maintenances/form.asset_maintenance_type'), - ], [ - 'field' => 'title', - 'searchable' => true, - 'sortable' => true, - 'switchable' => false, - 'title' => trans('admin/asset_maintenances/form.title'), ], [ 'field' => 'start_date', 'searchable' => true, @@ -117,12 +120,29 @@ class AssetMaintenancesPresenter extends Presenter 'title' => trans('admin/asset_maintenances/form.cost'), 'class' => 'text-right', ], [ - 'field' => 'user_id', - 'searchable' => true, + 'field' => 'created_by', + 'searchable' => false, 'sortable' => true, - 'title' => trans('general.admin'), + 'title' => trans('general.created_by'), + 'visible' => false, 'formatter' => 'usersLinkObjFormatter', ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ],[ 'field' => 'actions', 'searchable' => false, 'sortable' => false, diff --git a/app/Presenters/AssetModelPresenter.php b/app/Presenters/AssetModelPresenter.php index da93092b91..2ed019eaea 100644 --- a/app/Presenters/AssetModelPresenter.php +++ b/app/Presenters/AssetModelPresenter.php @@ -15,6 +15,7 @@ class AssetModelPresenter extends Presenter [ 'field' => 'checkbox', 'checkbox' => true, + 'titleTooltip' => trans('general.select_all_none'), ], [ 'field' => 'id', @@ -73,7 +74,10 @@ class AssetModelPresenter extends Presenter 'switchable' => true, 'title' => trans('mail.min_QTY'), 'visible' => true, + 'formatter' => 'minAmtFormatter', + 'class' => 'text-right text-padding-number-cell', ], + [ 'field' => 'assets_count', 'searchable' => false, @@ -81,6 +85,19 @@ class AssetModelPresenter extends Presenter 'switchable' => true, 'title' => trans('admin/models/table.numassets'), 'visible' => true, + 'class' => 'text-right text-padding-number-cell', + 'footerFormatter' => 'qtySumFormatter', + ], + + [ + 'field' => 'remaining', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.remaining'), + 'visible' => true, + 'class' => 'text-right text-padding-number-cell', + 'footerFormatter' => 'qtySumFormatter', ], [ 'field' => 'depreciation', @@ -135,19 +152,27 @@ class AssetModelPresenter extends Presenter 'formatter' => 'notesFormatter', ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ 'field' => 'created_at', 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.created_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', - ], - [ + ], [ 'field' => 'updated_at', 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.updated_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], diff --git a/app/Presenters/AssetPresenter.php b/app/Presenters/AssetPresenter.php index 163ee1b606..f4310b3a1a 100644 --- a/app/Presenters/AssetPresenter.php +++ b/app/Presenters/AssetPresenter.php @@ -21,6 +21,7 @@ class AssetPresenter extends Presenter [ 'field' => 'checkbox', 'checkbox' => true, + 'titleTooltip' => trans('general.select_all_none'), ], [ 'field' => 'id', 'searchable' => false, @@ -35,7 +36,7 @@ class AssetPresenter extends Presenter 'switchable' => true, 'title' => trans('general.company'), 'visible' => false, - 'formatter' => 'assetCompanyObjFilterFormatter', + 'formatter' => 'companiesLinkObjFormatter', ], [ 'field' => 'name', 'searchable' => true, @@ -233,18 +234,28 @@ class AssetPresenter extends Presenter 'title' => trans('general.user_requests_count'), ], [ - 'field' => 'created_at', + 'field' => 'created_by', 'searchable' => false, 'sortable' => true, + 'title' => trans('general.created_by'), 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], + [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, 'title' => trans('general.created_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'updated_at', - 'searchable' => false, + 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.updated_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'last_checkout', @@ -287,6 +298,7 @@ class AssetPresenter extends Presenter 'sortable' => true, 'visible' => false, 'title' => trans('general.byod'), + 'class' => 'byod', 'formatter' => 'trueFalseFormatter', ], @@ -342,6 +354,74 @@ class AssetPresenter extends Presenter return json_encode($layout); } + + public static function assignedAccessoriesDataTableLayout() + { + $layout = [ + [ + 'field' => 'id', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + ], + [ + 'field' => 'accessory', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.accessory'), + 'visible' => true, + 'formatter' => 'accessoriesLinkObjFormatter', + ], + [ + 'field' => 'image', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.image'), + 'visible' => true, + 'formatter' => 'imageFormatter', + ], + [ + 'field' => 'note', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.notes'), + 'visible' => true, + ], + [ + 'field' => 'created_at', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('admin/hardware/table.checkout_date'), + 'visible' => true, + 'formatter' => 'dateDisplayFormatter', + ], + [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => false, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], + [ + 'field' => 'available_actions', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('table.actions'), + 'formatter' => 'accessoriesInOutFormatter', + ], + ]; + + return json_encode($layout); + } + /** * Generate html link to this items name. * @return string @@ -476,7 +556,7 @@ class AssetPresenter extends Presenter */ public function statusMeta() { - if ($this->model->assigned) { + if ($this->model->assigned_to) { return 'deployed'; } @@ -556,13 +636,12 @@ class AssetPresenter extends Presenter } /** - * Used to take user created warranty URL and dynamically fill in the needed values per asset + * Used to take user created URL and dynamically fill in the needed values per asset * @return string */ - public function dynamicWarrantyUrl() + public function dynamicUrl($dynamic_url) { - $warranty_lookup_url = $this->model->model->manufacturer->warranty_lookup_url; - $url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale, $warranty_lookup_url)); + $url = (str_replace('{LOCALE}',\App\Models\Setting::getSettings()->locale, $dynamic_url)); $url = (str_replace('{SERIAL}', urlencode($this->model->serial), $url)); $url = (str_replace('{MODEL_NAME}', urlencode($this->model->model->name), $url)); $url = (str_replace('{MODEL_NUMBER}', urlencode($this->model->model->model_number), $url)); @@ -580,6 +659,6 @@ class AssetPresenter extends Presenter public function glyph() { - return ''; + return ''; } } diff --git a/app/Presenters/CategoryPresenter.php b/app/Presenters/CategoryPresenter.php index fbf431637c..d4a9f01a05 100644 --- a/app/Presenters/CategoryPresenter.php +++ b/app/Presenters/CategoryPresenter.php @@ -78,18 +78,33 @@ class CategoryPresenter extends Presenter 'visible' => true, "formatter" => 'trueFalseFormatter', ], [ - 'field' => 'created_at', + 'field' => 'notes', 'searchable' => true, 'sortable' => true, 'visible' => false, + 'title' => trans('general.notes'), + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, 'title' => trans('general.created_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'updated_at', 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.updated_at'), + 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'actions', diff --git a/app/Presenters/CompanyPresenter.php b/app/Presenters/CompanyPresenter.php index 7603191fc1..fb86166092 100644 --- a/app/Presenters/CompanyPresenter.php +++ b/app/Presenters/CompanyPresenter.php @@ -65,54 +65,75 @@ class CompanyPresenter extends Presenter 'field' => 'users_count', 'searchable' => false, 'sortable' => true, - 'title' => '', + 'title' => trans('general.users'), 'visible' => true, + 'class' => 'css-users', ], [ 'field' => 'assets_count', 'searchable' => false, 'sortable' => true, - 'title' => '', + 'title' => trans('general.assets'), 'visible' => true, + 'class' => 'css-barcode', ], [ 'field' => 'licenses_count', 'searchable' => false, 'sortable' => true, + 'title' => trans('general.licenses'), 'visible' => true, - 'title' => ' ', + 'class' => 'css-license', ], [ 'field' => 'accessories_count', 'searchable' => false, 'sortable' => true, + 'title' => trans('general.accessories'), 'visible' => true, - 'title' => ' ', + 'class' => 'css-accessory', ], [ 'field' => 'consumables_count', 'searchable' => false, 'sortable' => true, + 'title' => trans('general.consumables'), 'visible' => true, - 'title' => ' ', + 'class' => 'css-consumable', ], [ 'field' => 'components_count', 'searchable' => false, 'sortable' => true, + 'title' => trans('general.components'), 'visible' => true, - 'title' => ' ', + 'class' => 'css-component', ], [ - 'field' => 'updated_at', - 'searchable' => false, + 'field' => 'notes', + 'searchable' => true, 'sortable' => true, 'visible' => false, - 'title' => trans('general.updated_at'), - 'formatter' => 'createdAtFormatter', + 'title' => trans('general.notes'), + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', ], [ 'field' => 'created_at', - 'searchable' => false, + 'searchable' => true, 'sortable' => true, - 'visible' => false, + 'switchable' => true, 'title' => trans('general.created_at'), - 'formatter' => 'createdAtFormatter', + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'actions', 'searchable' => false, diff --git a/app/Presenters/ComponentPresenter.php b/app/Presenters/ComponentPresenter.php index d142d7abc2..61f7a0ef4b 100644 --- a/app/Presenters/ComponentPresenter.php +++ b/app/Presenters/ComponentPresenter.php @@ -66,25 +66,43 @@ class ComponentPresenter extends Presenter 'title' => trans('general.supplier'), 'visible' => false, 'formatter' => 'suppliersLinkObjFormatter', - ], - [ + ], [ + 'field' => 'model_number', + 'searchable' => true, + 'sortable' => true, + 'title' => trans('admin/models/table.modelnumber'), + ], [ + 'field' => 'manufacturer', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.manufacturer'), + 'visible' => false, + 'formatter' => 'manufacturersLinkObjFormatter', + ], [ + 'field' => 'min_amt', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.min_amt'), + 'visible' => true, + 'class' => 'text-right text-padding-number-cell', + 'formatter' => 'minAmtFormatter', + ], [ 'field' => 'qty', 'searchable' => false, 'sortable' => true, 'title' => trans('admin/components/general.total'), 'visible' => true, + 'class' => 'text-right text-padding-number-cell', + 'footerFormatter' => 'qtySumFormatter', ], [ 'field' => 'remaining', 'searchable' => false, 'sortable' => false, 'title' => trans('admin/components/general.remaining'), 'visible' => true, - ], [ - 'field' => 'min_amt', - 'searchable' => false, - 'sortable' => false, - 'title' => trans('general.min_amt'), - 'visible' => true, + 'class' => 'text-right text-padding-number-cell', + 'footerFormatter' => 'qtySumFormatter', ], [ 'field' => 'location', 'searchable' => true, @@ -119,6 +137,27 @@ class ComponentPresenter extends Presenter 'visible' => false, 'title' => trans('general.notes'), 'formatter' => 'notesFormatter', + ],[ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ],[ + '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', ], ]; diff --git a/app/Presenters/ConsumablePresenter.php b/app/Presenters/ConsumablePresenter.php index d3e73de1cf..e777cfe26b 100644 --- a/app/Presenters/ConsumablePresenter.php +++ b/app/Presenters/ConsumablePresenter.php @@ -75,22 +75,28 @@ class ConsumablePresenter extends Presenter ], [ 'field' => 'qty', 'searchable' => false, - 'sortable' => false, + 'sortable' => true, 'title' => trans('admin/components/general.total'), 'visible' => true, + 'class' => 'text-right text-padding-number-cell', + 'footerFormatter' => 'qtySumFormatter', ], [ 'field' => 'remaining', 'searchable' => false, - 'sortable' => false, + 'sortable' => true, 'title' => trans('admin/components/general.remaining'), 'visible' => true, + 'class' => 'text-right text-padding-number-cell', + 'footerFormatter' => 'qtySumFormatter', ], [ 'field' => 'min_amt', 'searchable' => false, - 'sortable' => false, + 'sortable' => true, 'title' => trans('general.min_amt'), 'visible' => true, - ], [ + 'formatter' => 'minAmtFormatter', + 'class' => 'text-right text-padding-number-cell', + ], [ 'field' => 'location', 'searchable' => true, 'sortable' => true, @@ -123,7 +129,7 @@ class ConsumablePresenter extends Presenter 'title' => trans('general.purchase_cost'), 'visible' => true, 'footerFormatter' => 'sumFormatterQuantity', - 'class' => 'text-right', + 'class' => 'text-right text-padding-number-cell', ], [ 'field' => 'notes', 'searchable' => true, @@ -131,6 +137,27 @@ class ConsumablePresenter extends Presenter 'visible' => false, 'title' => trans('general.notes'), 'formatter' => 'notesFormatter', + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ],[ + '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' => 'change', 'searchable' => false, diff --git a/app/Presenters/DepreciationPresenter.php b/app/Presenters/DepreciationPresenter.php index 9df1fe1322..3f240fcc53 100644 --- a/app/Presenters/DepreciationPresenter.php +++ b/app/Presenters/DepreciationPresenter.php @@ -47,6 +47,48 @@ class DepreciationPresenter extends Presenter "visible" => true, ], [ + 'field' => 'assets_count', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.assets'), + 'visible' => true, + ], + [ + 'field' => 'models_count', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.asset_models'), + 'visible' => true, + ], [ + 'field' => 'licenses_count', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.licenses'), + 'visible' => true, + ],[ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ 'field' => 'actions', 'searchable' => false, 'sortable' => false, diff --git a/app/Presenters/DepreciationReportPresenter.php b/app/Presenters/DepreciationReportPresenter.php index 50a8b73b54..1d4c459614 100644 --- a/app/Presenters/DepreciationReportPresenter.php +++ b/app/Presenters/DepreciationReportPresenter.php @@ -140,7 +140,7 @@ class DepreciationReportPresenter extends Presenter ], [ "field" => "book_value", "searchable" => true, - "sortable" => true, + "sortable" => false, "visible" => true, "title" => trans('admin/hardware/table.book_value'), "footerFormatter" => 'sumFormatter', @@ -394,6 +394,6 @@ class DepreciationReportPresenter extends Presenter public function glyph() { - return ''; + return ''; } } diff --git a/app/Presenters/HistoryPresenter.php b/app/Presenters/HistoryPresenter.php new file mode 100644 index 0000000000..4b5d7d1f2b --- /dev/null +++ b/app/Presenters/HistoryPresenter.php @@ -0,0 +1,172 @@ + 'id', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + 'class' => 'hidden-xs', + ], + [ + 'field' => 'icon', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/hardware/table.icon'), + 'visible' => true, + 'class' => 'hidden-xs', + 'formatter' => 'iconFormatter', + ], + [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => true, + 'formatter' => 'dateDisplayFormatter', + ], + [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => true, + 'formatter' => 'usersLinkObjFormatter', + ], + [ + 'field' => 'action_date', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.action_date'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], + [ + 'field' => 'action_type', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.action'), + 'visible' => true, + ], + [ + 'field' => 'item', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.item'), + 'visible' => true, + 'formatter' => 'polymorphicItemFormatter', + ], + ]; + + + if ($serial) { + $extra = [ + [ + 'field' => 'item.serial', + 'title' => trans('admin/hardware/table.serial'), + 'visible' => false, + ] + ]; + } + + $layout_end = [ + [ + 'field' => 'target', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.target'), + 'visible' => true, + 'formatter' => 'polymorphicItemFormatter', + ], + [ + 'field' => 'file', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.file_name'), + 'visible' => true, + 'formatter' => 'fileUploadNameFormatter', + ], + [ + 'field' => 'file_download', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.download'), + 'visible' => true, + 'formatter' => 'fileUploadFormatter', + ], + [ + 'field' => 'note', + 'searchable' => true, + 'sortable' => true, + 'visible' => true, + 'title' => trans('general.notes'), + 'formatter' => 'notesFormatter' + ], + [ + 'field' => 'signature_file', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.signature'), + 'visible' => false, + 'formatter' => 'imageFormatter', + ], + [ + 'field' => 'log_meta', + 'searchable' => false, + 'sortable' => false, + 'visible' => true, + 'title' => trans('admin/hardware/table.changed'), + 'formatter' => 'changeLogFormatter', + ], + [ + 'field' => 'remote_ip', + 'searchable' => false, + 'sortable' => false, + 'visible' => false, + 'title' => trans('admin/settings/general.login_ip'), + ], + [ + 'field' => 'user_agent', + 'searchable' => false, + 'sortable' => false, + 'visible' => false, + 'title' => trans('admin/settings/general.login_user_agent'), + ], + [ + 'field' => 'action_source', + 'searchable' => false, + 'sortable' => false, + 'visible' => false, + 'title' => trans('general.action_source'), + ], + ]; + + $merged = array_merge($layout_start, $extra, $layout_end); + return json_encode($merged); + } + +} \ No newline at end of file diff --git a/app/Presenters/LicensePresenter.php b/app/Presenters/LicensePresenter.php index 8ca8e120f2..e2a21a8bcf 100644 --- a/app/Presenters/LicensePresenter.php +++ b/app/Presenters/LicensePresenter.php @@ -41,7 +41,7 @@ class LicensePresenter extends Presenter 'searchable' => true, 'sortable' => true, 'title' => trans('admin/licenses/form.license_key'), - 'formatter' => 'licensesLinkFormatter', + 'formatter' => 'licenseKeyFormatter', ], [ 'field' => 'expiration_date', 'searchable' => true, @@ -80,24 +80,28 @@ class LicensePresenter extends Presenter 'sortable' => true, 'title' => trans('general.manufacturer'), 'formatter' => 'manufacturersLinkObjFormatter', - ], [ - 'field' => 'seats', - 'searchable' => false, - 'sortable' => true, - 'title' => trans('admin/accessories/general.total'), - ], [ - 'field' => 'free_seats_count', - 'searchable' => false, - 'sortable' => true, - 'title' => trans('admin/accessories/general.remaining'), - ], - [ + ], [ 'field' => 'min_amt', 'searchable' => false, 'sortable' => true, 'title' => trans('mail.min_QTY'), 'formatter' => 'minAmtFormatter', - ],[ + 'class' => 'text-right text-padding-number-cell', + ], [ + 'field' => 'seats', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('admin/accessories/general.total'), + 'class' => 'text-right text-padding-number-cell', + 'footerFormatter' => 'qtySumFormatter', + ], [ + 'field' => 'free_seats_count', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('admin/accessories/general.remaining'), + 'class' => 'text-right text-padding-number-cell', + 'footerFormatter' => 'qtySumFormatter', + ], [ 'field' => 'purchase_date', 'searchable' => true, 'sortable' => true, @@ -158,6 +162,13 @@ class LicensePresenter extends Presenter 'sortable' => true, 'visible' => false, 'title' => trans('general.order_number'), + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', ], [ 'field' => 'created_at', 'searchable' => false, @@ -219,16 +230,7 @@ class LicensePresenter extends Presenter 'switchable' => true, 'title' => trans('general.id'), 'visible' => false, - ], - [ - 'field' => 'name', - 'searchable' => false, - 'sortable' => false, - 'sorter' => 'numericOnly', - 'switchable' => true, - 'title' => trans('admin/licenses/general.seat'), - 'visible' => true, - ], [ + ],[ 'field' => 'assigned_user', 'searchable' => false, 'sortable' => false, @@ -269,6 +271,14 @@ class LicensePresenter extends Presenter 'visible' => true, 'formatter' => 'locationsLinkObjFormatter', ], + [ + 'field' => 'updated_at', + 'searchable' => false, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.updated_at'), + 'formatter' => 'dateDisplayFormatter', + ], [ 'field' => 'notes', 'searchable' => false, diff --git a/app/Presenters/LocationPresenter.php b/app/Presenters/LocationPresenter.php index 56d710ac96..52076e06c1 100644 --- a/app/Presenters/LocationPresenter.php +++ b/app/Presenters/LocationPresenter.php @@ -18,8 +18,7 @@ class LocationPresenter extends Presenter 'field' => 'bulk_selectable', 'checkbox' => true, 'formatter' => 'checkboxEnabledFormatter', - ], - [ + ], [ 'field' => 'id', 'searchable' => false, 'sortable' => true, @@ -27,6 +26,15 @@ class LocationPresenter extends Presenter 'title' => trans('general.id'), 'visible' => false, ], + [ + 'field' => 'company', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.company'), + 'visible' => false, + 'formatter' => 'companiesLinkObjFormatter' + ], [ 'field' => 'name', 'searchable' => true, @@ -35,8 +43,7 @@ class LocationPresenter extends Presenter 'title' => trans('admin/locations/table.name'), 'visible' => true, 'formatter' => 'locationsLinkFormatter', - ], - [ + ], [ 'field' => 'image', 'searchable' => false, 'sortable' => true, @@ -44,8 +51,7 @@ class LocationPresenter extends Presenter 'title' => trans('general.image'), 'visible' => true, 'formatter' => 'imageFormatter', - ], - [ + ], [ 'field' => 'parent', 'searchable' => false, 'sortable' => true, @@ -53,100 +59,111 @@ class LocationPresenter extends Presenter 'title' => trans('admin/locations/table.parent'), 'visible' => true, 'formatter' => 'locationsLinkObjFormatter', - ], - - [ + ], [ 'field' => 'assets_count', 'searchable' => false, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/message.current_location'), 'visible' => true, - ], - - [ + ], [ 'field' => 'rtd_assets_count', 'searchable' => false, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/hardware/form.default_location'), + 'titleTooltip' => trans('admin/hardware/form.default_location'), + 'tooltip' => 'true', 'visible' => false, - ], - - [ + 'class' => 'css-house-flag', + ], [ 'field' => 'assigned_assets_count', 'searchable' => false, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/message.assigned_assets'), + 'titleTooltip' => trans('admin/locations/message.assigned_assets'), 'visible' => true, - ], - - [ + 'class' => 'css-house-laptop', + ], [ + 'field' => 'accessories_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.accessories'), + 'titleTooltip' => trans('general.accessories'), + 'visible' => true, + 'class' => 'css-accessory', + ], [ + 'field' => 'assigned_accessories_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.accessories_assigned'), + 'titleTooltip' => trans('general.accessories_assigned'), + 'visible' => true, + 'class' => 'css-accessory-alt', + ], [ 'field' => 'users_count', 'searchable' => false, 'sortable' => true, 'switchable' => true, 'title' => trans('general.people'), + 'titleTooltip' => trans('general.people'), 'visible' => true, - ], - [ + 'class' => 'css-house-user', + ], [ 'field' => 'currency', 'searchable' => true, 'sortable' => true, 'switchable' => true, - 'title' => trans('general.currency'), + 'title' => trans('general.currency_text'), + 'titleTooltip' => trans('general.currency_text'), 'visible' => true, - ], - [ + 'class' => 'css-currency', + ], [ 'field' => 'address', 'searchable' => true, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/table.address'), 'visible' => true, - ], - [ + ], [ 'field' => 'address2', 'searchable' => true, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/table.address2'), 'visible' => false, - ], - [ + ], [ 'field' => 'city', 'searchable' => true, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/table.city'), 'visible' => true, - ], - [ + ], [ 'field' => 'state', 'searchable' => true, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/table.state'), 'visible' => true, - ], - [ + ], [ 'field' => 'zip', 'searchable' => true, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/table.zip'), 'visible' => false, - ], - [ + ], [ 'field' => 'country', 'searchable' => true, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/table.country'), 'visible' => false, - ], - [ + ], [ 'field' => 'phone', 'searchable' => true, 'sortable' => true, @@ -154,8 +171,7 @@ class LocationPresenter extends Presenter 'title' => trans('admin/users/table.phone'), 'visible' => false, 'formatter' => 'phoneFormatter', - ], - [ + ], [ 'field' => 'fax', 'searchable' => true, 'sortable' => true, @@ -163,16 +179,14 @@ class LocationPresenter extends Presenter 'title' => trans('admin/suppliers/table.fax'), 'visible' => false, 'formatter' => 'phoneFormatter', - ], - [ + ], [ 'field' => 'ldap_ou', 'searchable' => true, 'sortable' => true, 'switchable' => true, 'title' => trans('admin/locations/table.ldap_ou'), 'visible' => false, - ], - [ + ], [ 'field' => 'manager', 'searchable' => false, 'sortable' => true, @@ -180,9 +194,13 @@ class LocationPresenter extends Presenter 'title' => trans('admin/users/table.manager'), 'visible' => false, 'formatter' => 'usersLinkObjFormatter', - ], - - [ + ], [ + 'field' => 'notes', + 'searchable' => true, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.notes'), + ], [ 'field' => 'created_at', 'searchable' => true, 'sortable' => true, @@ -191,8 +209,15 @@ class LocationPresenter extends Presenter 'visible' => false, 'formatter' => 'dateDisplayFormatter', ], - [ + 'field' => 'created_by', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ],[ 'field' => 'actions', 'searchable' => false, 'sortable' => false, @@ -206,6 +231,73 @@ class LocationPresenter extends Presenter return json_encode($layout); } + public static function assignedAccessoriesDataTableLayout() + { + $layout = [ + [ + 'field' => 'id', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + ], + [ + 'field' => 'accessory', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.accessory'), + 'visible' => true, + 'formatter' => 'accessoriesLinkObjFormatter', + ], + [ + 'field' => 'image', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.image'), + 'visible' => true, + 'formatter' => 'imageFormatter', + ], + [ + 'field' => 'note', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('general.notes'), + 'visible' => true, + ], + [ + 'field' => 'created_at', + 'searchable' => false, + 'sortable' => false, + 'switchable' => true, + 'title' => trans('admin/hardware/table.checkout_date'), + 'visible' => true, + 'formatter' => 'dateDisplayFormatter', + ], + [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => false, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], + [ + 'field' => 'available_actions', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('table.actions'), + 'formatter' => 'accessoriesInOutFormatter', + ], + ]; + + return json_encode($layout); + } + /** * Link to this locations name * @return string @@ -235,7 +327,7 @@ class LocationPresenter extends Presenter public function glyph() { - return ''; + return ''; } public function fullName() diff --git a/app/Presenters/ManufacturerPresenter.php b/app/Presenters/ManufacturerPresenter.php index 3e36cbcde0..dfefec2998 100644 --- a/app/Presenters/ManufacturerPresenter.php +++ b/app/Presenters/ManufacturerPresenter.php @@ -94,38 +94,52 @@ class ManufacturerPresenter extends Presenter 'searchable' => false, 'sortable' => true, 'switchable' => true, - 'title' => ' ' - .'', + 'title' => trans('general.assets'), 'visible' => true, + 'class' => 'css-barcode', ], [ 'field' => 'licenses_count', 'searchable' => false, 'sortable' => true, 'switchable' => true, - 'title' => ' ' - .'', + 'title' => trans('general.licenses'), 'visible' => true, + 'class' => 'css-license', ], [ 'field' => 'consumables_count', 'searchable' => false, 'sortable' => true, 'switchable' => true, - 'title' => ' ' - .'', + 'title' => trans('general.consumables'), 'visible' => true, + 'class' => 'css-consumable', ], [ 'field' => 'accessories_count', 'searchable' => false, 'sortable' => true, 'switchable' => true, - 'title' => ' ' - .'', + 'title' => trans('general.accessories'), 'visible' => true, - ], - [ + 'class' => 'css-accessory', + ], [ + 'field' => 'components_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.components'), + 'visible' => true, + 'class' => 'css-component', + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ 'field' => 'created_at', 'searchable' => true, 'sortable' => true, @@ -133,9 +147,15 @@ class ManufacturerPresenter extends Presenter 'title' => trans('general.created_at'), 'visible' => false, 'formatter' => 'dateDisplayFormatter', - ], - - [ + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ 'field' => 'actions', 'searchable' => false, 'sortable' => false, diff --git a/app/Presenters/PredefinedKitPresenter.php b/app/Presenters/PredefinedKitPresenter.php index b234653adf..7ce7d8c23d 100644 --- a/app/Presenters/PredefinedKitPresenter.php +++ b/app/Presenters/PredefinedKitPresenter.php @@ -27,6 +27,29 @@ class PredefinedKitPresenter extends Presenter 'sortable' => true, 'title' => trans('general.name'), 'formatter' => 'kitsLinkFormatter', + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', ], ]; diff --git a/app/Presenters/StatusLabelPresenter.php b/app/Presenters/StatusLabelPresenter.php new file mode 100644 index 0000000000..5bc4bd8831 --- /dev/null +++ b/app/Presenters/StatusLabelPresenter.php @@ -0,0 +1,115 @@ + 'id', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + ], [ + 'field' => 'name', + 'searchable' => true, + 'sortable' => true, + 'switchable' => false, + 'title' => trans('general.name'), + 'visible' => true, + 'formatter' => 'statuslabelsLinkFormatter', + ],[ + 'field' => 'type', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('admin/statuslabels/table.status_type'), + 'visible' => true, + 'formatter' => 'statusLabelTypeFormatter', + ], [ + 'field' => 'assets_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => false, + 'title' => trans('general.assets'), + 'visible' => true, + ], [ + 'field' => 'color', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/statuslabels/table.color'), + 'visible' => true, + 'formatter' => 'colorSqFormatter', + ], [ + 'field' => 'show_in_nav', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/statuslabels/table.show_in_nav'), + 'visible' => true, + 'formatter' => 'trueFalseFormatter', + ], [ + 'field' => 'default_label', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/statuslabels/table.default_label'), + 'visible' => true, + 'formatter' => 'trueFalseFormatter', + ],[ + 'field' => 'notes', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.notes'), + 'visible' => false, + ], [ + 'field' => 'created_by', + 'searchable' => false, + 'sortable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'actions', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('table.actions'), + 'formatter' => 'statuslabelsActionsFormatter', + ], + ]; + + return json_encode($layout); + } + + +} diff --git a/app/Presenters/SupplierPresenter.php b/app/Presenters/SupplierPresenter.php new file mode 100644 index 0000000000..20957cc648 --- /dev/null +++ b/app/Presenters/SupplierPresenter.php @@ -0,0 +1,226 @@ + 'id', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.id'), + 'visible' => false, + ], + [ + 'field' => 'name', + 'searchable' => true, + 'sortable' => true, + 'switchable' => false, + 'title' => trans('general.name'), + 'visible' => true, + 'formatter' => 'suppliersLinkFormatter', + ], [ + 'field' => 'image', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.image'), + 'visible' => true, + 'formatter' => 'imageFormatter', + ], + [ + 'field' => 'assets_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.assets'), + 'titleTooltip' => trans('general.assets'), + 'visible' => true, + 'class' => 'css-barcode', + ], [ + 'field' => 'accessories_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.accessories'), + 'titleTooltip' => trans('general.accessories'), + 'visible' => true, + 'class' => 'css-accessory', + ], + [ + 'field' => 'licenses_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.licenses'), + 'titleTooltip' => trans('general.licenses'), + 'visible' => true, + 'class' => 'css-license', + ], [ + 'field' => 'components_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.components'), + 'titleTooltip' => trans('general.components'), + 'visible' => true, + 'class' => 'css-component', + ], [ + 'field' => 'consumables_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.consumables'), + 'titleTooltip' => trans('general.consumables'), + 'visible' => true, + 'class' => 'css-consumable', + ], [ + 'field' => 'url', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.url'), + 'visible' => true, + 'formatter' => 'externalLinkFormatter', + ], [ + 'field' => 'address', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.address'), + 'visible' => true, + ], [ + 'field' => 'address2', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.address2'), + 'visible' => false, + ], [ + 'field' => 'city', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.city'), + 'visible' => true, + ], [ + 'field' => 'state', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.state'), + 'visible' => true, + ], [ + 'field' => 'zip', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.zip'), + 'visible' => false, + ], [ + 'field' => 'country', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/locations/table.country'), + 'visible' => false, + ], [ + 'field' => 'phone', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/users/table.phone'), + 'visible' => false, + 'formatter' => 'phoneFormatter', + ], [ + 'field' => 'fax', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('admin/suppliers/table.fax'), + 'visible' => false, + 'formatter' => 'phoneFormatter', + ], [ + 'field' => 'notes', + 'searchable' => true, + 'sortable' => true, + 'visible' => false, + 'title' => trans('general.notes'), + ], [ + 'field' => 'created_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'created_by', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.created_by'), + 'visible' => false, + 'formatter' => 'usersLinkObjFormatter', + ], [ + 'field' => 'actions', + 'searchable' => false, + 'sortable' => false, + 'switchable' => false, + 'title' => trans('table.actions'), + 'visible' => true, + 'formatter' => 'suppliersActionsFormatter', + ], + ]; + + return json_encode($layout); + } + + + /** + * Link to this supplier name + * @return string + */ + public function nameUrl() + { + return (string) link_to_route('suppliers.show', $this->name, $this->id); + } + + /** + * Getter for Polymorphism. + * @return mixed + */ + public function name() + { + return $this->model->name; + } + + /** + * Url to view this item. + * @return string + */ + public function viewUrl() + { + return route('suppliers.show', $this->id); + } + + public function glyph() + { + return ''; + } + + public function fullName() + { + return $this->name; + } +} diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index 4b26f95f40..dd64212c34 100644 --- a/app/Presenters/UserPresenter.php +++ b/app/Presenters/UserPresenter.php @@ -24,6 +24,7 @@ class UserPresenter extends Presenter [ 'field' => 'checkbox', 'checkbox' => true, + 'titleTooltip' => trans('general.select_all_none'), ], [ 'field' => 'id', @@ -188,6 +189,14 @@ class UserPresenter extends Presenter 'title' => trans('general.employee_number'), 'visible' => false, ], + [ + 'field' => 'locale', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.language'), + 'visible' => false, + ], [ 'field' => 'department', 'searchable' => true, @@ -232,6 +241,7 @@ class UserPresenter extends Presenter 'class' => 'css-barcode', 'title' => trans('general.assets'), 'visible' => true, + 'formatter' => 'linkNumberToUserAssetsFormatter', ], [ 'field' => 'licenses_count', @@ -241,6 +251,7 @@ class UserPresenter extends Presenter 'class' => 'css-license', 'title' => trans('general.licenses'), 'visible' => true, + 'formatter' => 'linkNumberToUserLicensesFormatter', ], [ 'field' => 'consumables_count', @@ -250,6 +261,7 @@ class UserPresenter extends Presenter 'class' => 'css-consumable', 'title' => trans('general.consumables'), 'visible' => true, + 'formatter' => 'linkNumberToUserConsumablesFormatter', ], [ 'field' => 'accessories_count', @@ -259,6 +271,7 @@ class UserPresenter extends Presenter 'class' => 'css-accessory', 'title' => trans('general.accessories'), 'visible' => true, + 'formatter' => 'linkNumberToUserAccessoriesFormatter', ], [ 'field' => 'manages_users_count', @@ -268,6 +281,7 @@ class UserPresenter extends Presenter 'class' => 'css-users', 'title' => trans('admin/users/table.managed_users'), 'visible' => true, + 'formatter' => 'linkNumberToUserManagedUsersFormatter', ], [ 'field' => 'manages_locations_count', @@ -277,6 +291,7 @@ class UserPresenter extends Presenter 'class' => 'css-location', 'title' => trans('admin/users/table.managed_locations'), 'visible' => true, + 'formatter' => 'linkNumberToUserManagedLocationsFormatter', ], [ 'field' => 'notes', @@ -356,6 +371,14 @@ class UserPresenter extends Presenter 'title' => trans('general.created_at'), 'visible' => false, 'formatter' => 'dateDisplayFormatter', + ], [ + 'field' => 'updated_at', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.updated_at'), + 'visible' => false, + 'formatter' => 'dateDisplayFormatter', ], [ 'field' => 'start_date', @@ -435,6 +458,8 @@ class UserPresenter extends Presenter */ public function gravatar() { + + // User's specific avatar if ($this->avatar) { // Check if it's a google avatar or some external avatar @@ -446,21 +471,31 @@ class UserPresenter extends Presenter return Storage::disk('public')->url('avatars/'.e($this->avatar)); } - if (Setting::getSettings()->load_remote == '1') { - if ($this->model->gravatar != '') { + // If the default is system default + if (Setting::getSettings()->default_avatar == 'default.png') { + return Storage::disk('public')->url('default.png'); + } + + // If there is a custom default avatar + if (Setting::getSettings()->default_avatar != '') { + return Storage::disk('public')->url('avatars/'.e(Setting::getSettings()->default_avatar)); + } + + // If there is no default and no custom avatar, check for gravatar + if ((Setting::getSettings()->load_remote == '1') && (Setting::getSettings()->default_avatar == '')) { + + if ($this->model->gravatar != '') { $gravatar = md5(strtolower(trim($this->model->gravatar))); return '//gravatar.com/avatar/'.$gravatar; } elseif ($this->email != '') { - $gravatar = md5(strtolower(trim($this->email))); return '//gravatar.com/avatar/'.$gravatar; } } - // Set a fun, gender-neutral default icon - return config('app.url').'/img/default-sm.png'; + return false; } /** @@ -483,6 +518,6 @@ class UserPresenter extends Presenter public function glyph() { - return ''; + return ''; } } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index e17d667845..899df2ef14 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -87,11 +87,11 @@ class AuthServiceProvider extends ServiceProvider ]); $this->registerPolicies(); - //Passport::routes(); //this is no longer required in newer passport versions - Passport::tokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); - Passport::refreshTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); - Passport::personalAccessTokensExpireIn(Carbon::now()->addYears(config('passport.expiration_years'))); - Passport::withCookieSerialization(); + Passport::tokensExpireIn(Carbon::now()->addYears((int)config('passport.expiration_years'))); + Passport::refreshTokensExpireIn(Carbon::now()->addYears((int)config('passport.expiration_years'))); + Passport::personalAccessTokensExpireIn(Carbon::now()->addYears((int)config('passport.expiration_years'))); + + Passport::cookie(config('passport.cookie_name')); /** @@ -168,6 +168,15 @@ class AuthServiceProvider extends ServiceProvider } }); + // ----------------------------------------- + // Activity + // ----------------------------------------- + Gate::define('activity.view', function ($user) { + if (($user->hasAccess('reports.view')) || ($user->hasAccess('admin'))) { + return true; + } + }); + // ----------------------------------------- // Self // ----------------------------------------- @@ -230,7 +239,8 @@ class AuthServiceProvider extends ServiceProvider || $user->can('update', Accessory::class) || $user->can('create', Accessory::class) || $user->can('update', User::class) - || $user->can('create', User::class); + || $user->can('create', User::class) + || ($user->hasAccess('reports.view')); }); diff --git a/app/Providers/BladeServiceProvider.php b/app/Providers/BladeServiceProvider.php new file mode 100644 index 0000000000..024ee9d3dc --- /dev/null +++ b/app/Providers/BladeServiceProvider.php @@ -0,0 +1,25 @@ + + $trail->push(''.trans('general.dashboard').'', route('home')) + ); + + /** + * Asset Breadcrumbs + */ + + + if ((request()->is('hardware*')) && (request()->status!='')) { + Breadcrumbs::for('hardware.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.assets'), route('hardware.index')) + ->push(request()->status.' '.trans('general.assets'), route('hardware.index', ['status' => request()->status])) + ); + + } else { + Breadcrumbs::for('hardware.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.assets'), route('hardware.index')) + ); + } + + + + + Breadcrumbs::for('hardware.create', fn (Trail $trail) => + $trail->parent('hardware.index', route('hardware.index')) + ->push(trans('general.create'), route('hardware.create')) + ); + + Breadcrumbs::for('clone/hardware', fn (Trail $trail) => + $trail->parent('hardware.index', route('hardware.index')) + ->push(trans('admin/hardware/general.clone'), route('hardware.create')) + ); + + Breadcrumbs::for('hardware.show', fn (Trail $trail, Asset $asset) => + $trail->parent('hardware.index', route('hardware.index')) + ->push($asset->present()->fullName(), route('hardware.show', $asset)) + ); + + Breadcrumbs::for('hardware.edit', fn (Trail $trail, Asset $asset) => + $trail->parent('hardware.index', route('hardware.index')) + ->push($asset->present()->fullName(), route('hardware.show', $asset)) + ->push(trans('admin/hardware/general.edit')) + ); + + + /** + * Asset Model Breadcrumbs + */ + + if ((request()->is('models*')) && (request()->status=='deleted')) { + Breadcrumbs::for('models.index', fn (Trail $trail) => + $trail->parent('hardware.index', route('hardware.index')) + ->push(trans('general.asset_models'), route('models.index')) + ->push(trans('general.deleted_models'), route('models.index', ['status' => request()->status])) + ); + } else { + Breadcrumbs::for('models.index', fn (Trail $trail) => + $trail->parent('hardware.index', route('hardware.index')) + ->push(trans('general.asset_models'), route('models.index')) + ); + } + + + Breadcrumbs::for('models.create', fn (Trail $trail) => + $trail->parent('models.index', route('models.index')) + ->push(trans('general.create'), route('models.create')) + ); + + Breadcrumbs::for('models.show', fn (Trail $trail, AssetModel $model) => + $trail->parent('models.index', route('models.index')) + ->push($model->name, route('models.show', $model)) + ); + + Breadcrumbs::for('models.edit', fn (Trail $trail, AssetModel $model) => + $trail->parent('models.index', route('models.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $model->name]), route('models.edit', $model)) + ); + + + /** + * Accessories Breadcrumbs + */ + Breadcrumbs::for('accessories.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.accessories'), route('accessories.index')) + ); + + Breadcrumbs::for('accessories.create', fn (Trail $trail) => + $trail->parent('accessories.index', route('accessories.index')) + ->push(trans('general.create'), route('accessories.create')) + ); + + Breadcrumbs::for('accessories.show', fn (Trail $trail, Accessory $accessory) => + $trail->parent('accessories.index', route('accessories.index')) + ->push($accessory->name, route('accessories.show', $accessory)) + ); + + Breadcrumbs::for('accessories.edit', fn (Trail $trail, Accessory $accessory) => + $trail->parent('accessories.index', route('accessories.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $accessory->name]), route('accessories.edit', $accessory)) + ); + + + /** + * Categories Breadcrumbs + */ + Breadcrumbs::for('categories.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.categories'), route('categories.index')) + ); + + Breadcrumbs::for('categories.create', fn (Trail $trail) => + $trail->parent('categories.index', route('categories.index')) + ->push(trans('general.create'), route('categories.create')) + ); + + Breadcrumbs::for('categories.show', fn (Trail $trail, Category $category) => + $trail->parent('categories.index', route('categories.index')) + ->push($category->name, route('categories.show', $category)) + ); + + Breadcrumbs::for('categories.edit', fn (Trail $trail, Category $category) => + $trail->parent('categories.index', route('categories.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $category->name]), route('categories.edit', $category)) + ); + + + + /** + * Company Breadcrumbs + */ + Breadcrumbs::for('companies.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.companies'), route('companies.index')) + ); + + Breadcrumbs::for('companies.create', fn (Trail $trail) => + $trail->parent('companies.index', route('companies.index')) + ->push(trans('general.create'), route('companies.create')) + ); + + Breadcrumbs::for('companies.show', fn (Trail $trail, Company $company) => + $trail->parent('companies.index', route('companies.index')) + ->push($company->name, route('companies.show', $company)) + ); + + Breadcrumbs::for('companies.edit', fn (Trail $trail, Company $company) => + $trail->parent('companies.index', route('companies.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $company->name]), route('companies.edit', $company)) + ); + + + /** + * Components Breadcrumbs + */ + Breadcrumbs::for('components.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.components'), route('components.index')) + ); + + Breadcrumbs::for('components.create', fn (Trail $trail) => + $trail->parent('components.index', route('components.index')) + ->push(trans('general.create'), route('components.create')) + ); + + Breadcrumbs::for('components.show', fn (Trail $trail, Component $component) => + $trail->parent('components.index', route('components.index')) + ->push($component->name, route('components.show', $component)) + ); + + Breadcrumbs::for('components.edit', fn (Trail $trail, Component $component) => + $trail->parent('components.index', route('components.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $component->name]), route('components.edit', $component)) + ); + + + /** + * Consumables Breadcrumbs + */ + Breadcrumbs::for('consumables.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.consumables'), route('consumables.index')) + ); + + Breadcrumbs::for('consumables.create', fn (Trail $trail) => + $trail->parent('consumables.index', route('consumables.index')) + ->push(trans('general.create'), route('consumables.create')) + ); + + Breadcrumbs::for('consumables.show', fn (Trail $trail, Consumable $consumable) => + $trail->parent('consumables.index', route('consumables.index')) + ->push($consumable->name, route('consumables.show', $consumable)) + ); + + Breadcrumbs::for('consumables.edit', fn (Trail $trail, Consumable $consumable) => + $trail->parent('consumables.index', route('consumables.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $consumable->name]), route('consumables.edit', $consumable)) + ); + + /** + * Custom fields Breadcrumbs + */ + Breadcrumbs::for('fields.index', fn (Trail $trail) => + $trail->parent('models.index', route('models.index')) + ->push(trans('admin/custom_fields/general.custom_fields'), route('fields.index')) + ); + + Breadcrumbs::for('fields.create', fn (Trail $trail) => + $trail->parent('fields.index', route('fields.index')) + ->push(trans('general.create'), route('fields.create')) + ); + + Breadcrumbs::for('fields.edit', fn (Trail $trail, CustomField $field) => + $trail->parent('fields.index', route('fields.index')) + ->push($field->name, route('fields.edit', $field)) + ); + + /** + * Custom fieldsets Breadcrumbs + */ + + Breadcrumbs::for('fieldsets.create', fn (Trail $trail) => + $trail->parent('fields.index', route('fields.index')) + ->push(trans('general.create'), route('fieldsets.create')) + ); + + Breadcrumbs::for('fieldsets.show', fn (Trail $trail, CustomFieldset $fieldset) => + $trail->parent('fields.index', route('fields.index')) + ->push($fieldset->name, route('fields.index')) + ); + + Breadcrumbs::for('fieldsets.edit', fn (Trail $trail, CustomFieldset $fieldset) => + $trail->parent('fields.index', route('fields.index')) + ->push($fieldset->name, route('fieldsets.edit', $fieldset)) + ); + + /** + * Department Breadcrumbs + */ + Breadcrumbs::for('departments.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.departments'), route('departments.index')) + ); + + Breadcrumbs::for('departments.create', fn (Trail $trail) => + $trail->parent('departments.index', route('departments.index')) + ->push(trans('general.create'), route('departments.create')) + ); + + Breadcrumbs::for('departments.show', fn (Trail $trail, Department $department) => + $trail->parent('departments.index', route('departments.index')) + ->push($department->name, route('home')) + ); + + Breadcrumbs::for('departments.edit', fn (Trail $trail, Department $department) => + $trail->parent('departments.index', route('departments.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $department->name]), route('departments.edit', $department)) + ); + + + /** + * Department Breadcrumbs + */ + Breadcrumbs::for('depreciations.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.depreciations'), route('depreciations.index')) + ); + + Breadcrumbs::for('depreciations.create', fn (Trail $trail) => + $trail->parent('depreciations.index', route('depreciations.index')) + ->push(trans('general.create'), route('depreciations.create')) + ); + + Breadcrumbs::for('depreciations.show', fn (Trail $trail, Depreciation $depreciation) => + $trail->parent('depreciations.index', route('depreciations.index')) + ->push($depreciation->name, route('depreciations.show', $depreciation)) + ); + + Breadcrumbs::for('depreciations.edit', fn (Trail $trail, Depreciation $depreciation) => + $trail->parent('depreciations.index', route('depreciations.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $depreciation->name]), route('depreciations.edit', $depreciation)) + ); + + /** + * Groups Breadcrumbs + */ + Breadcrumbs::for('groups.index', fn (Trail $trail) => + $trail->parent('settings.index', route('settings.index')) + ->push(trans('general.groups'), route('groups.index')) + ); + + Breadcrumbs::for('groups.create', fn (Trail $trail) => + $trail->parent('groups.index', route('groups.index')) + ->push(trans('general.create'), route('groups.create')) + ); + + Breadcrumbs::for('groups.show', fn (Trail $trail, Group $group) => + $trail->parent('groups.index', route('groups.index')) + ->push($group->name, route('groups.show', $group)) + ); + + Breadcrumbs::for('groups.edit', fn (Trail $trail, Group $group) => + $trail->parent('groups.index', route('groups.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $group->name]), route('groups.edit', $group)) + ); + + + /** + * Licenses Breadcrumbs + */ + Breadcrumbs::for('licenses.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.licenses'), route('licenses.index')) + ); + + Breadcrumbs::for('licenses.create', fn (Trail $trail) => + $trail->parent('licenses.index', route('licenses.index')) + ->push(trans('general.create'), route('licenses.create')) + ); + + Breadcrumbs::for('licenses.show', fn (Trail $trail, License $license) => + $trail->parent('licenses.index', route('licenses.index')) + ->push($license->name, route('licenses.show', $license)) + ); + + Breadcrumbs::for('licenses.edit', fn (Trail $trail, License $license) => + $trail->parent('licenses.index', route('licenses.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $license->name]), route('licenses.edit', $license)) + ); + + + /** + * Locations Breadcrumbs + */ + Breadcrumbs::for('locations.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.locations'), route('locations.index')) + ); + + Breadcrumbs::for('locations.create', fn (Trail $trail) => + $trail->parent('locations.index', route('locations.index')) + ->push(trans('general.create'), route('locations.create')) + ); + + Breadcrumbs::for('clone/location', fn (Trail $trail) => + $trail->parent('locations.index', route('locations.index')) + ->push(trans('admin/locations/table.clone'), route('locations.create')) + ); + + + Breadcrumbs::for('locations.show', fn (Trail $trail, Location $location) => + $trail->parent('locations.index', route('locations.index')) + ->push($location->name, route('locations.show', $location)) + ); + + Breadcrumbs::for('locations.edit', fn (Trail $trail, Location $location) => + $trail->parent('locations.index', route('locations.index')) + ->push($location->name, route('locations.show', $location)) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $location->name]), route('locations.edit', $location)) + ); + + /** + * Maintenances Breadcrumbs + */ + Breadcrumbs::for('maintenances.index', fn (Trail $trail) => + $trail->parent('hardware.index', route('hardware.index')) + ->push(trans('general.maintenances'), route('maintenances.index')) + ); + + Breadcrumbs::for('maintenances.create', fn (Trail $trail) => + $trail->parent('maintenances.index', route('maintenances.index')) + ->push(trans('general.create'), route('maintenances.create')) + ); + + Breadcrumbs::for('maintenances.show', fn (Trail $trail, AssetMaintenance $maintenance) => + $trail->parent('maintenances.index', route('maintenances.index')) + ->push($maintenance->title, route('maintenances.show', $maintenance)) + ); + + Breadcrumbs::for('maintenances.edit', fn (Trail $trail, AssetMaintenance $maintenance) => + $trail->parent('maintenances.index', route('maintenances.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $maintenance->title]), route('maintenances.edit', $maintenance)) + ); + + + /** + * Manufacturers Breadcrumbs + */ + Breadcrumbs::for('manufacturers.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.manufacturers'), route('manufacturers.index')) + ); + + Breadcrumbs::for('manufacturers.create', fn (Trail $trail) => + $trail->parent('manufacturers.index', route('manufacturers.index')) + ->push(trans('general.create'), route('manufacturers.create')) + ); + + Breadcrumbs::for('manufacturers.show', fn (Trail $trail, Manufacturer $manufacturer) => + $trail->parent('manufacturers.index', route('manufacturers.index')) + ->push($manufacturer->name, route('home')) + ); + + Breadcrumbs::for('manufacturers.edit', fn (Trail $trail, Manufacturer $manufacturer) => + $trail->parent('manufacturers.index', route('manufacturers.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $manufacturer->name]), route('manufacturers.edit', $manufacturer)) + ); + + + /** + * Predefined Kits Breadcrumbs + */ + Breadcrumbs::for('kits.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.kits'), route('kits.index')) + ); + + Breadcrumbs::for('kits.create', fn (Trail $trail) => + $trail->parent('kits.index', route('kits.index')) + ->push(trans('general.create'), route('kits.create')) + ); + + Breadcrumbs::for('kits.show', fn (Trail $trail, PredefinedKit $kit) => + $trail->parent('kits.index', route('kits.index')) + ->push($kit->name, route('kits.show', $kit)) + ); + + Breadcrumbs::for('kits.edit', fn (Trail $trail, PredefinedKit $kit) => + $trail->parent('kits.index', route('kits.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $kit->name]), route('kits.edit', $kit)) + ); + + + /** + * Status Labels Breadcrumbs + */ + Breadcrumbs::for('statuslabels.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.status_labels'), route('statuslabels.index')) + ); + + Breadcrumbs::for('statuslabels.create', fn (Trail $trail) => + $trail->parent('statuslabels.index', route('statuslabels.index')) + ->push(trans('general.create'), route('statuslabels.create')) + ); + + Breadcrumbs::for('statuslabels.show', fn (Trail $trail, Statuslabel $statuslabel) => + $trail->parent('statuslabels.index', route('statuslabels.index')) + ->push($statuslabel->name, route('statuslabels.show', $statuslabel)) + ); + + Breadcrumbs::for('statuslabels.edit', fn (Trail $trail, Statuslabel $statuslabel) => + $trail->parent('statuslabels.index', route('statuslabels.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $statuslabel->name]), route('statuslabels.edit', $statuslabel)) + ); + + + /** + * Settings Breadcrumbs + */ + Breadcrumbs::for('settings.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.admin'), route('settings.index')) + ); + + + + /** + * Suppliers Breadcrumbs + */ + Breadcrumbs::for('suppliers.index', fn (Trail $trail) => + $trail->parent('home', route('home')) + ->push(trans('general.suppliers'), route('suppliers.index')) + ); + + Breadcrumbs::for('suppliers.create', fn (Trail $trail) => + $trail->parent('suppliers.index', route('suppliers.index')) + ->push(trans('general.create'), route('suppliers.create')) + ); + + Breadcrumbs::for('suppliers.show', fn (Trail $trail, Supplier $supplier) => + $trail->parent('suppliers.index', route('suppliers.index')) + ->push($supplier->name, route('home')) + ); + + Breadcrumbs::for('suppliers.edit', fn (Trail $trail, Supplier $supplier) => + $trail->parent('suppliers.index', route('suppliers.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $supplier->name]), route('suppliers.edit', $supplier)) + ); + + + + /** + * Users Breadcrumbs + */ + if ((request()->is('users*')) && (request()->status=='deleted')) { + Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home')) + ->push(trans('general.users'), route('users.index')) + ->push(trans('general.deleted_users'), route('users.index')) + ); + } else { + Breadcrumbs::for('users.index', fn(Trail $trail) => $trail->parent('home', route('home')) + ->push(trans('general.users'), route('users.index')) + ); + } + + Breadcrumbs::for('users.create', fn (Trail $trail) => + $trail->parent('users.index', route('users.index')) + ->push(trans('general.create'), route('users.create')) + ); + + Breadcrumbs::for('users.clone.show', fn (Trail $trail) => + $trail->parent('users.index', route('users.index')) + ->push(trans('admin/users/general.clone'), route('users.create')) + ); + + + + Breadcrumbs::for('users.show', fn (Trail $trail, User $user) => + $trail->parent('users.index', route('users.index')) + ->push($user->getFullNameAttribute() ?? 'Missing Username!', route('users.show', $user)) + ); + + Breadcrumbs::for('users.edit', fn (Trail $trail, User $user) => + $trail->parent('users.index', route('users.index')) + ->push(trans('general.breadcrumb_button_actions.edit_item', ['name' => $user->name]), route('users.edit', $user)) + ); + + + + } + + +} \ No newline at end of file diff --git a/app/Providers/LivewireServiceProvider.php b/app/Providers/LivewireServiceProvider.php new file mode 100644 index 0000000000..9fd0fd561f --- /dev/null +++ b/app/Providers/LivewireServiceProvider.php @@ -0,0 +1,32 @@ +by(optional($request->user())->id ?: $request->ip()); + return Limit::perMinute(config('app.api_throttle_per_minute'))->by(optional($request->user())->id ?: $request->ip()) + ->response(function ($request, $headers) { + return response()->json([ + 'status' => 'error', + 'messages' => 'Too many requests. Try again in '.$headers['Retry-After'].' seconds.', + 'status_code' => 429, + 'retryAfter' => $headers['Retry-After'] ?? 60, + ], 429, $headers); + }); }); // Rate limiter for forgotten password requests diff --git a/app/Providers/SamlServiceProvider.php b/app/Providers/SamlServiceProvider.php index 18b93cf56f..c9e50dafe2 100644 --- a/app/Providers/SamlServiceProvider.php +++ b/app/Providers/SamlServiceProvider.php @@ -49,10 +49,6 @@ class SamlServiceProvider extends ServiceProvider 'uses' => 'Auth\SamlController@login', ] ); - Route::group(['prefix' => 'admin', 'middleware' => ['web', 'auth', 'authorize:superuser']], function () { - Route::get('saml', ['as' => 'settings.saml.index', 'uses' => 'SettingsController@getSamlSettings']); - Route::post('saml', ['as' => 'settings.saml.save', 'uses' => 'SettingsController@postSamlSettings']); - }); }); } diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 1c89dc2b8c..9a533b9fb3 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -65,6 +65,10 @@ class SettingsServiceProvider extends ServiceProvider return 'assets/'; }); + \App::singleton('audits_upload_path', function () { + return 'audits/'; + }); + \App::singleton('accessories_upload_path', function () { return 'public/uploads/accessories/'; }); diff --git a/app/Providers/ValidationServiceProvider.php b/app/Providers/ValidationServiceProvider.php index 690af759c7..a08a035fbb 100644 --- a/app/Providers/ValidationServiceProvider.php +++ b/app/Providers/ValidationServiceProvider.php @@ -4,13 +4,11 @@ namespace App\Providers; use App\Models\CustomField; use App\Models\Department; +use App\Models\Location; use App\Models\Setting; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Crypt; -use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; -use Illuminate\Validation\Rule; -use Validator; +use Illuminate\Support\Facades\Validator; /** * This service provider handles a few custom validation rules. @@ -34,6 +32,7 @@ class ValidationServiceProvider extends ServiceProvider Validator::extend('email_array', function ($attribute, $value, $parameters, $validator) { $value = str_replace(' ', '', $value); $array = explode(',', $value); + $email_to_validate = []; foreach ($array as $email) { //loop over values $email_to_validate['alert_email'][] = $email; @@ -41,7 +40,7 @@ class ValidationServiceProvider extends ServiceProvider $rules = ['alert_email.*'=>'email']; $messages = [ - 'alert_email.*'=>trans('validation.email_array'), + 'alert_email.*' => trans('validation.custom.email_array'), ]; $validator = Validator::make($email_to_validate, $rules, $messages); @@ -66,7 +65,6 @@ class ValidationServiceProvider extends ServiceProvider * `unique_undeleted:table,fieldname` in your rules out of the box */ Validator::extend('unique_undeleted', function ($attribute, $value, $parameters, $validator) { - if (count($parameters)) { // This is a bit of a shim, but serial doesn't have any other rules around it other than that it's nullable @@ -92,18 +90,26 @@ class ValidationServiceProvider extends ServiceProvider * * $parameters[0] - the name of the first table we're looking at * $parameters[1] - the ID (this will be 0 on new creations) - * $parameters[2] - the name of the second table we're looking at + * $parameters[2] - the name of the second field we're looking at * $parameters[3] - the value that the request is passing for the second table we're * checking for uniqueness across * */ Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) { + if (count($parameters)) { + $count = DB::table($parameters[0]) - ->select('id')->where($attribute, '=', $value) - ->whereNull('deleted_at') - ->where('id', '!=', $parameters[1]) - ->where($parameters[2], $parameters[3])->count(); + ->select('id') + ->where($attribute, '=', $value) + ->where('id', '!=', $parameters[1]); + + if ($parameters[3]!='') { + $count = $count->where($parameters[2], $parameters[3]); + } + + $count = $count->whereNull('deleted_at') + ->count(); return $count < 1; } @@ -348,6 +354,20 @@ class ValidationServiceProvider extends ServiceProvider return in_array($value, $options); }); + + // Validates that the company of the validated object matches the company of the location in case of scoped locations + Validator::extend('fmcs_location', function ($attribute, $value, $parameters, $validator){ + $settings = Setting::getSettings(); + if ($settings->full_multiple_companies_support == '1' && $settings->scope_locations_fmcs == '1') { + $company_id = array_get($validator->getData(), 'company_id'); + $location = Location::find($value); + + if ($company_id != $location->company_id) { + return false; + } + } + return true; + }); } /** diff --git a/app/Rules/AlphaEncrypted.php b/app/Rules/AlphaEncrypted.php new file mode 100644 index 0000000000..f4ed1d6c32 --- /dev/null +++ b/app/Rules/AlphaEncrypted.php @@ -0,0 +1,29 @@ + $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/AssetCannotBeCheckedOutToNondeployableStatus.php b/app/Rules/AssetCannotBeCheckedOutToNondeployableStatus.php new file mode 100644 index 0000000000..c2c451b82b --- /dev/null +++ b/app/Rules/AssetCannotBeCheckedOutToNondeployableStatus.php @@ -0,0 +1,51 @@ + + */ + protected $data = []; + + + /** + * Set the data under validation. + * + * @param array $data + */ + public function setData(array $data): static + { + $this->data = $data; + return $this; + } + + /** + * Run the validation rule. + * + * @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail + */ + public function validate(string $attribute, mixed $value, Closure $fail): void + { + // Check to see if any of the assign-ish fields are set + if ((isset($this->data['assigned_to'])) || (isset($this->data['assigned_user'])) || (isset($this->data['assigned_location'])) || (isset($this->data['assigned_asset'])) || (isset($this->data['assigned_type']))) { + + if (($value) && ($label = Statuslabel::find($value)) && ($label->getStatuslabelType()!='deployable')) { + $fail(trans('admin/hardware/form.asset_not_deployable')); + } + + } + + + } +} diff --git a/app/Rules/NumericEncrypted.php b/app/Rules/NumericEncrypted.php new file mode 100644 index 0000000000..f3cb3ba76e --- /dev/null +++ b/app/Rules/NumericEncrypted.php @@ -0,0 +1,31 @@ + $attributeName])); + } + } catch (\Exception $e) { + report($e->getMessage()); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/UserCannotSwitchCompaniesIfItemsAssigned.php b/app/Rules/UserCannotSwitchCompaniesIfItemsAssigned.php new file mode 100644 index 0000000000..b31bc91d1b --- /dev/null +++ b/app/Rules/UserCannotSwitchCompaniesIfItemsAssigned.php @@ -0,0 +1,29 @@ +route('user'); + + if (($value) && ($user->allAssignedCount() > 0) && (Setting::getSettings()->full_multiple_companies_support=='1')) { + + // Check for assets with a different company_id than the selected company_id + $user_assets = $user->assets()->where('assets.company_id', '!=', $value)->count(); + if ($user_assets > 0) { + $fail(trans('admin/users/message.error.multi_company_items_assigned')); + } + } + } +} diff --git a/app/Services/PredefinedKitCheckoutService.php b/app/Services/PredefinedKitCheckoutService.php index 7ef28c9ee0..2cf4593687 100644 --- a/app/Services/PredefinedKitCheckoutService.php +++ b/app/Services/PredefinedKitCheckoutService.php @@ -54,7 +54,7 @@ class PredefinedKitCheckoutService $expected_checkin = $request->get('expected_checkin'); } - $admin = Auth::user(); + $admin = auth()->user(); $note = e($request->get('note')); @@ -157,7 +157,7 @@ class PredefinedKitCheckoutService } // licenses foreach ($license_seats_to_add as $licenseSeat) { - $licenseSeat->user_id = $admin->id; + $licenseSeat->created_by = $admin->id; $licenseSeat->assigned_to = $user->id; if ($licenseSeat->save()) { event(new CheckoutableCheckedOut($licenseSeat, $user, $admin, $note)); diff --git a/app/Services/Saml.php b/app/Services/Saml.php index 8202868013..222c2c2ea3 100644 --- a/app/Services/Saml.php +++ b/app/Services/Saml.php @@ -209,7 +209,7 @@ class Saml } } - $custom_settings = preg_split('/\r\n|\r|\n/', $setting->saml_custom_settings); + $custom_settings = preg_split('/\r\n|\r|\n/', $setting->saml_custom_settings ?? ''); if ($custom_settings) { foreach ($custom_settings as $custom_setting) { $split = explode('=', $custom_setting, 2); @@ -337,12 +337,12 @@ class Saml /** * Get a setting. * - * @author Johnson Yi - * * @param string|array|int $key * @param mixed $default * - * @return void + * @return mixed + * @author Johnson Yi + * */ public function getSetting($key, $default = null) { diff --git a/app/Services/SnipeTranslator.php b/app/Services/SnipeTranslator.php index 8a3552a343..eb7a9eb044 100644 --- a/app/Services/SnipeTranslator.php +++ b/app/Services/SnipeTranslator.php @@ -26,17 +26,19 @@ class SnipeTranslator extends Translator { public function choice($key, $number, array $replace = [], $locale = null) { $line = $this->get( - $key, $replace, $locale = $this->localeForChoice($locale) + $key, [], $locale = $this->localeForChoice($key, $locale) ); // If the given "number" is actually an array or countable we will simply count the // number of elements in an instance. This allows developers to pass an array of // items without having to count it on their end first which gives bad syntax. - if (is_array($number) || $number instanceof Countable) { + if (is_countable($number)) { $number = count($number); } - $replace['count'] = $number; + if (!isset($replace['count'])) { + $replace['count'] = $number; + } $underscored_locale = str_replace("-","_",$locale); // OUR CHANGE. return $this->makeReplacements( // BELOW - that $underscored_locale is the *ONLY* modified part diff --git a/app/View/Label.php b/app/View/Label.php index 3ec3a4099c..ee9a1ae2fd 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -7,6 +7,7 @@ use App\Models\Labels\Label as LabelModel; use App\Models\Labels\Sheet; use Illuminate\Contracts\View\View; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Traits\Macroable; use TCPDF; @@ -24,6 +25,16 @@ class Label implements View */ protected $data; + + /** + * TCPDF output destination. + * "I" - inline by default. + * See TCPDF's Output method for details. + * + * @var string + */ + private string $destination = 'I'; + public function __construct() { $this->data = new Collection(); } @@ -38,7 +49,7 @@ class Label implements View $settings = $this->data->get('settings'); $assets = $this->data->get('assets'); $offset = $this->data->get('offset'); - $template = LabelModel::find($settings->label2_template); + // If disabled, pass to legacy view if ((!$settings->label2_enable)) { @@ -49,12 +60,18 @@ class Label implements View ->with('count', $this->data->get('count')); } + $template = LabelModel::find($settings->label2_template); + + if ($template === null) { + return redirect()->route('settings.labels.index')->with('error', trans('admin/settings/message.labels.null_template')); + } + $template->validate(); $pdf = new TCPDF( $template->getOrientation(), $template->getUnit(), - [ $template->getWidth(), $template->getHeight() ] + [0 => $template->getWidth(), 1 => $template->getHeight(), 'Rotate' => $template->getRotation()] ); // Reset parameters @@ -105,9 +122,9 @@ class Label implements View } } - if ($settings->alt_barcode_enabled) { + if ($template->getSupport1DBarcode()) { - $barcode1DType = $settings->alt_barcode; + $barcode1DType = $settings->label2_1d_type; if ($barcode1DType != 'none') { $assetData->put('barcode1d', (object)[ 'type' => $barcode1DType, @@ -115,25 +132,39 @@ class Label implements View ]); } } - } - - if ($template->getSupport2DBarcode()) { + + if ($template->getSupport2DBarcode()) { $barcode2DType = $settings->label2_2d_type; - $barcode2DType = ($barcode2DType == 'default') ? - $settings->barcode_type : - $barcode2DType; - if (($barcode2DType != 'none') && (!is_null($barcode2DType))) { + if (($barcode2DType != 'none') && (!is_null($barcode2DType))) { switch ($settings->label2_2d_target) { - case 'ht_tag': $barcode2DTarget = route('ht/assetTag', $asset->asset_tag); break; + case 'ht_tag': + $barcode2DTarget = route('ht/assetTag', $asset->asset_tag); + break; + case 'plain_asset_id': + $barcode2DTarget = (string) $asset->id; + break; + case 'plain_asset_tag': + $barcode2DTarget = $asset->asset_tag; + break; + case 'plain_serial_number': + $barcode2DTarget = $asset->serial; + break; + case 'location': + $barcode2DTarget = $asset->location_id + ? route('locations.show', $asset->location_id) + : null; + break; case 'hardware_id': - default: $barcode2DTarget = route('hardware.show', ['hardware' => $asset->id]); break; + default: + $barcode2DTarget = route('hardware.show', $asset); + break; + } + $assetData->put('barcode2d', (object)[ + 'type' => $barcode2DType, + 'content' => $barcode2DTarget, + ]); } - $assetData->put('barcode2d', (object)[ - 'type' => $barcode2DType, - 'content' => $barcode2DTarget, - ]); } - } $fields = $fieldDefinitions ->map(fn($field) => $field->toArray($asset)) @@ -165,16 +196,34 @@ class Label implements View // We'll set the label to an empty string since we // injected the label into the value field above. $previous['label'] = ''; - return $previous; }); return $toAdd ? $myFields->push($toAdd) : $myFields; }, new Collection()); - $assetData->put('fields', $fields->take($template->getSupportFields())); + $emptyRowsCount = $settings->label2_empty_row_count; + if($emptyRowsCount) { + // Create empty rows + $emptyRows = collect(range(1, $emptyRowsCount))->map(function () { + return [ + 'label' => '', + 'value' => '', + 'dataSource' => null, + ]; + }); + + // Prepend empty rows to the existing fields + $fieldsWithEmpty = $emptyRows->merge($fields); + + $assetData->put('fields', $fieldsWithEmpty->take($template->getSupportFields())); + return $assetData; + } + else{ + $assetData->put('fields', $fields->take($template->getSupportFields())); + return $assetData; + } - return $assetData; }); if ($template instanceof Sheet) { @@ -183,7 +232,7 @@ class Label implements View $template->writeAll($pdf, $data); $filename = $assets->count() > 1 ? 'assets.pdf' : $assets->first()->asset_tag.'.pdf'; - $pdf->Output($filename, 'I'); + $pdf->Output($filename, $this->destination); } /** diff --git a/composer.json b/composer.json index 9a76301e24..8e4110b936 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "snipe/snipe-it", + "name": "grokability/snipe-it", "description": "Open source asset management system built on Laravel.", "keywords": [ "assets", @@ -14,17 +14,22 @@ { "type": "vcs", "url": "https://github.com/grokability/laravel-scim-server" + }, + { + "type": "vcs", + "url": "https://github.com/grokability/html" } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-curl": "*", "ext-fileinfo": "*", + "ext-iconv": "*", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*", "alek13/slack": "^2.0", - "arietimmerman/laravel-scim-server": "dev-master", + "arietimmerman/laravel-scim-server": "dev-laravel_11_compatibility", "bacon/bacon-qr-code": "^2.0", "barryvdh/laravel-debugbar": "^3.13", "barryvdh/laravel-dompdf": "^2.0", @@ -34,27 +39,30 @@ "eduardokum/laravel-mail-auto-embed": "^2.0", "enshrined/svg-sanitize": "^0.15.0", "erusev/parsedown": "^1.7", + "fakerphp/faker": "^1.24", "guzzlehttp/guzzle": "^7.0.1", "intervention/image": "^2.5", "javiereguiluz/easyslugger": "^1.0", "laravel-notification-channels/google-chat": "^3.0", - "laravel-notification-channels/microsoft-teams": "^1.1", - "laravel/framework": "^10.0", + "laravel-notification-channels/microsoft-teams": "^1.2", + "laravel/framework": "^11.0", "laravel/helpers": "^1.4", - "laravel/passport": "^11.0", - "laravel/slack-notification-channel": "^2.3", + "laravel/passport": "^12.0", + "laravel/slack-notification-channel": "^3.4", "laravel/socialite": "^5.6", "laravel/tinker": "^2.6", "laravel/ui": "^4.0", - "laravelcollective/html": "^6.2", + "laravelcollective/html": "6.x-dev", "league/csv": "^9.7", "league/flysystem-aws-s3-v3": "^3.0", "livewire/livewire": "^3.5", "neitanod/forceutf8": "^2.0", - "nesbot/carbon": "^2.32", - "nunomaduro/collision": "^6.1", + "nesbot/carbon": "^3.0", + "nunomaduro/collision": "^8.1", "okvpn/clock-lts": "^1.0", "onelogin/php-saml": "^3.4", + "onnov/detect-encoding": "^2.0", + "osa-eg/laravel-teams-notification": "^2.1", "paragonie/constant_time_encoding": "^2.3", "paragonie/sodium_compat": "^1.19", "phpdocumentor/reflection-docblock": "^5.1", @@ -63,6 +71,7 @@ "rollbar/rollbar-laravel": "^8.0", "spatie/laravel-backup": "^8.8", "spatie/laravel-ignition": "^2.0", + "tabuna/breadcrumbs": "^4.2", "tecnickcom/tc-lib-barcode": "^1.15", "tecnickcom/tcpdf": "^6.5", "unicodeveloper/laravel-password": "^1.0", @@ -74,17 +83,14 @@ "ext-exif": "*" }, "require-dev": { - "brianium/paratest": "^v6.4.4", - "fakerphp/faker": "^1.16", "larastan/larastan": "^2.9", "mockery/mockery": "^1.4", - "nunomaduro/phpinsights": "^2.7", + "nunomaduro/phpinsights": "^2.11", "php-mock/php-mock-phpunit": "^2.10", - "phpunit/phpunit": "^9.6.19", + "phpunit/phpunit": "^10.0", "squizlabs/php_codesniffer": "^3.5", "symfony/css-selector": "^4.4", - "symfony/dom-crawler": "^4.4", - "vimeo/psalm": "^5.13" + "symfony/dom-crawler": "^4.4" }, "extra": { "laravel": { @@ -120,7 +126,9 @@ ], "post-create-project-cmd": [ "php artisan key:generate" - ] + ], + "coverage:herd:clover": "herd coverage vendor/bin/phpunit --coverage-clover tests/coverage/clover.xml", + "coverage:herd:html": "herd coverage vendor/bin/phpunit --coverage-html tests/coverage/html" }, "config": { "preferred-install": "dist", diff --git a/composer.lock b/composer.lock index ae6675b718..e88ef1f841 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "51e716db4ccd70bf942062789f7303ad", + "content-hash": "089c4016869fc4c657d80bdd4b100ec2", "packages": [ { "name": "alek13/slack", - "version": "2.2.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/php-slack/slack.git", - "reference": "9222449402df4e1e57d7850be87898b2c99803bd" + "reference": "9b50bae7a8149a3afb96cb11674972e3adcdf103" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-slack/slack/zipball/9222449402df4e1e57d7850be87898b2c99803bd", - "reference": "9222449402df4e1e57d7850be87898b2c99803bd", + "url": "https://api.github.com/repos/php-slack/slack/zipball/9b50bae7a8149a3afb96cb11674972e3adcdf103", + "reference": "9b50bae7a8149a3afb96cb11674972e3adcdf103", "shasum": "" }, "require": { @@ -62,7 +62,7 @@ ], "support": { "issues": "https://github.com/php-slack/slack/issues", - "source": "https://github.com/php-slack/slack/tree/2.2.1" + "source": "https://github.com/php-slack/slack/tree/2.3.0" }, "funding": [ { @@ -70,26 +70,26 @@ "type": "custom" } ], - "time": "2021-10-20T22:52:32+00:00" + "time": "2024-12-20T13:50:33+00:00" }, { "name": "arietimmerman/laravel-scim-server", - "version": "dev-master", + "version": "dev-laravel_11_compatibility", "source": { "type": "git", "url": "https://github.com/grokability/laravel-scim-server.git", - "reference": "dda6dfb60d70fb6cca4b8d4ce1c5f4c19deaab2d" + "reference": "6c771799090bfe04dcee94a1dc9f82870aed4dbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/dda6dfb60d70fb6cca4b8d4ce1c5f4c19deaab2d", - "reference": "dda6dfb60d70fb6cca4b8d4ce1c5f4c19deaab2d", + "url": "https://api.github.com/repos/grokability/laravel-scim-server/zipball/6c771799090bfe04dcee94a1dc9f82870aed4dbe", + "reference": "6c771799090bfe04dcee94a1dc9f82870aed4dbe", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "php": "^7.0|^8.0", "tmilos/scim-filter-parser": "^1.3", "tmilos/scim-schema": "^0.1.0" @@ -98,7 +98,6 @@ "laravel/legacy-factories": "*", "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0" }, - "default-branch": true, "type": "library", "extra": { "laravel": { @@ -131,22 +130,22 @@ ], "description": "Laravel Package for creating a SCIM server", "support": { - "source": "https://github.com/grokability/laravel-scim-server/tree/master" + "source": "https://github.com/grokability/laravel-scim-server/tree/laravel_11_compatibility" }, - "time": "2023-09-07T16:45:26+00:00" + "time": "2025-01-20T14:49:28+00:00" }, { "name": "aws/aws-crt-php", - "version": "v1.2.5", + "version": "v1.2.7", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b" + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/0ea1f04ec5aa9f049f97e012d1ed63b76834a31b", - "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", "shasum": "" }, "require": { @@ -185,22 +184,22 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.5" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" }, - "time": "2024-04-19T21:30:56+00:00" + "time": "2024-10-18T22:15:13+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.314.6", + "version": "3.337.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "d04da330b0201edab71edd33a03b8d5ad6e4a313" + "reference": "f885dd803a257da9d54e72a4750bba73e1196aee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d04da330b0201edab71edd33a03b8d5ad6e4a313", - "reference": "d04da330b0201edab71edd33a03b8d5ad6e4a313", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f885dd803a257da9d54e72a4750bba73e1196aee", + "reference": "f885dd803a257da9d54e72a4750bba73e1196aee", "shasum": "" }, "require": { @@ -229,8 +228,8 @@ "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" }, @@ -253,7 +252,10 @@ ], "psr-4": { "Aws\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/data/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -280,9 +282,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.314.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.337.2" }, - "time": "2024-06-20T18:09:51+00:00" + "time": "2025-01-17T19:10:04+00:00" }, { "name": "bacon/bacon-qr-code", @@ -340,44 +342,44 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.13.5", + "version": "v3.15.4", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07" + "reference": "c0667ea91f7185f1e074402c5788195e96bf8106" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/92d86be45ee54edff735e46856f64f14b6a8bb07", - "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0667ea91f7185f1e074402c5788195e96bf8106", + "reference": "c0667ea91f7185f1e074402c5788195e96bf8106", "shasum": "" }, "require": { - "illuminate/routing": "^9|^10|^11", - "illuminate/session": "^9|^10|^11", - "illuminate/support": "^9|^10|^11", - "maximebf/debugbar": "~1.22.0", - "php": "^8.0", + "illuminate/routing": "^9|^10|^11|^12", + "illuminate/session": "^9|^10|^11|^12", + "illuminate/support": "^9|^10|^11|^12", + "php": "^8.1", + "php-debugbar/php-debugbar": "~2.1.1", "symfony/finder": "^6|^7" }, "require-dev": { "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^5|^6|^7|^8|^9", - "phpunit/phpunit": "^9.6|^10.5", + "orchestra/testbench-dusk": "^7|^8|^9|^10", + "phpunit/phpunit": "^9.5.10|^10|^11", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.13-dev" - }, "laravel": { - "providers": [ - "Barryvdh\\Debugbar\\ServiceProvider" - ], "aliases": { "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" - } + }, + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.15-dev" } }, "autoload": { @@ -402,13 +404,14 @@ "keywords": [ "debug", "debugbar", + "dev", "laravel", "profiler", "webprofiler" ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.5" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.4" }, "funding": [ { @@ -420,7 +423,7 @@ "type": "github" } ], - "time": "2024-04-12T11:20:37+00:00" + "time": "2025-04-16T06:32:06+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -449,17 +452,17 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - }, "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, "providers": [ "Barryvdh\\DomPDF\\ServiceProvider" - ], - "aliases": { - "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf", - "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf" - } + ] + }, + "branch-alias": { + "dev-master": "2.0-dev" } }, "autoload": { @@ -630,23 +633,23 @@ }, { "name": "dasprid/enum", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", - "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90", + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90", "shasum": "" }, "require": { "php": ">=7.1 <9.0" }, "require-dev": { - "phpunit/phpunit": "^7 | ^8 | ^9", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", "squizlabs/php_codesniffer": "*" }, "type": "library", @@ -674,9 +677,9 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.6" }, - "time": "2023-08-25T16:18:39+00:00" + "time": "2024-08-09T14:30:48+00:00" }, { "name": "defuse/php-encryption", @@ -747,16 +750,16 @@ }, { "name": "dflydev/dot-access-data", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "f41715465d65213d644d3141a6a93081be5d3549" + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", - "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { @@ -816,9 +819,9 @@ ], "support": { "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "time": "2022-10-27T11:44:00+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { "name": "doctrine/cache", @@ -921,16 +924,16 @@ }, { "name": "doctrine/dbal", - "version": "3.8.6", + "version": "3.9.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1" + "reference": "ec16c82f20be1a7224e65ac67144a29199f87959" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/b7411825cf7efb7e51f9791dea19d86e43b399a1", - "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/ec16c82f20be1a7224e65ac67144a29199f87959", + "reference": "ec16c82f20be1a7224e65ac67144a29199f87959", "shasum": "" }, "require": { @@ -946,15 +949,13 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.11.5", - "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "9.6.19", - "psalm/plugin-phpunit": "0.18.4", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "9.6.22", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.1", + "squizlabs/php_codesniffer": "3.10.2", "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0", - "vimeo/psalm": "4.30.0" + "symfony/console": "^4.4|^5.4|^6.0|^7.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -1014,7 +1015,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.6" + "source": "https://github.com/doctrine/dbal/tree/3.9.4" }, "funding": [ { @@ -1030,33 +1031,31 @@ "type": "tidelift" } ], - "time": "2024-06-19T10:38:17+00:00" + "time": "2025-01-16T08:28:55+00:00" }, { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -1064,7 +1063,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1075,9 +1074,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.4" }, - "time": "2024-01-30T19:34:25+00:00" + "time": "2024-12-07T21:18:45+00:00" }, { "name": "doctrine/event-manager", @@ -1472,16 +1471,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.3", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" + "reference": "8c784d071debd117328803d86b2097615b457500" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", + "reference": "8c784d071debd117328803d86b2097615b457500", "shasum": "" }, "require": { @@ -1494,10 +1493,14 @@ "require-dev": { "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.0", - "phpstan/phpstan-webmozart-assert": "^1.0", "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -1521,7 +1524,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" }, "funding": [ { @@ -1529,7 +1532,7 @@ "type": "github" } ], - "time": "2023-08-10T19:36:49+00:00" + "time": "2024-10-09T13:47:03+00:00" }, { "name": "eduardokum/laravel-mail-auto-embed", @@ -1603,16 +1606,16 @@ }, { "name": "egulias/email-validator", - "version": "4.0.2", + "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" + "reference": "b115554301161fa21467629f1e1391c1936de517" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", - "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517", + "reference": "b115554301161fa21467629f1e1391c1936de517", "shasum": "" }, "require": { @@ -1658,7 +1661,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.3" }, "funding": [ { @@ -1666,7 +1669,7 @@ "type": "github" } ], - "time": "2023-10-06T06:47:41+00:00" + "time": "2024-12-27T00:36:43+00:00" }, { "name": "enshrined/svg-sanitize", @@ -1764,27 +1767,90 @@ "time": "2019-12-30T22:54:17+00:00" }, { - "name": "filp/whoops", - "version": "2.15.4", + "name": "fakerphp/faker", + "version": "v1.24.1", "source": { "type": "git", - "url": "https://github.com/filp/whoops.git", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "filp/whoops", + "version": "2.16.0", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2", + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -1824,7 +1890,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.4" + "source": "https://github.com/filp/whoops/tree/2.16.0" }, "funding": [ { @@ -1832,20 +1898,20 @@ "type": "github" } ], - "time": "2023-11-03T12:00:00+00:00" + "time": "2024-09-25T12:00:00+00:00" }, { "name": "firebase/php-jwt", - "version": "v6.10.1", + "version": "v6.10.2", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "500501c2ce893c824c801da135d02661199f60c5" + "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5", - "reference": "500501c2ce893c824c801da135d02661199f60c5", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/30c19ed0f3264cb660ea496895cfb6ef7ee3653b", + "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b", "shasum": "" }, "require": { @@ -1893,9 +1959,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.10.1" + "source": "https://github.com/firebase/php-jwt/tree/v6.10.2" }, - "time": "2024-05-18T18:05:11+00:00" + "time": "2024-11-24T11:22:49+00:00" }, { "name": "fruitcake/php-cors", @@ -1970,24 +2036,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.2", + "version": "v1.1.3", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2" + "phpoption/phpoption": "^1.9.3" }, "require-dev": { - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "autoload": { @@ -2016,7 +2082,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" }, "funding": [ { @@ -2028,26 +2094,26 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:16:48+00:00" + "time": "2024-07-20T21:45:45+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.8.1", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -2058,9 +2124,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -2138,7 +2204,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -2154,20 +2220,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.2", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -2175,7 +2241,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { @@ -2221,7 +2287,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -2237,20 +2303,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -2265,8 +2331,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2337,7 +2403,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -2353,7 +2419,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2471,16 +2537,16 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - }, "laravel": { - "providers": [ - "Intervention\\Image\\ImageServiceProvider" - ], "aliases": { "Image": "Intervention\\Image\\Facades\\Image" - } + }, + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "2.4-dev" } }, "autoload": { @@ -2675,23 +2741,23 @@ }, { "name": "laravel/framework", - "version": "v10.48.14", + "version": "v11.44.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "27cb4736bb7e60a5311ec73160068dfbcf98336b" + "reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/27cb4736bb7e60a5311ec73160068dfbcf98336b", - "reference": "27cb4736bb7e60a5311ec73160068dfbcf98336b", + "url": "https://api.github.com/repos/laravel/framework/zipball/0883d4175f4e2b5c299e7087ad3c74f2ce195c6d", + "reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d", "shasum": "" }, "require": { "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", - "dragonmantank/cron-expression": "^3.3.2", + "dragonmantank/cron-expression": "^3.4", "egulias/email-validator": "^3.2.1|^4.0", "ext-ctype": "*", "ext-filter": "*", @@ -2700,44 +2766,45 @@ "ext-openssl": "*", "ext-session": "*", "ext-tokenizer": "*", - "fruitcake/php-cors": "^1.2", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.9", - "laravel/serializable-closure": "^1.3", - "league/commonmark": "^2.2.1", - "league/flysystem": "^3.8.0", + "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", + "laravel/serializable-closure": "^1.3|^2.0", + "league/commonmark": "^2.6", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.67", - "nunomaduro/termwind": "^1.13", - "php": "^8.1", + "nesbot/carbon": "^2.72.6|^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^6.2", - "symfony/error-handler": "^6.2", - "symfony/finder": "^6.2", - "symfony/http-foundation": "^6.4", - "symfony/http-kernel": "^6.2", - "symfony/mailer": "^6.2", - "symfony/mime": "^6.2", - "symfony/process": "^6.2", - "symfony/routing": "^6.2", - "symfony/uid": "^6.2", - "symfony/var-dumper": "^6.2", + "symfony/console": "^7.0.3", + "symfony/error-handler": "^7.0.3", + "symfony/finder": "^7.0.3", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.0.3", + "symfony/mailer": "^7.0.3", + "symfony/mime": "^7.0.3", + "symfony/polyfill-php83": "^1.31", + "symfony/process": "^7.0.3", + "symfony/routing": "^7.0.3", + "symfony/uid": "^7.0.3", + "symfony/var-dumper": "^7.0.3", "tijsverkoyen/css-to-inline-styles": "^2.2.5", - "vlucas/phpdotenv": "^5.4.1", - "voku/portable-ascii": "^2.0" + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" }, "conflict": { - "carbonphp/carbon-doctrine-types": ">=3.0", - "doctrine/dbal": ">=4.0", - "mockery/mockery": "1.6.8", - "phpunit/phpunit": ">=11.0.0", "tightenco/collect": "<5.5.33" }, "provide": { "psr/container-implementation": "1.1|2.0", + "psr/log-implementation": "1.0|2.0|3.0", "psr/simple-cache-implementation": "1.0|2.0|3.0" }, "replace": { @@ -2746,6 +2813,7 @@ "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", "illuminate/conditionable": "self.version", "illuminate/config": "self.version", "illuminate/console": "self.version", @@ -2773,36 +2841,39 @@ "illuminate/testing": "self.version", "illuminate/translation": "self.version", "illuminate/validation": "self.version", - "illuminate/view": "self.version" + "illuminate/view": "self.version", + "spatie/once": "*" }, "require-dev": { "ably/ably-php": "^1.0", - "aws/aws-sdk-php": "^3.235.5", - "doctrine/dbal": "^3.5.1", + "aws/aws-sdk-php": "^3.322.9", "ext-gmp": "*", - "fakerphp/faker": "^1.21", - "guzzlehttp/guzzle": "^7.5", - "league/flysystem-aws-s3-v3": "^3.0", - "league/flysystem-ftp": "^3.0", - "league/flysystem-path-prefixing": "^3.3", - "league/flysystem-read-only": "^3.3", - "league/flysystem-sftp-v3": "^3.0", - "mockery/mockery": "^1.5.1", - "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^8.23.4", - "pda/pheanstalk": "^4.0", - "phpstan/phpstan": "^1.4.7", - "phpunit/phpunit": "^10.0.7", - "predis/predis": "^2.0.2", - "symfony/cache": "^6.2", - "symfony/http-client": "^6.2.4", - "symfony/psr-http-message-bridge": "^2.0" + "fakerphp/faker": "^1.24", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "laravel/pint": "^1.18", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "orchestra/testbench-core": "^9.11.2", + "pda/pheanstalk": "^5.0.6", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", + "predis/predis": "^2.3", + "resend/resend-php": "^0.10.0", + "symfony/cache": "^7.0.3", + "symfony/http-client": "^7.0.3", + "symfony/psr-http-message-bridge": "^7.0.3", + "symfony/translation": "^7.0.3" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", - "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", "ext-apcu": "Required to use the APC cache driver.", "ext-fileinfo": "Required to use the Filesystem class.", "ext-ftp": "Required to use the Flysystem FTP driver.", @@ -2811,42 +2882,45 @@ "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", - "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", - "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).", "laravel/tinker": "Required to use the tinker console command (^2.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", - "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", - "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", - "league/flysystem-read-only": "Required to use read-only disks (^3.3)", - "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", - "mockery/mockery": "Required to use mocking (^1.5.1).", - "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8|^10.0.7).", - "predis/predis": "Required to use the predis connector (^2.0.2).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "mockery/mockery": "Required to use mocking (^1.6).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1).", + "predis/predis": "Required to use the predis connector (^2.3).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^6.2).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^6.2).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.2).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.2).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.2).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" } }, "autoload": { "files": [ + "src/Illuminate/Collections/functions.php", "src/Illuminate/Collections/helpers.php", "src/Illuminate/Events/functions.php", "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Support/functions.php", "src/Illuminate/Support/helpers.php" ], "psr-4": { @@ -2878,20 +2952,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-06-21T10:06:42+00:00" + "time": "2025-03-05T15:34:10+00:00" }, { "name": "laravel/helpers", - "version": "v1.7.0", + "version": "v1.7.1", "source": { "type": "git", "url": "https://github.com/laravel/helpers.git", - "reference": "6caaa242a23bc39b4e3cf57304b5409260a7a346" + "reference": "f28907033d7edf8a0525cfb781ab30ce6d531c35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/6caaa242a23bc39b4e3cf57304b5409260a7a346", - "reference": "6caaa242a23bc39b4e3cf57304b5409260a7a346", + "url": "https://api.github.com/repos/laravel/helpers/zipball/f28907033d7edf8a0525cfb781ab30ce6d531c35", + "reference": "f28907033d7edf8a0525cfb781ab30ce6d531c35", "shasum": "" }, "require": { @@ -2933,54 +3007,52 @@ "laravel" ], "support": { - "source": "https://github.com/laravel/helpers/tree/v1.7.0" + "source": "https://github.com/laravel/helpers/tree/v1.7.1" }, - "time": "2023-11-30T14:09:05+00:00" + "time": "2024-11-26T14:56:25+00:00" }, { "name": "laravel/passport", - "version": "v11.10.6", + "version": "v12.4.0", "source": { "type": "git", "url": "https://github.com/laravel/passport.git", - "reference": "2642f360c51dfde3a6ea60f86ae5d9a8c0caf3cf" + "reference": "b06a413cb18d07123ced88ba8caa432d40e3bb8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/passport/zipball/2642f360c51dfde3a6ea60f86ae5d9a8c0caf3cf", - "reference": "2642f360c51dfde3a6ea60f86ae5d9a8c0caf3cf", + "url": "https://api.github.com/repos/laravel/passport/zipball/b06a413cb18d07123ced88ba8caa432d40e3bb8c", + "reference": "b06a413cb18d07123ced88ba8caa432d40e3bb8c", "shasum": "" }, "require": { "ext-json": "*", "firebase/php-jwt": "^6.4", - "illuminate/auth": "^9.0|^10.0", - "illuminate/console": "^9.0|^10.0", - "illuminate/container": "^9.0|^10.0", - "illuminate/contracts": "^9.0|^10.0", - "illuminate/cookie": "^9.0|^10.0", - "illuminate/database": "^9.0|^10.0", - "illuminate/encryption": "^9.0|^10.0", - "illuminate/http": "^9.0|^10.0", - "illuminate/support": "^9.0|^10.0", + "illuminate/auth": "^9.21|^10.0|^11.0", + "illuminate/console": "^9.21|^10.0|^11.0", + "illuminate/container": "^9.21|^10.0|^11.0", + "illuminate/contracts": "^9.21|^10.0|^11.0", + "illuminate/cookie": "^9.21|^10.0|^11.0", + "illuminate/database": "^9.21|^10.0|^11.0", + "illuminate/encryption": "^9.21|^10.0|^11.0", + "illuminate/http": "^9.21|^10.0|^11.0", + "illuminate/support": "^9.21|^10.0|^11.0", "lcobucci/jwt": "^4.3|^5.0", "league/oauth2-server": "^8.5.3", "nyholm/psr7": "^1.5", "php": "^8.0", "phpseclib/phpseclib": "^2.0|^3.0", - "symfony/psr-http-message-bridge": "^2.1" + "symfony/console": "^6.0|^7.0", + "symfony/psr-http-message-bridge": "^2.1|^6.0|^7.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^7.31|^8.11", + "orchestra/testbench": "^7.35|^8.14|^9.0", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.3|^10.5" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "11.x-dev" - }, "laravel": { "providers": [ "Laravel\\Passport\\PassportServiceProvider" @@ -3013,25 +3085,25 @@ "issues": "https://github.com/laravel/passport/issues", "source": "https://github.com/laravel/passport" }, - "time": "2024-03-01T11:11:18+00:00" + "time": "2025-01-13T17:40:20+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.24", + "version": "v0.3.3", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "409b0b4305273472f3754826e68f4edbd0150149" + "reference": "749395fcd5f8f7530fe1f00dfa84eb22c83d94ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/409b0b4305273472f3754826e68f4edbd0150149", - "reference": "409b0b4305273472f3754826e68f4edbd0150149", + "url": "https://api.github.com/repos/laravel/prompts/zipball/749395fcd5f8f7530fe1f00dfa84eb22c83d94ea", + "reference": "749395fcd5f8f7530fe1f00dfa84eb22c83d94ea", "shasum": "" }, "require": { + "composer-runtime-api": "^2.2", "ext-mbstring": "*", - "illuminate/collections": "^10.0|^11.0", "php": "^8.1", "symfony/console": "^6.2|^7.0" }, @@ -3040,8 +3112,9 @@ "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { + "illuminate/collections": "^10.0|^11.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3", + "pestphp/pest": "^2.3|^3.4", "phpstan/phpstan": "^1.11", "phpstan/phpstan-mockery": "^1.1" }, @@ -3051,7 +3124,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.1.x-dev" + "dev-main": "0.3.x-dev" } }, "autoload": { @@ -3069,37 +3142,38 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.24" + "source": "https://github.com/laravel/prompts/tree/v0.3.3" }, - "time": "2024-06-17T13:58:22+00:00" + "time": "2024-12-30T15:53:31+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.3", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754" + "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/613b2d4998f85564d40497e05e89cb6d9bd1cbe8", + "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": "^8.1" }, "require-dev": { - "nesbot/carbon": "^2.61", - "pestphp/pest": "^1.21.3", - "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11" + "illuminate/support": "^10.0|^11.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -3131,40 +3205,44 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-11-08T14:08:06+00:00" + "time": "2024-12-16T15:26:28+00:00" }, { "name": "laravel/slack-notification-channel", - "version": "v2.5.0", + "version": "v3.4.2", "source": { "type": "git", "url": "https://github.com/laravel/slack-notification-channel.git", - "reference": "e0d4be5e01d443a69fa89f0a4cac6bd2eda2be8f" + "reference": "282d52d70195283eb1b92e59d7bdc2d525361f4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/e0d4be5e01d443a69fa89f0a4cac6bd2eda2be8f", - "reference": "e0d4be5e01d443a69fa89f0a4cac6bd2eda2be8f", + "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/282d52d70195283eb1b92e59d7bdc2d525361f4b", + "reference": "282d52d70195283eb1b92e59d7bdc2d525361f4b", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/notifications": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0", - "php": "^7.1.3|^8.0" + "guzzlehttp/guzzle": "^7.0", + "illuminate/http": "^9.0|^10.0|^11.0", + "illuminate/notifications": "^9.0|^10.0|^11.0", + "illuminate/support": "^9.0|^10.0|^11.0", + "php": "^8.0" }, "require-dev": { "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "orchestra/testbench": "^7.0|^8.0|^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.0|^10.4" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - }, "laravel": { "providers": [ "Illuminate\\Notifications\\SlackChannelServiceProvider" ] + }, + "branch-alias": { + "dev-master": "3.x-dev" } }, "autoload": { @@ -3190,22 +3268,22 @@ ], "support": { "issues": "https://github.com/laravel/slack-notification-channel/issues", - "source": "https://github.com/laravel/slack-notification-channel/tree/v2.5.0" + "source": "https://github.com/laravel/slack-notification-channel/tree/v3.4.2" }, - "time": "2023-01-12T16:21:26+00:00" + "time": "2024-11-29T14:59:32+00:00" }, { "name": "laravel/socialite", - "version": "v5.15.0", + "version": "v5.16.1", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "c8234bfb286a8210df8d62f94562c71bfda4a446" + "reference": "4e5be83c0b3ecf81b2ffa47092e917d1f79dce71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/c8234bfb286a8210df8d62f94562c71bfda4a446", - "reference": "c8234bfb286a8210df8d62f94562c71bfda4a446", + "url": "https://api.github.com/repos/laravel/socialite/zipball/4e5be83c0b3ecf81b2ffa47092e917d1f79dce71", + "reference": "4e5be83c0b3ecf81b2ffa47092e917d1f79dce71", "shasum": "" }, "require": { @@ -3215,7 +3293,7 @@ "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "league/oauth1-client": "^1.10.1", + "league/oauth1-client": "^1.11", "php": "^7.2|^8.0", "phpseclib/phpseclib": "^3.0" }, @@ -3227,16 +3305,16 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - }, "laravel": { - "providers": [ - "Laravel\\Socialite\\SocialiteServiceProvider" - ], "aliases": { "Socialite": "Laravel\\Socialite\\Facades\\Socialite" - } + }, + "providers": [ + "Laravel\\Socialite\\SocialiteServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "5.x-dev" } }, "autoload": { @@ -3264,20 +3342,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2024-06-11T13:33:20+00:00" + "time": "2024-12-11T16:43:51+00:00" }, { "name": "laravel/tinker", - "version": "v2.9.0", + "version": "v2.10.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe" + "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe", - "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "url": "https://api.github.com/repos/laravel/tinker/zipball/ba4d51eb56de7711b3a37d63aa0643e99a339ae5", + "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5", "shasum": "" }, "require": { @@ -3328,22 +3406,22 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.9.0" + "source": "https://github.com/laravel/tinker/tree/v2.10.0" }, - "time": "2024-01-04T16:10:04+00:00" + "time": "2024-09-23T13:32:56+00:00" }, { "name": "laravel/ui", - "version": "v4.5.2", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "c75396f63268c95b053c8e4814eb70e0875e9628" + "reference": "a34609b15ae0c0512a0cf47a21695a2729cb7f93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/c75396f63268c95b053c8e4814eb70e0875e9628", - "reference": "c75396f63268c95b053c8e4814eb70e0875e9628", + "url": "https://api.github.com/repos/laravel/ui/zipball/a34609b15ae0c0512a0cf47a21695a2729cb7f93", + "reference": "a34609b15ae0c0512a0cf47a21695a2729cb7f93", "shasum": "" }, "require": { @@ -3360,13 +3438,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - }, "laravel": { "providers": [ "Laravel\\Ui\\UiServiceProvider" ] + }, + "branch-alias": { + "dev-master": "4.x-dev" } }, "autoload": { @@ -3391,37 +3469,38 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v4.5.2" + "source": "https://github.com/laravel/ui/tree/v4.6.0" }, - "time": "2024-05-08T18:07:10+00:00" + "time": "2024-11-21T15:06:41+00:00" }, { "name": "laravelcollective/html", - "version": "v6.4.1", + "version": "6.x-dev", "source": { "type": "git", - "url": "https://github.com/LaravelCollective/html.git", - "reference": "64ddfdcaeeb8d332bd98bef442bef81e39c3910b" + "url": "https://github.com/grokability/html.git", + "reference": "944b7029c207914ecec437c0141b7a0e32d9e1a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LaravelCollective/html/zipball/64ddfdcaeeb8d332bd98bef442bef81e39c3910b", - "reference": "64ddfdcaeeb8d332bd98bef442bef81e39c3910b", + "url": "https://api.github.com/repos/grokability/html/zipball/944b7029c207914ecec437c0141b7a0e32d9e1a1", + "reference": "944b7029c207914ecec437c0141b7a0e32d9e1a1", "shasum": "" }, "require": { - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/session": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/view": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/session": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/view": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "php": ">=7.2.5" }, "require-dev": { - "illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "mockery/mockery": "~1.0", "phpunit/phpunit": "~8.5|^9.5.10" }, + "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3438,14 +3517,13 @@ } }, "autoload": { - "files": [ - "src/helpers.php" - ], "psr-4": { "Collective\\Html\\": "src/" - } + }, + "files": [ + "src/helpers.php" + ] }, - "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -3466,42 +3544,42 @@ "source": "https://github.com/LaravelCollective/html" }, "abandoned": "spatie/laravel-html", - "time": "2023-04-25T02:46:11+00:00" + "time": "2025-01-20T14:37:14+00:00" }, { "name": "lcobucci/jwt", - "version": "5.3.0", + "version": "5.4.2", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83" + "reference": "ea1ce71cbf9741e445a5914e2f67cdbb484ff712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", - "reference": "08071d8d2c7f4b00222cc4b1fb6aa46990a80f83", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/ea1ce71cbf9741e445a5914e2f67cdbb484ff712", + "reference": "ea1ce71cbf9741e445a5914e2f67cdbb484ff712", "shasum": "" }, "require": { "ext-openssl": "*", "ext-sodium": "*", - "php": "~8.1.0 || ~8.2.0 || ~8.3.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "psr/clock": "^1.0" }, "require-dev": { - "infection/infection": "^0.27.0", - "lcobucci/clock": "^3.0", + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", "lcobucci/coding-standard": "^11.0", - "phpbench/phpbench": "^1.2.9", + "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.2", "phpstan/phpstan": "^1.10.7", "phpstan/phpstan-deprecation-rules": "^1.1.3", "phpstan/phpstan-phpunit": "^1.3.10", "phpstan/phpstan-strict-rules": "^1.5.0", - "phpunit/phpunit": "^10.2.6" + "phpunit/phpunit": "^11.1" }, "suggest": { - "lcobucci/clock": ">= 3.0" + "lcobucci/clock": ">= 3.2" }, "type": "library", "autoload": { @@ -3527,7 +3605,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.3.0" + "source": "https://github.com/lcobucci/jwt/tree/5.4.2" }, "funding": [ { @@ -3539,20 +3617,20 @@ "type": "patreon" } ], - "time": "2024-04-11T23:07:54+00:00" + "time": "2024-11-07T12:54:35+00:00" }, { "name": "league/commonmark", - "version": "2.4.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", + "reference": "6fbb36d44824ed4091adbcf4c7d4a3923cdb3405", "shasum": "" }, "require": { @@ -3565,8 +3643,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.3", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -3577,8 +3655,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 || ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, @@ -3588,7 +3667,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "2.8-dev" } }, "autoload": { @@ -3645,7 +3724,7 @@ "type": "tidelift" } ], - "time": "2024-02-02T11:59:32+00:00" + "time": "2025-05-05T12:20:28+00:00" }, { "name": "league/config", @@ -3731,16 +3810,16 @@ }, { "name": "league/csv", - "version": "9.16.0", + "version": "9.21.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "998280c6c34bd67d8125fdc8b45bae28d761b440" + "reference": "72196d11ebba22d868954cb39c0c7346207430cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/998280c6c34bd67d8125fdc8b45bae28d761b440", - "reference": "998280c6c34bd67d8125fdc8b45bae28d761b440", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/72196d11ebba22d868954cb39c0c7346207430cc", + "reference": "72196d11ebba22d868954cb39c0c7346207430cc", "shasum": "" }, "require": { @@ -3748,17 +3827,16 @@ "php": "^8.1.2" }, "require-dev": { - "doctrine/collections": "^2.2.2", "ext-dom": "*", "ext-xdebug": "*", - "friendsofphp/php-cs-fixer": "^3.57.1", - "phpbench/phpbench": "^1.2.15", - "phpstan/phpstan": "^1.11.1", - "phpstan/phpstan-deprecation-rules": "^1.2.0", - "phpstan/phpstan-phpunit": "^1.4.0", - "phpstan/phpstan-strict-rules": "^1.6.0", - "phpunit/phpunit": "^10.5.16 || ^11.1.3", - "symfony/var-dumper": "^6.4.6 || ^7.0.7" + "friendsofphp/php-cs-fixer": "^3.64.0", + "phpbench/phpbench": "^1.3.1", + "phpstan/phpstan": "^1.12.11", + "phpstan/phpstan-deprecation-rules": "^1.2.1", + "phpstan/phpstan-phpunit": "^1.4.1", + "phpstan/phpstan-strict-rules": "^1.6.1", + "phpunit/phpunit": "^10.5.16 || ^11.4.3", + "symfony/var-dumper": "^6.4.8 || ^7.1.8" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", @@ -3776,7 +3854,7 @@ "src/functions_include.php" ], "psr-4": { - "League\\Csv\\": "src" + "League\\Csv\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3815,7 +3893,7 @@ "type": "github" } ], - "time": "2024-05-24T11:04:54+00:00" + "time": "2025-01-08T19:27:58+00:00" }, { "name": "league/event", @@ -3873,16 +3951,16 @@ }, { "name": "league/flysystem", - "version": "3.28.0", + "version": "3.29.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c" + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", "shasum": "" }, "require": { @@ -3950,22 +4028,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" }, - "time": "2024-05-22T10:09:12+00:00" + "time": "2024-10-08T08:58:34+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "22071ef1604bc776f5ff2468ac27a752514665c8" + "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/22071ef1604bc776f5ff2468ac27a752514665c8", - "reference": "22071ef1604bc776f5ff2468ac27a752514665c8", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c6ff6d4606e48249b63f269eba7fabdb584e76a9", + "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9", "shasum": "" }, "require": { @@ -4005,22 +4083,22 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.29.0" }, - "time": "2024-05-06T20:05:52+00:00" + "time": "2024-08-17T13:10:48+00:00" }, { "name": "league/flysystem-local", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40" + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", "shasum": "" }, "require": { @@ -4054,22 +4132,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" }, - "time": "2024-05-06T20:05:52+00:00" + "time": "2024-08-09T21:24:39+00:00" }, { "name": "league/mime-type-detection", - "version": "1.15.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", "shasum": "" }, "require": { @@ -4100,7 +4178,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { @@ -4112,20 +4190,20 @@ "type": "tidelift" } ], - "time": "2024-01-28T23:22:08+00:00" + "time": "2024-09-21T08:32:55+00:00" }, { "name": "league/oauth1-client", - "version": "v1.10.1", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth1-client.git", - "reference": "d6365b901b5c287dd41f143033315e2f777e1167" + "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167", - "reference": "d6365b901b5c287dd41f143033315e2f777e1167", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/f9c94b088837eb1aae1ad7c4f23eb65cc6993055", + "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055", "shasum": "" }, "require": { @@ -4186,22 +4264,22 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth1-client/issues", - "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1" + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.11.0" }, - "time": "2022-04-15T14:02:14+00:00" + "time": "2024-12-10T19:59:05+00:00" }, { "name": "league/oauth2-server", - "version": "8.5.4", + "version": "8.5.5", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-server.git", - "reference": "ab7714d073844497fd222d5d0a217629089936bc" + "reference": "cc8778350f905667e796b3c2364a9d3bd7a73518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/ab7714d073844497fd222d5d0a217629089936bc", - "reference": "ab7714d073844497fd222d5d0a217629089936bc", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/cc8778350f905667e796b3c2364a9d3bd7a73518", + "reference": "cc8778350f905667e796b3c2364a9d3bd7a73518", "shasum": "" }, "require": { @@ -4268,7 +4346,7 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-server/issues", - "source": "https://github.com/thephpleague/oauth2-server/tree/8.5.4" + "source": "https://github.com/thephpleague/oauth2-server/tree/8.5.5" }, "funding": [ { @@ -4276,24 +4354,24 @@ "type": "github" } ], - "time": "2023-08-25T22:35:12+00:00" + "time": "2024-12-20T23:06:10+00:00" }, { "name": "league/uri", - "version": "7.4.1", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4" + "reference": "81fb5145d2644324614cc532b28efd0215bda430" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/bedb6e55eff0c933668addaa7efa1e1f2c417cc4", - "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.3", + "league/uri-interfaces": "^7.5", "php": "^8.1" }, "conflict": { @@ -4358,7 +4436,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.4.1" + "source": "https://github.com/thephpleague/uri/tree/7.5.1" }, "funding": [ { @@ -4366,20 +4444,20 @@ "type": "github" } ], - "time": "2024-03-23T07:42:40+00:00" + "time": "2024-12-08T08:40:02+00:00" }, { "name": "league/uri-interfaces", - "version": "7.4.1", + "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "8d43ef5c841032c87e2de015972c06f3865ef718" + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/8d43ef5c841032c87e2de015972c06f3865ef718", - "reference": "8d43ef5c841032c87e2de015972c06f3865ef718", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", "shasum": "" }, "require": { @@ -4442,7 +4520,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.4.1" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" }, "funding": [ { @@ -4450,20 +4528,20 @@ "type": "github" } ], - "time": "2024-03-23T07:42:40+00:00" + "time": "2024-12-08T08:18:47+00:00" }, { "name": "livewire/livewire", - "version": "v3.5.1", + "version": "v3.5.18", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "da044261bb5c5449397f18fda3409f14acf47c0a" + "reference": "62f0fa6b340a467c25baa590a567d9a134b357da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/da044261bb5c5449397f18fda3409f14acf47c0a", - "reference": "da044261bb5c5449397f18fda3409f14acf47c0a", + "url": "https://api.github.com/repos/livewire/livewire/zipball/62f0fa6b340a467c25baa590a567d9a134b357da", + "reference": "62f0fa6b340a467c25baa590a567d9a134b357da", "shasum": "" }, "require": { @@ -4471,6 +4549,7 @@ "illuminate/routing": "^10.0|^11.0", "illuminate/support": "^10.0|^11.0", "illuminate/validation": "^10.0|^11.0", + "laravel/prompts": "^0.1.24|^0.2|^0.3", "league/mime-type-detection": "^1.9", "php": "^8.1", "symfony/console": "^6.0|^7.0", @@ -4479,7 +4558,6 @@ "require-dev": { "calebporzio/sushi": "^2.1", "laravel/framework": "^10.15.0|^11.0", - "laravel/prompts": "^0.1.6", "mockery/mockery": "^1.3.1", "orchestra/testbench": "^8.21.0|^9.0", "orchestra/testbench-dusk": "^8.24|^9.1", @@ -4489,12 +4567,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Livewire\\LivewireServiceProvider" - ], "aliases": { "Livewire": "Livewire\\Livewire" - } + }, + "providers": [ + "Livewire\\LivewireServiceProvider" + ] } }, "autoload": { @@ -4518,7 +4596,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.1" + "source": "https://github.com/livewire/livewire/tree/v3.5.18" }, "funding": [ { @@ -4526,7 +4604,7 @@ "type": "github" } ], - "time": "2024-06-18T11:10:42+00:00" + "time": "2024-12-23T15:05:02+00:00" }, { "name": "masterminds/html5", @@ -4595,86 +4673,18 @@ }, "time": "2024-03-31T07:05:07+00:00" }, - { - "name": "maximebf/debugbar", - "version": "v1.22.3", - "source": { - "type": "git", - "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", - "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", - "shasum": "" - }, - "require": { - "php": "^7.2|^8", - "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4|^5|^6|^7" - }, - "require-dev": { - "dbrekelmans/bdi": "^1", - "phpunit/phpunit": "^8|^9", - "symfony/panther": "^1|^2.1", - "twig/twig": "^1.38|^2.7|^3.0" - }, - "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.22-dev" - } - }, - "autoload": { - "psr-4": { - "DebugBar\\": "src/DebugBar/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maxime Bouroumeau-Fuseau", - "email": "maxime.bouroumeau@gmail.com", - "homepage": "http://maximebf.com" - }, - { - "name": "Barry vd. Heuvel", - "email": "barryvdh@gmail.com" - } - ], - "description": "Debug bar in the browser for php application", - "homepage": "https://github.com/maximebf/php-debugbar", - "keywords": [ - "debug", - "debugbar" - ], - "support": { - "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.3" - }, - "time": "2024-04-03T19:39:26+00:00" - }, { "name": "monolog/monolog", - "version": "3.6.0", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654" + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", - "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", "shasum": "" }, "require": { @@ -4694,12 +4704,14 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.5.17", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", - "ruflin/elastica": "^7", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -4750,7 +4762,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.6.0" + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" }, "funding": [ { @@ -4762,20 +4774,20 @@ "type": "tidelift" } ], - "time": "2024-04-12T21:02:21+00:00" + "time": "2025-03-24T10:02:05+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", "shasum": "" }, "require": { @@ -4792,7 +4804,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -4826,9 +4838,9 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" }, - "time": "2023-08-25T10:54:48+00:00" + "time": "2024-09-04T18:46:31+00:00" }, { "name": "neitanod/forceutf8", @@ -4873,52 +4885,47 @@ }, { "name": "nesbot/carbon", - "version": "2.72.5", + "version": "3.8.4", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "afd46589c216118ecd48ff2b95d77596af1e57ed" + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "129700ed449b1f02d70272d2ac802357c8c30c58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/afd46589c216118ecd48ff2b95d77596af1e57ed", - "reference": "afd46589c216118ecd48ff2b95d77596af1e57ed", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", + "reference": "129700ed449b1f02d70272d2ac802357c8c30c58", "shasum": "" }, "require": { - "carbonphp/carbon-doctrine-types": "*", + "carbonphp/carbon-doctrine-types": "<100.0", "ext-json": "*", - "php": "^7.1.8 || ^8.0", + "php": "^8.1", "psr/clock": "^1.0", + "symfony/clock": "^6.3 || ^7.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" }, "provide": { "psr/clock-implementation": "1.0" }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0", - "doctrine/orm": "^2.7 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.0", - "kylekatarnls/multi-tester": "^2.0", - "ondrejmirtes/better-reflection": "*", - "phpmd/phpmd": "^2.9", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.99 || ^1.7.14", - "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", - "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", - "squizlabs/php_codesniffer": "^3.4" + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.57.2", + "kylekatarnls/multi-tester": "^2.5.3", + "ondrejmirtes/better-reflection": "^6.25.0.4", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.11.2", + "phpunit/phpunit": "^10.5.20", + "squizlabs/php_codesniffer": "^3.9.0" }, "bin": [ "bin/carbon" ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev", - "dev-2.x": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -4928,6 +4935,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -4976,28 +4987,28 @@ "type": "tidelift" } ], - "time": "2024-06-03T19:18:41+00:00" + "time": "2024-12-27T09:25:35+00:00" }, { "name": "nette/schema", - "version": "v1.3.0", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.3" + "php": "8.1 - 8.4" }, "require-dev": { - "nette/tester": "^2.4", + "nette/tester": "^2.5.2", "phpstan/phpstan-nette": "^1.0", "tracy/tracy": "^2.8" }, @@ -5036,26 +5047,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.0" + "source": "https://github.com/nette/schema/tree/v1.3.2" }, - "time": "2023-12-11T11:54:22+00:00" + "time": "2024-10-06T23:10:23+00:00" }, { "name": "nette/utils", - "version": "v4.0.4", + "version": "v4.0.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", - "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", "shasum": "" }, "require": { - "php": ">=8.0 <8.4" + "php": "8.0 - 8.4" }, "conflict": { "nette/finder": "<3", @@ -5122,22 +5133,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.4" + "source": "https://github.com/nette/utils/tree/v4.0.5" }, - "time": "2024-01-17T16:50:36+00:00" + "time": "2024-08-07T15:39:19+00:00" }, { "name": "nikic/php-parser", - "version": "v4.19.1", + "version": "v4.19.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", - "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/715f4d25e225bc47b293a8b997fe6ce99bf987d2", + "reference": "715f4d25e225bc47b293a8b997fe6ce99bf987d2", "shasum": "" }, "require": { @@ -5146,7 +5157,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -5178,51 +5189,60 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.4" }, - "time": "2024-03-17T08:10:35+00:00" + "time": "2024-09-29T15:01:53+00:00" }, { "name": "nunomaduro/collision", - "version": "v6.4.0", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "f05978827b9343cba381ca05b8c7deee346b6015" + "reference": "f5c101b929c958e849a633283adff296ed5f38f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f05978827b9343cba381ca05b8c7deee346b6015", - "reference": "f05978827b9343cba381ca05b8c7deee346b6015", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", + "reference": "f5c101b929c958e849a633283adff296ed5f38f5", "shasum": "" }, "require": { - "filp/whoops": "^2.14.5", - "php": "^8.0.0", - "symfony/console": "^6.0.2" + "filp/whoops": "^2.16.0", + "nunomaduro/termwind": "^2.1.0", + "php": "^8.2.0", + "symfony/console": "^7.1.5" + }, + "conflict": { + "laravel/framework": "<11.0.0 || >=12.0.0", + "phpunit/phpunit": "<10.5.1 || >=12.0.0" }, "require-dev": { - "brianium/paratest": "^6.4.1", - "laravel/framework": "^9.26.1", - "laravel/pint": "^1.1.1", - "nunomaduro/larastan": "^1.0.3", - "nunomaduro/mock-final-classes": "^1.1.0", - "orchestra/testbench": "^7.7", - "phpunit/phpunit": "^9.5.23", - "spatie/ignition": "^1.4.1" + "larastan/larastan": "^2.9.8", + "laravel/framework": "^11.28.0", + "laravel/pint": "^1.18.1", + "laravel/sail": "^1.36.0", + "laravel/sanctum": "^4.0.3", + "laravel/tinker": "^2.10.0", + "orchestra/testbench-core": "^9.5.3", + "pestphp/pest": "^2.36.0 || ^3.4.0", + "sebastian/environment": "^6.1.0 || ^7.2.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-develop": "6.x-dev" - }, "laravel": { "providers": [ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" } }, "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], "psr-4": { "NunoMaduro\\Collision\\": "src/" } @@ -5268,37 +5288,35 @@ "type": "patreon" } ], - "time": "2023-01-03T12:54:54+00:00" + "time": "2024-10-15T16:06:32+00:00" }, { "name": "nunomaduro/termwind", - "version": "v1.15.1", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc" + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/8ab0b32c8caa4a2e09700ea32925441385e4a5dc", - "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^8.0", - "symfony/console": "^5.3.0|^6.0.0" + "php": "^8.2", + "symfony/console": "^7.1.8" }, "require-dev": { - "ergebnis/phpstan-rules": "^1.0.", - "illuminate/console": "^8.0|^9.0", - "illuminate/support": "^8.0|^9.0", - "laravel/pint": "^1.0.0", - "pestphp/pest": "^1.21.0", - "pestphp/pest-plugin-mock": "^1.0", - "phpstan/phpstan": "^1.4.6", - "phpstan/phpstan-strict-rules": "^1.1.0", - "symfony/var-dumper": "^5.2.7|^6.0.0", + "illuminate/console": "^11.33.2", + "laravel/pint": "^1.18.2", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0", + "phpstan/phpstan": "^1.12.11", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^7.1.8", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -5307,6 +5325,9 @@ "providers": [ "Termwind\\Laravel\\TermwindServiceProvider" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -5338,7 +5359,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.15.1" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" }, "funding": [ { @@ -5354,20 +5375,20 @@ "type": "github" } ], - "time": "2023-02-08T01:06:31+00:00" + "time": "2024-11-21T10:39:51+00:00" }, { "name": "nyholm/psr7", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e" + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/aa5fc277a4f5508013d571341ade0c3886d4d00e", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", "shasum": "" }, "require": { @@ -5420,7 +5441,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.8.1" + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { @@ -5432,7 +5453,7 @@ "type": "github" } ], - "time": "2023-11-13T09:31:12+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { "name": "okvpn/clock-lts", @@ -5557,6 +5578,135 @@ ], "time": "2024-05-30T15:14:26+00:00" }, + { + "name": "onnov/detect-encoding", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/onnov/detect-encoding.git", + "reference": "6a8159ac3e6178ae043244b9d66a9b2701121e07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/onnov/detect-encoding/zipball/6a8159ac3e6178ae043244b9d66a9b2701121e07", + "reference": "6a8159ac3e6178ae043244b9d66a9b2701121e07", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.3" + }, + "require-dev": { + "infection/infection": "*", + "phpbench/phpbench": "*", + "phpcompatibility/php-compatibility": "*", + "phpmd/phpmd": "*", + "phpstan/phpstan": "*", + "phpstan/phpstan-strict-rules": "*", + "phpunit/phpunit": "*", + "roave/backward-compatibility-check": "*", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Onnov\\DetectEncoding\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "onnov", + "email": "oblnn@yandex.ru" + } + ], + "description": "Text encoding definition class instead of mb_detect_encoding. Defines: utf-8, windows-1251, koi8-r, iso-8859-5, ibm866, .....", + "homepage": "https://github.com/onnov/detect-encoding", + "keywords": [ + "cyrillic", + "encoding", + "ibm866", + "iconv", + "iso-8859-5", + "koi8-r", + "mb_detect_encoding", + "utf-8", + "windows-1251" + ], + "support": { + "issues": "https://github.com/onnov/detect-encoding/issues", + "source": "https://github.com/onnov/detect-encoding/tree/v2.0.0" + }, + "time": "2021-01-04T14:29:34+00:00" + }, + { + "name": "osa-eg/laravel-teams-notification", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/osa-eg/laravel-teams-notification.git", + "reference": "76173689930aca92b5174a3b102e705279192c0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/osa-eg/laravel-teams-notification/zipball/76173689930aca92b5174a3b102e705279192c0f", + "reference": "76173689930aca92b5174a3b102e705279192c0f", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": ">=6.5", + "illuminate/support": "^5.5 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0", + "monolog/monolog": ">=1.0", + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Osama\\LaravelTeamsNotification\\LaravelTeamsNotificationServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Osama\\LaravelTeamsNotification\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Osama Saad", + "email": "osaad96eg@gmail.com" + } + ], + "description": "A Laravel package to send notifications to Microsoft Teams", + "keywords": [ + "Teams", + "adaptive-card", + "laravel", + "logging", + "microsoft-teams-workflow", + "notification", + "teams-connector", + "teams-webhock", + "teams-workflow", + "teams_logging" + ], + "support": { + "issues": "https://github.com/osa-eg/laravel-teams-notification/issues", + "source": "https://github.com/osa-eg/laravel-teams-notification/tree/v2.1.2" + }, + "time": "2024-09-23T05:24:48+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v2.7.0", @@ -5850,6 +6000,76 @@ }, "time": "2024-04-08T12:52:34+00:00" }, + { + "name": "php-debugbar/php-debugbar", + "version": "v2.1.6", + "source": { + "type": "git", + "url": "https://github.com/php-debugbar/php-debugbar.git", + "reference": "16fa68da5617220594aa5e33fa9de415f94784a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/16fa68da5617220594aa5e33fa9de415f94784a0", + "reference": "16fa68da5617220594aa5e33fa9de415f94784a0", + "shasum": "" + }, + "require": { + "php": "^8", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4|^5|^6|^7" + }, + "require-dev": { + "dbrekelmans/bdi": "^1", + "phpunit/phpunit": "^8|^9", + "symfony/panther": "^1|^2.1", + "twig/twig": "^1.38|^2.7|^3.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/php-debugbar/php-debugbar", + "keywords": [ + "debug", + "debug bar", + "debugbar", + "dev" + ], + "support": { + "issues": "https://github.com/php-debugbar/php-debugbar/issues", + "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.1.6" + }, + "time": "2025-02-21T17:47:03+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -5905,16 +6125,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.1", + "version": "5.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", "shasum": "" }, "require": { @@ -5923,17 +6143,17 @@ "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.5", + "mockery/mockery": "~1.3.5 || ~1.6.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.8", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.13" + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -5963,29 +6183,29 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" }, - "time": "2024-05-21T05:55:05+00:00" + "time": "2024-12-07T09:39:29+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -6021,22 +6241,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.2", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", "shasum": "" }, "require": { @@ -6044,13 +6264,13 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "1.9-dev" @@ -6086,7 +6306,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" }, "funding": [ { @@ -6098,20 +6318,20 @@ "type": "tidelift" } ], - "time": "2023-11-12T21:59:55+00:00" + "time": "2024-07-20T21:41:07+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.38", + "version": "3.0.43", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "b18b8788e51156c4dd97b7f220a31149a0052067" + "reference": "709ec107af3cb2f385b9617be72af8cf62441d02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/b18b8788e51156c4dd97b7f220a31149a0052067", - "reference": "b18b8788e51156c4dd97b7f220a31149a0052067", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/709ec107af3cb2f385b9617be72af8cf62441d02", + "reference": "709ec107af3cb2f385b9617be72af8cf62441d02", "shasum": "" }, "require": { @@ -6192,7 +6412,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.38" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.43" }, "funding": [ { @@ -6208,30 +6428,31 @@ "type": "tidelift" } ], - "time": "2024-06-17T10:11:32+00:00" + "time": "2024-12-14T21:12:59+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.19.0", + "version": "v1.20.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87" + "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/67a759e7d8746d501c41536ba40cd9c0a07d6a87", - "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/a0165c648cab6a80311c74ffc708a07bb53ecc93", + "reference": "a0165c648cab6a80311c74ffc708a07bb53ecc93", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", - "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", + "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.* || 8.4.*", "phpdocumentor/reflection-docblock": "^5.2", "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0", "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.40", "phpspec/phpspec": "^6.0 || ^7.0", "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" @@ -6275,22 +6496,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.19.0" + "source": "https://github.com/phpspec/prophecy/tree/v1.20.0" }, - "time": "2024-02-29T11:52:51+00:00" + "time": "2024-11-19T13:12:41+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.1", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -6322,30 +6543,30 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2024-05-31T08:52:43+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "pragmarx/google2fa", - "version": "v8.0.1", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3" + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3", - "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", "shasum": "" }, "require": { - "paragonie/constant_time_encoding": "^1.0|^2.0", + "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0", "php": "^7.1|^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.18", + "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^7.5.15|^8.5|^9.0" }, "type": "library", @@ -6374,9 +6595,9 @@ ], "support": { "issues": "https://github.com/antonioribeiro/google2fa/issues", - "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1" + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.3" }, - "time": "2022-06-13T21:57:56+00:00" + "time": "2024-09-05T11:56:40+00:00" }, { "name": "pragmarx/google2fa-laravel", @@ -6407,20 +6628,20 @@ }, "type": "library", "extra": { + "laravel": { + "aliases": { + "Google2FA": "PragmaRX\\Google2FALaravel\\Facade" + }, + "providers": [ + "PragmaRX\\Google2FALaravel\\ServiceProvider" + ] + }, "component": "package", "frameworks": [ "Laravel" ], "branch-alias": { "dev-master": "0.2-dev" - }, - "laravel": { - "providers": [ - "PragmaRX\\Google2FALaravel\\ServiceProvider" - ], - "aliases": { - "Google2FA": "PragmaRX\\Google2FALaravel\\Facade" - } } }, "autoload": { @@ -6877,16 +7098,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -6921,9 +7142,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", @@ -6978,16 +7199,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.4", + "version": "v0.12.7", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", "shasum": "" }, "require": { @@ -7014,12 +7235,12 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "0.12.x-dev" - }, "bamarni-bin": { "bin-links": false, "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" } }, "autoload": { @@ -7051,9 +7272,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" }, - "time": "2024-06-10T01:18:23+00:00" + "time": "2024-12-10T01:58:33+00:00" }, { "name": "ralouphie/getallheaders", @@ -7282,16 +7503,16 @@ }, { "name": "robrichards/xmlseclibs", - "version": "3.1.1", + "version": "3.1.3", "source": { "type": "git", "url": "https://github.com/robrichards/xmlseclibs.git", - "reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df" + "reference": "2bdfd742624d739dfadbd415f00181b4a77aaf07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/f8f19e58f26cdb42c54b214ff8a820760292f8df", - "reference": "f8f19e58f26cdb42c54b214ff8a820760292f8df", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/2bdfd742624d739dfadbd415f00181b4a77aaf07", + "reference": "2bdfd742624d739dfadbd415f00181b4a77aaf07", "shasum": "" }, "require": { @@ -7318,28 +7539,28 @@ ], "support": { "issues": "https://github.com/robrichards/xmlseclibs/issues", - "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.1" + "source": "https://github.com/robrichards/xmlseclibs/tree/3.1.3" }, - "time": "2020-09-05T13:00:25+00:00" + "time": "2024-11-20T21:13:56+00:00" }, { "name": "rollbar/rollbar", - "version": "v4.0.2", + "version": "v4.1.1", "source": { "type": "git", "url": "https://github.com/rollbar/rollbar-php.git", - "reference": "6966a4c97c170298108f5e5b543c2710658f8fe0" + "reference": "146216bbfde503632c6969a7c334c8a63c98f50e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rollbar/rollbar-php/zipball/6966a4c97c170298108f5e5b543c2710658f8fe0", - "reference": "6966a4c97c170298108f5e5b543c2710658f8fe0", + "url": "https://api.github.com/repos/rollbar/rollbar-php/zipball/146216bbfde503632c6969a7c334c8a63c98f50e", + "reference": "146216bbfde503632c6969a7c334c8a63c98f50e", "shasum": "" }, "require": { "ext-curl": "*", "monolog/monolog": "^2 || ^3", - "php": ">=8.0.0 <9.0", + "php": ">=8.1.0 <9.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { @@ -7381,28 +7602,28 @@ "support": { "email": "support@rollbar.com", "issues": "https://github.com/rollbar/rollbar-php/issues", - "source": "https://github.com/rollbar/rollbar-php/tree/v4.0.2" + "source": "https://github.com/rollbar/rollbar-php/tree/v4.1.1" }, - "time": "2023-12-22T21:40:31+00:00" + "time": "2025-03-21T19:07:30+00:00" }, { "name": "rollbar/rollbar-laravel", - "version": "v8.0.1", + "version": "v8.1.2", "source": { "type": "git", "url": "https://github.com/rollbar/rollbar-php-laravel.git", - "reference": "69ecc7c19b54b1d88bf8a2bdfa56277500cc41f6" + "reference": "fbe96ea28601294aa7dc41cd16db62cddddeb0c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rollbar/rollbar-php-laravel/zipball/69ecc7c19b54b1d88bf8a2bdfa56277500cc41f6", - "reference": "69ecc7c19b54b1d88bf8a2bdfa56277500cc41f6", + "url": "https://api.github.com/repos/rollbar/rollbar-php-laravel/zipball/fbe96ea28601294aa7dc41cd16db62cddddeb0c2", + "reference": "fbe96ea28601294aa7dc41cd16db62cddddeb0c2", "shasum": "" }, "require": { - "illuminate/support": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0|^12.0", "php": "^8.1", - "rollbar/rollbar": "^4.0" + "rollbar/rollbar": "v4.1.1" }, "require-dev": { "mockery/mockery": "^1", @@ -7414,12 +7635,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Rollbar\\Laravel\\RollbarServiceProvider" - ], "aliases": { "Rollbar": "Rollbar\\Laravel\\Facades\\Rollbar" - } + }, + "providers": [ + "Rollbar\\Laravel\\RollbarServiceProvider" + ] } }, "autoload": { @@ -7453,30 +7674,30 @@ ], "support": { "issues": "https://github.com/rollbar/rollbar-php-laravel/issues", - "source": "https://github.com/rollbar/rollbar-php-laravel/tree/v8.0.1" + "source": "https://github.com/rollbar/rollbar-php-laravel/tree/v8.1.2" }, - "time": "2024-02-13T21:58:17+00:00" + "time": "2025-03-21T19:26:39+00:00" }, { "name": "sabberworm/php-css-parser", - "version": "v8.5.1", + "version": "v8.7.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152" + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/4a3d572b0f8b28bb6fd016ae8bbfc445facef152", - "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf", + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf", "shasum": "" }, "require": { "ext-iconv": "*", - "php": ">=5.6.20" + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.27" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -7518,36 +7739,38 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.5.1" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0" }, - "time": "2024-02-15T16:41:13+00:00" + "time": "2024-10-27T17:38:32+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7586,7 +7809,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" }, "funding": [ { @@ -7594,33 +7818,33 @@ "type": "github" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2024-10-18T14:56:07+00:00" }, { "name": "sebastian/diff", - "version": "4.0.6", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", - "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -7652,7 +7876,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" }, "funding": [ { @@ -7660,34 +7885,34 @@ "type": "github" } ], - "time": "2024-03-02T06:30:58+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.6", + "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", - "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -7729,7 +7954,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" }, "funding": [ { @@ -7737,32 +7963,32 @@ "type": "github" } ], - "time": "2024-03-02T06:33:00+00:00" + "time": "2024-03-02T07:17:12+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7792,7 +8018,7 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" }, "funding": [ { @@ -7800,31 +8026,31 @@ "type": "github" } ], - "time": "2023-02-03T06:07:39+00:00" + "time": "2023-02-03T07:05:40+00:00" }, { "name": "spatie/backtrace", - "version": "1.6.1", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "8373b9d51638292e3bfd736a9c19a654111b4a23" + "reference": "0f2477c520e3729de58e061b8192f161c99f770b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/8373b9d51638292e3bfd736a9c19a654111b4a23", - "reference": "8373b9d51638292e3bfd736a9c19a654111b4a23", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/0f2477c520e3729de58e061b8192f161c99f770b", + "reference": "0f2477c520e3729de58e061b8192f161c99f770b", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": "^7.3 || ^8.0" }, "require-dev": { "ext-json": "*", - "laravel/serializable-closure": "^1.3", - "phpunit/phpunit": "^9.3", - "spatie/phpunit-snapshot-assertions": "^4.2", - "symfony/var-dumper": "^5.1" + "laravel/serializable-closure": "^1.3 || ^2.0", + "phpunit/phpunit": "^9.3 || ^11.4.3", + "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", + "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0" }, "type": "library", "autoload": { @@ -7851,7 +8077,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.6.1" + "source": "https://github.com/spatie/backtrace/tree/1.7.1" }, "funding": [ { @@ -7863,20 +8089,20 @@ "type": "other" } ], - "time": "2024-04-24T13:22:11+00:00" + "time": "2024-12-02T13:28:15+00:00" }, { "name": "spatie/db-dumper", - "version": "3.6.0", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "faca5056830bccea04eadf07e8074669cb9e905e" + "reference": "55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/faca5056830bccea04eadf07e8074669cb9e905e", - "reference": "faca5056830bccea04eadf07e8074669cb9e905e", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016", + "reference": "55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016", "shasum": "" }, "require": { @@ -7914,7 +8140,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.6.0" + "source": "https://github.com/spatie/db-dumper/tree/3.7.1" }, "funding": [ { @@ -7926,20 +8152,20 @@ "type": "github" } ], - "time": "2024-04-24T14:54:13+00:00" + "time": "2024-11-18T14:54:31+00:00" }, { "name": "spatie/error-solutions", - "version": "1.0.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "d60d4d2ef4b7701c86134ded959667cac6215233" + "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d60d4d2ef4b7701c86134ded959667cac6215233", - "reference": "d60d4d2ef4b7701c86134ded959667cac6215233", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d239a65235a1eb128dfa0a4e4c4ef032ea11b541", + "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541", "shasum": "" }, "require": { @@ -7992,7 +8218,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/1.0.1" + "source": "https://github.com/spatie/error-solutions/tree/1.1.2" }, "funding": [ { @@ -8000,20 +8226,20 @@ "type": "github" } ], - "time": "2024-06-21T10:09:00+00:00" + "time": "2024-12-11T09:51:56+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.7.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234" + "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234", - "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", + "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", "shasum": "" }, "require": { @@ -8031,7 +8257,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/phpunit-snapshot-assertions": "^4.0|^5.0" + "spatie/pest-plugin-snapshots": "^1.0|^2.0" }, "type": "library", "extra": { @@ -8061,7 +8287,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.7.0" + "source": "https://github.com/spatie/flare-client-php/tree/1.10.0" }, "funding": [ { @@ -8069,7 +8295,7 @@ "type": "github" } ], - "time": "2024-06-12T14:39:14+00:00" + "time": "2024-12-02T14:30:06+00:00" }, { "name": "spatie/ignition", @@ -8156,16 +8382,16 @@ }, { "name": "spatie/laravel-backup", - "version": "8.8.1", + "version": "8.8.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-backup.git", - "reference": "a9c2d2f726f4c60c2dc5d7c0c8380f72492638c2" + "reference": "5b672713283703a74c629ccd67b1d77eb57e24b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/a9c2d2f726f4c60c2dc5d7c0c8380f72492638c2", - "reference": "a9c2d2f726f4c60c2dc5d7c0c8380f72492638c2", + "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/5b672713283703a74c629ccd67b1d77eb57e24b9", + "reference": "5b672713283703a74c629ccd67b1d77eb57e24b9", "shasum": "" }, "require": { @@ -8239,7 +8465,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-backup/issues", - "source": "https://github.com/spatie/laravel-backup/tree/8.8.1" + "source": "https://github.com/spatie/laravel-backup/tree/8.8.2" }, "funding": [ { @@ -8251,20 +8477,20 @@ "type": "other" } ], - "time": "2024-06-04T11:31:33+00:00" + "time": "2024-08-07T11:07:52+00:00" }, { "name": "spatie/laravel-ignition", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "3c067b75bfb50574db8f7e2c3978c65eed71126c" + "reference": "62042df15314b829d0f26e02108f559018e2aad0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/3c067b75bfb50574db8f7e2c3978c65eed71126c", - "reference": "3c067b75bfb50574db8f7e2c3978c65eed71126c", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/62042df15314b829d0f26e02108f559018e2aad0", + "reference": "62042df15314b829d0f26e02108f559018e2aad0", "shasum": "" }, "require": { @@ -8295,12 +8521,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Spatie\\LaravelIgnition\\IgnitionServiceProvider" - ], "aliases": { "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" - } + }, + "providers": [ + "Spatie\\LaravelIgnition\\IgnitionServiceProvider" + ] } }, "autoload": { @@ -8342,20 +8568,20 @@ "type": "github" } ], - "time": "2024-06-12T15:01:18+00:00" + "time": "2024-12-02T08:43:31+00:00" }, { "name": "spatie/laravel-package-tools", - "version": "1.16.4", + "version": "1.18.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53" + "reference": "d41c44a7eab604c3eb0cad93210612d4c1429c20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", - "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d41c44a7eab604c3eb0cad93210612d4c1429c20", + "reference": "d41c44a7eab604c3eb0cad93210612d4c1429c20", "shasum": "" }, "require": { @@ -8364,10 +8590,10 @@ }, "require-dev": { "mockery/mockery": "^1.5", - "orchestra/testbench": "^7.7|^8.0", - "pestphp/pest": "^1.22", - "phpunit/phpunit": "^9.5.24", - "spatie/pest-plugin-test-time": "^1.1" + "orchestra/testbench": "^7.7|^8.0|^9.0", + "pestphp/pest": "^1.22|^2", + "phpunit/phpunit": "^9.5.24|^10.5", + "spatie/pest-plugin-test-time": "^1.1|^2.2" }, "type": "library", "autoload": { @@ -8394,7 +8620,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.4" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.18.2" }, "funding": [ { @@ -8402,45 +8628,46 @@ "type": "github" } ], - "time": "2024-03-20T07:29:11+00:00" + "time": "2025-01-20T14:14:17+00:00" }, { "name": "spatie/laravel-signal-aware-command", - "version": "1.3.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-signal-aware-command.git", - "reference": "46cda09a85aef3fd47fb73ddc7081f963e255571" + "reference": "49a5e671c3a3fd992187a777d01385fc6a84759d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-signal-aware-command/zipball/46cda09a85aef3fd47fb73ddc7081f963e255571", - "reference": "46cda09a85aef3fd47fb73ddc7081f963e255571", + "url": "https://api.github.com/repos/spatie/laravel-signal-aware-command/zipball/49a5e671c3a3fd992187a777d01385fc6a84759d", + "reference": "49a5e671c3a3fd992187a777d01385fc6a84759d", "shasum": "" }, "require": { - "illuminate/contracts": "^8.35|^9.0|^10.0", - "php": "^8.0", - "spatie/laravel-package-tools": "^1.4.3" + "illuminate/contracts": "^11.0", + "php": "^8.2", + "spatie/laravel-package-tools": "^1.4.3", + "symfony/console": "^7.0" }, "require-dev": { - "brianium/paratest": "^6.2", + "brianium/paratest": "^6.2|^7.0", "ext-pcntl": "*", - "nunomaduro/collision": "^5.3|^6.0", - "orchestra/testbench": "^6.16|^7.0|^8.0", - "pestphp/pest-plugin-laravel": "^1.3", - "phpunit/phpunit": "^9.5", + "nunomaduro/collision": "^5.3|^6.0|^7.0|^8.0", + "orchestra/testbench": "^9.0", + "pestphp/pest-plugin-laravel": "^1.3|^2.0", + "phpunit/phpunit": "^9.5|^10|^11", "spatie/laravel-ray": "^1.17" }, "type": "library", "extra": { "laravel": { - "providers": [ - "Spatie\\SignalAwareCommand\\SignalAwareCommandServiceProvider" - ], "aliases": { "Signal": "Spatie\\SignalAwareCommand\\Facades\\Signal" - } + }, + "providers": [ + "Spatie\\SignalAwareCommand\\SignalAwareCommandServiceProvider" + ] } }, "autoload": { @@ -8468,7 +8695,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-signal-aware-command/issues", - "source": "https://github.com/spatie/laravel-signal-aware-command/tree/1.3.0" + "source": "https://github.com/spatie/laravel-signal-aware-command/tree/2.0.0" }, "funding": [ { @@ -8476,20 +8703,20 @@ "type": "github" } ], - "time": "2023-01-14T21:10:59+00:00" + "time": "2024-02-05T13:37:25+00:00" }, { "name": "spatie/temporary-directory", - "version": "2.2.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/spatie/temporary-directory.git", - "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a" + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", - "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b", "shasum": "" }, "require": { @@ -8525,7 +8752,7 @@ ], "support": { "issues": "https://github.com/spatie/temporary-directory/issues", - "source": "https://github.com/spatie/temporary-directory/tree/2.2.1" + "source": "https://github.com/spatie/temporary-directory/tree/2.3.0" }, "funding": [ { @@ -8537,51 +8764,124 @@ "type": "github" } ], - "time": "2023-12-25T11:46:58+00:00" + "time": "2025-01-13T13:04:43+00:00" }, { - "name": "symfony/console", - "version": "v6.4.8", + "name": "symfony/clock", + "version": "v7.2.0", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "be5854cee0e8c7b110f00d695d11debdfa1a2a91" + "url": "https://github.com/symfony/clock.git", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/be5854cee0e8c7b110f00d695d11debdfa1a2a91", - "reference": "be5854cee0e8c7b110f00d695d11debdfa1a2a91", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/console", + "version": "v7.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "shasum": "" + }, + "require": { + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" + "symfony/string": "^6.4|^7.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8615,7 +8915,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.8" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -8631,7 +8931,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/css-selector", @@ -8701,16 +9001,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -8718,12 +9018,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -8748,7 +9048,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -8764,26 +9064,26 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/error-handler", - "version": "v6.4.8", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc" + "reference": "6150b89186573046167796fa5f3f76601d5145f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc", - "reference": "ef836152bf13472dc5fb5b08b0c0c4cfeddc0fcc", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6150b89186573046167796fa5f3f76601d5145f8", + "reference": "6150b89186573046167796fa5f3f76601d5145f8", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0" }, "conflict": { "symfony/deprecation-contracts": "<2.5", @@ -8792,7 +9092,7 @@ "require-dev": { "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^5.4|^6.0|^7.0" + "symfony/serializer": "^6.4|^7.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -8823,7 +9123,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.8" + "source": "https://github.com/symfony/error-handler/tree/v7.2.1" }, "funding": [ { @@ -8839,28 +9139,28 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -8869,13 +9169,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8903,7 +9203,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" }, "funding": [ { @@ -8919,20 +9219,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", "shasum": "" }, "require": { @@ -8941,12 +9241,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -8979,7 +9279,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" }, "funding": [ { @@ -8995,27 +9295,27 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/finder", - "version": "v6.4.8", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c" + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/3ef977a43883215d560a2cecb82ec8e62131471c", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.0|^7.0" + "symfony/filesystem": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9043,7 +9343,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.8" + "source": "https://github.com/symfony/finder/tree/v7.2.2" }, "funding": [ { @@ -9059,40 +9359,41 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.8", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "27de8cc95e11db7a50b027e71caaab9024545947" + "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/27de8cc95e11db7a50b027e71caaab9024545947", - "reference": "27de8cc95e11db7a50b027e71caaab9024545947", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62d1a43796ca3fea3f83a8470dfe63a4af3bc588", + "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-mbstring": "~1.1", "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.3" + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { - "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.3|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/rate-limiter": "^5.4|^6.0|^7.0" + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9120,7 +9421,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.8" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.2" }, "funding": [ { @@ -9136,77 +9437,77 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.8", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1" + "reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1", - "reference": "6c519aa3f32adcfd1d1f18d923f6b227d9acf3c1", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3c432966bd8c7ec7429663105f5a02d7e75b4306", + "reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.4", - "symfony/config": "<6.1", - "symfony/console": "<5.4", + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<5.4", - "symfony/form": "<5.4", - "symfony/http-client": "<5.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/mailer": "<5.4", - "symfony/messenger": "<5.4", - "symfony/translation": "<5.4", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<5.4", + "symfony/twig-bridge": "<6.4", "symfony/validator": "<6.4", - "symfony/var-dumper": "<6.3", - "twig/twig": "<2.13" + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^5.4|^6.0|^7.0", - "symfony/clock": "^6.2|^7.0", - "symfony/config": "^6.1|^7.0", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", - "symfony/dom-crawler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/property-access": "^5.4.5|^6.0.5|^7.0", - "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.4.4|^7.0.4", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.4|^7.0", - "symfony/var-exporter": "^6.2|^7.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.12" }, "type": "library", "autoload": { @@ -9234,7 +9535,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.8" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.2" }, "funding": [ { @@ -9250,43 +9551,43 @@ "type": "tidelift" } ], - "time": "2024-06-02T16:06:25+00:00" + "time": "2024-12-31T14:59:40+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "76326421d44c07f7824b19487cfbf87870b37efc" + "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/76326421d44c07f7824b19487cfbf87870b37efc", - "reference": "76326421d44c07f7824b19487cfbf87870b37efc", + "url": "https://api.github.com/repos/symfony/mailer/zipball/e4d358702fb66e4c8a2af08e90e7271a62de39cc", + "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc", "shasum": "" }, "require": { "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=8.1", + "php": ">=8.2", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/mime": "^6.2|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^7.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<5.4", - "symfony/messenger": "<6.2", - "symfony/mime": "<6.2", - "symfony/twig-bridge": "<6.2.1" + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/messenger": "^6.2|^7.0", - "symfony/twig-bridge": "^6.2|^7.0" + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -9314,7 +9615,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.8" + "source": "https://github.com/symfony/mailer/tree/v7.2.0" }, "funding": [ { @@ -9330,25 +9631,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-25T15:21:05+00:00" }, { "name": "symfony/mime", - "version": "v6.4.8", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "618597ab8b78ac86d1c75a9d0b35540cda074f33" + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/618597ab8b78ac86d1c75a9d0b35540cda074f33", - "reference": "618597ab8b78ac86d1c75a9d0b35540cda074f33", + "url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -9356,18 +9656,18 @@ "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<5.4", - "symfony/serializer": "<6.3.2" + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.4|^7.0", - "symfony/property-access": "^5.4|^6.0|^7.0", - "symfony/property-info": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.3.2|^7.0" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", "autoload": { @@ -9399,7 +9699,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.8" + "source": "https://github.com/symfony/mime/tree/v7.2.1" }, "funding": [ { @@ -9415,24 +9715,24 @@ "type": "tidelift" } ], - "time": "2024-06-01T07:50:16+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -9443,8 +9743,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9478,7 +9778,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -9494,24 +9794,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -9519,8 +9819,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9556,7 +9856,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -9572,26 +9872,25 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", - "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" @@ -9599,8 +9898,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9640,7 +9939,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" }, "funding": [ { @@ -9656,24 +9955,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -9681,8 +9980,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9721,7 +10020,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -9737,24 +10036,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -9765,8 +10064,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9801,7 +10100,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -9817,103 +10116,30 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "10112722600777e02d2745716b70c5db4ca70442" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", - "reference": "10112722600777e02d2745716b70c5db4ca70442", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9954,7 +10180,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -9970,30 +10196,30 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10030,7 +10256,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -10046,24 +10272,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:35:24+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-uuid": "*" @@ -10074,8 +10300,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10109,7 +10335,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" }, "funding": [ { @@ -10125,24 +10351,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5" + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8d92dd79149f29e89ee0f480254db595f6a6a2c5", - "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5", + "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -10170,7 +10396,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.8" + "source": "https://github.com/symfony/process/tree/v7.2.0" }, "funding": [ { @@ -10186,47 +10412,42 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-06T14:24:19+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v2.3.1", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e" + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e", - "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/http-message": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.5 || ^3.0", - "symfony/http-foundation": "^5.4 || ^6.0" + "php": ">=8.2", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" }, "require-dev": { "nyholm/psr7": "^1.1", - "psr/log": "^1.1 || ^2 || ^3", - "symfony/browser-kit": "^5.4 || ^6.0", - "symfony/config": "^5.4 || ^6.0", - "symfony/event-dispatcher": "^5.4 || ^6.0", - "symfony/framework-bundle": "^5.4 || ^6.0", - "symfony/http-kernel": "^5.4 || ^6.0", - "symfony/phpunit-bridge": "^6.2" - }, - "suggest": { - "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "symfony-bridge", - "extra": { - "branch-alias": { - "dev-main": "2.3-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" @@ -10246,11 +10467,11 @@ }, { "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "homepage": "https://symfony.com/contributors" } ], "description": "PSR HTTP message bridge", - "homepage": "http://symfony.com", + "homepage": "https://symfony.com", "keywords": [ "http", "http-message", @@ -10258,8 +10479,7 @@ "psr-7" ], "support": { - "issues": "https://github.com/symfony/psr-http-message-bridge/issues", - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.2.0" }, "funding": [ { @@ -10275,40 +10495,38 @@ "type": "tidelift" } ], - "time": "2023-07-26T11:53:26+00:00" + "time": "2024-09-26T08:57:56+00:00" }, { "name": "symfony/routing", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58" + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58", - "reference": "8a40d0f9b01f0fbb80885d3ce0ad6714fb603a58", + "url": "https://api.github.com/repos/symfony/routing/zipball/e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "doctrine/annotations": "<1.12", - "symfony/config": "<6.2", - "symfony/dependency-injection": "<5.4", - "symfony/yaml": "<5.4" + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^6.2|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10342,7 +10560,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.8" + "source": "https://github.com/symfony/routing/tree/v7.2.0" }, "funding": [ { @@ -10358,20 +10576,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-25T11:08:51+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", "shasum": "" }, "require": { @@ -10384,12 +10602,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -10425,7 +10643,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" }, "funding": [ { @@ -10441,24 +10659,24 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/string", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d" + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/a147c0f826c4a1f3afb763ab8e009e37c877a44d", - "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -10468,11 +10686,12 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10511,7 +10730,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.8" + "source": "https://github.com/symfony/string/tree/v7.2.0" }, "funding": [ { @@ -10527,20 +10746,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-13T13:31:26+00:00" }, { "name": "symfony/translation", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a002933b13989fc4bd0b58e04bf7eec5210e438a" + "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a002933b13989fc4bd0b58e04bf7eec5210e438a", - "reference": "a002933b13989fc4bd0b58e04bf7eec5210e438a", + "url": "https://api.github.com/repos/symfony/translation/zipball/bee9bfabfa8b4045a66bf82520e492cddbaffa66", + "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66", "shasum": "" }, "require": { @@ -10606,7 +10825,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.8" + "source": "https://github.com/symfony/translation/tree/v6.4.13" }, "funding": [ { @@ -10622,20 +10841,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-27T18:14:25+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", "shasum": "" }, "require": { @@ -10643,12 +10862,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -10684,7 +10903,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" }, "funding": [ { @@ -10700,28 +10919,28 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/uid", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf" + "reference": "2d294d0c48df244c71c105a169d0190bfb080426" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", - "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "url": "https://api.github.com/repos/symfony/uid/zipball/2d294d0c48df244c71c105a169d0190bfb080426", + "reference": "2d294d0c48df244c71c105a169d0190bfb080426", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10758,7 +10977,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.8" + "source": "https://github.com/symfony/uid/tree/v7.2.0" }, "funding": [ { @@ -10774,38 +10993,36 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "ad23ca4312395f0a8a8633c831ef4c4ee542ed25" + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ad23ca4312395f0a8a8633c831ef4c4ee542ed25", - "reference": "ad23ca4312395f0a8a8633c831ef4c4ee542ed25", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6a22929407dec8765d6e2b6ff85b800b245879c", + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -10843,7 +11060,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.8" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.0" }, "funding": [ { @@ -10859,7 +11076,73 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-08T15:48:14+00:00" + }, + { + "name": "tabuna/breadcrumbs", + "version": "4.2.1", + "source": { + "type": "git", + "url": "https://github.com/tabuna/breadcrumbs.git", + "reference": "1d9047306f67e7fcc86fc7e608f1432f247636da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tabuna/breadcrumbs/zipball/1d9047306f67e7fcc86fc7e608f1432f247636da", + "reference": "1d9047306f67e7fcc86fc7e608f1432f247636da", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel/framework": "^10.0|^11.0", + "laravel/serializable-closure": "^1.0|^2.0", + "php": "^8.1" + }, + "require-dev": { + "orchestra/testbench": "^8.0|^9.0", + "phpunit/php-code-coverage": "^10.|^11.0", + "phpunit/phpunit": "^10.5|^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Breadcrumbs": "Tabuna\\Breadcrumbs\\Breadcrumbs" + }, + "providers": [ + "Tabuna\\Breadcrumbs\\BreadcrumbsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Tabuna\\Breadcrumbs\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexandr Chernyaev", + "email": "bliz48rus@gmail.com" + }, + { + "name": "Dwight Watson", + "email": "dwight@studentservices.com.au" + }, + { + "name": "Dave James Miller", + "email": "dave@davejamesmiller.com" + } + ], + "description": "An easy way to add breadcrumbs to your Laravel app.", + "support": { + "issues": "https://github.com/tabuna/breadcrumbs/issues", + "source": "https://github.com/tabuna/breadcrumbs/tree/4.2.1" + }, + "time": "2024-11-26T12:21:27+00:00" }, { "name": "tecnickcom/tc-lib-barcode", @@ -11032,20 +11315,21 @@ }, { "name": "tecnickcom/tcpdf", - "version": "6.7.5", + "version": "6.8.0", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36" + "reference": "14ffa0e308f5634aa2489568b4b90b24073b6731" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", - "reference": "951eabf0338ec2522bd0d5d9c79b08a3a3d36b36", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/14ffa0e308f5634aa2489568b4b90b24073b6731", + "reference": "14ffa0e308f5634aa2489568b4b90b24073b6731", "shasum": "" }, "require": { - "php": ">=5.5.0" + "ext-curl": "*", + "php": ">=7.1.0" }, "type": "library", "autoload": { @@ -11092,7 +11376,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/TCPDF/issues", - "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.5" + "source": "https://github.com/tecnickcom/TCPDF/tree/6.8.0" }, "funding": [ { @@ -11100,7 +11384,7 @@ "type": "custom" } ], - "time": "2024-04-20T17:25:10+00:00" + "time": "2024-12-23T13:34:57+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11372,13 +11656,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - }, "laravel": { "providers": [ "Unicodeveloper\\DumbPassword\\DumbPasswordServiceProvider" ] + }, + "branch-alias": { + "dev-master": "1.0-dev" } }, "autoload": { @@ -11414,23 +11698,23 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.2", + "graham-campbell/result-type": "^1.1.3", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2", + "phpoption/phpoption": "^1.9.3", "symfony/polyfill-ctype": "^1.24", "symfony/polyfill-mbstring": "^1.24", "symfony/polyfill-php80": "^1.24" @@ -11447,7 +11731,7 @@ "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "5.6-dev" @@ -11482,7 +11766,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" }, "funding": [ { @@ -11494,20 +11778,20 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:43:29+00:00" + "time": "2024-07-20T21:52:34+00:00" }, { "name": "voku/portable-ascii", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b56450eed252f6801410d810c8e1727224ae0743" + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", - "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", "shasum": "" }, "require": { @@ -11532,7 +11816,7 @@ "authors": [ { "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" + "homepage": "https://www.moelleken.org/" } ], "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", @@ -11544,7 +11828,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" }, "funding": [ { @@ -11568,7 +11852,7 @@ "type": "tidelift" } ], - "time": "2022-03-08T17:03:00+00:00" + "time": "2024-11-21T01:49:47+00:00" }, { "name": "watson/validating", @@ -11684,258 +11968,6 @@ } ], "packages-dev": [ - { - "name": "amphp/amp", - "version": "v2.6.4", - "source": { - "type": "git", - "url": "https://github.com/amphp/amp.git", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", - "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^7 | ^8 | ^9", - "react/promise": "^2", - "vimeo/psalm": "^3.12" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "files": [ - "lib/functions.php", - "lib/Internal/functions.php" - ], - "psr-4": { - "Amp\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" - }, - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Bob Weinand", - "email": "bobwei9@hotmail.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A non-blocking concurrency framework for PHP applications.", - "homepage": "https://amphp.org/amp", - "keywords": [ - "async", - "asynchronous", - "awaitable", - "concurrency", - "event", - "event-loop", - "future", - "non-blocking", - "promise" - ], - "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.4" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2024-03-21T18:52:26+00:00" - }, - { - "name": "amphp/byte-stream", - "version": "v1.8.2", - "source": { - "type": "git", - "url": "https://github.com/amphp/byte-stream.git", - "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", - "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", - "shasum": "" - }, - "require": { - "amphp/amp": "^2", - "php": ">=7.1" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1.4", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "psalm/phar": "^3.11.4" - }, - "type": "library", - "autoload": { - "files": [ - "lib/functions.php" - ], - "psr-4": { - "Amp\\ByteStream\\": "lib" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" - }, - { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "https://amphp.org/byte-stream", - "keywords": [ - "amp", - "amphp", - "async", - "io", - "non-blocking", - "stream" - ], - "support": { - "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2024-04-13T18:00:56+00:00" - }, - { - "name": "brianium/paratest", - "version": "v6.11.1", - "source": { - "type": "git", - "url": "https://github.com/paratestphp/paratest.git", - "reference": "78e297a969049ca7cc370e80ff5e102921ef39a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/78e297a969049ca7cc370e80ff5e102921ef39a3", - "reference": "78e297a969049ca7cc370e80ff5e102921ef39a3", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-simplexml": "*", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", - "jean85/pretty-package-versions": "^2.0.5", - "php": "^7.3 || ^8.0", - "phpunit/php-code-coverage": "^9.2.25", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-timer": "^5.0.3", - "phpunit/phpunit": "^9.6.4", - "sebastian/environment": "^5.1.5", - "symfony/console": "^5.4.28 || ^6.3.4 || ^7.0.0", - "symfony/process": "^5.4.28 || ^6.3.4 || ^7.0.0" - }, - "require-dev": { - "doctrine/coding-standard": "^12.0.0", - "ext-pcov": "*", - "ext-posix": "*", - "infection/infection": "^0.27.6", - "squizlabs/php_codesniffer": "^3.7.2", - "symfony/filesystem": "^5.4.25 || ^6.3.1 || ^7.0.0", - "vimeo/psalm": "^5.7.7" - }, - "bin": [ - "bin/paratest", - "bin/paratest.bat", - "bin/paratest_for_phpstorm" - ], - "type": "library", - "autoload": { - "psr-4": { - "ParaTest\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Brian Scaturro", - "email": "scaturrob@gmail.com", - "role": "Developer" - }, - { - "name": "Filippo Tessarotto", - "email": "zoeslam@gmail.com", - "role": "Developer" - } - ], - "description": "Parallel testing for PHP", - "homepage": "https://github.com/paratestphp/paratest", - "keywords": [ - "concurrent", - "parallel", - "phpunit", - "testing" - ], - "support": { - "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v6.11.1" - }, - "funding": [ - { - "url": "https://github.com/sponsors/Slamdunk", - "type": "github" - }, - { - "url": "https://paypal.me/filippotessarotto", - "type": "paypal" - } - ], - "time": "2024-03-13T06:54:29+00:00" - }, { "name": "clue/ndjson-react", "version": "v1.3.0", @@ -12002,24 +12034,24 @@ }, { "name": "cmgmyr/phploc", - "version": "8.0.3", + "version": "8.0.4", "source": { "type": "git", "url": "https://github.com/cmgmyr/phploc.git", - "reference": "e61d4729df46c5920ab61973bfa3f70f81a70b5f" + "reference": "b0c4ec71f40ef84c9893e1a7212a72e1098b90f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/e61d4729df46c5920ab61973bfa3f70f81a70b5f", - "reference": "e61d4729df46c5920ab61973bfa3f70f81a70b5f", + "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/b0c4ec71f40ef84c9893e1a7212a72e1098b90f7", + "reference": "b0c4ec71f40ef84c9893e1a7212a72e1098b90f7", "shasum": "" }, "require": { "ext-dom": "*", "ext-json": "*", "php": "^7.4 || ^8.0", - "phpunit/php-file-iterator": "^3.0|^4.0", - "sebastian/cli-parser": "^1.0|^2.0" + "phpunit/php-file-iterator": "^3.0|^4.0|^5.0", + "sebastian/cli-parser": "^1.0|^2.0|^3.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", @@ -12055,7 +12087,7 @@ "homepage": "https://github.com/cmgmyr/phploc", "support": { "issues": "https://github.com/cmgmyr/phploc/issues", - "source": "https://github.com/cmgmyr/phploc/tree/8.0.3" + "source": "https://github.com/cmgmyr/phploc/tree/8.0.4" }, "funding": [ { @@ -12063,32 +12095,40 @@ "type": "github" } ], - "time": "2023-08-05T16:49:39+00:00" + "time": "2024-10-31T19:26:53+00:00" }, { "name": "composer/pcre", - "version": "3.1.4", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "04229f163664973f68f38f6f73d917799168ef24" + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24", - "reference": "04229f163664973f68f38f6f73d917799168ef24", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, "branch-alias": { "dev-main": "3.x-dev" } @@ -12118,7 +12158,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.4" + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { @@ -12134,28 +12174,28 @@ "type": "tidelift" } ], - "time": "2024-05-27T13:40:54+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.3", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { @@ -12199,7 +12239,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { @@ -12215,7 +12255,7 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { "name": "composer/xdebug-handler", @@ -12361,43 +12401,6 @@ }, "time": "2023-01-05T11:28:13+00:00" }, - { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", - "source": { - "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "XdgBaseDir\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "implementation of xdg base directory specification for php", - "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" - }, - "time": "2019-12-04T15:06:13+00:00" - }, { "name": "evenement/evenement", "version": "v3.0.2", @@ -12445,182 +12448,18 @@ }, "time": "2023-08-08T05:53:35+00:00" }, - { - "name": "fakerphp/faker", - "version": "v1.23.1", - "source": { - "type": "git", - "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "psr/container": "^1.0 || ^2.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "conflict": { - "fzaninotto/faker": "*" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "doctrine/persistence": "^1.3 || ^2.0", - "ext-intl": "*", - "phpunit/phpunit": "^9.5.26", - "symfony/phpunit-bridge": "^5.4.16" - }, - "suggest": { - "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", - "ext-curl": "Required by Faker\\Provider\\Image to download images.", - "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", - "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", - "ext-mbstring": "Required for multibyte Unicode string functionality." - }, - "type": "library", - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "support": { - "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" - }, - "time": "2024-01-02T13:46:09+00:00" - }, - { - "name": "felixfbecker/advanced-json-rpc", - "version": "v3.2.1", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-advanced-json-rpc.git", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-advanced-json-rpc/zipball/b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "reference": "b5f37dbff9a8ad360ca341f3240dc1c168b45447", - "shasum": "" - }, - "require": { - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "php": "^7.1 || ^8.0", - "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^7.0 || ^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "AdvancedJsonRpc\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "A more advanced JSONRPC implementation", - "support": { - "issues": "https://github.com/felixfbecker/php-advanced-json-rpc/issues", - "source": "https://github.com/felixfbecker/php-advanced-json-rpc/tree/v3.2.1" - }, - "time": "2021-06-11T22:34:44+00:00" - }, - { - "name": "felixfbecker/language-server-protocol", - "version": "v1.5.2", - "source": { - "type": "git", - "url": "https://github.com/felixfbecker/php-language-server-protocol.git", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842", - "reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpstan/phpstan": "*", - "squizlabs/php_codesniffer": "^3.1", - "vimeo/psalm": "^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "LanguageServerProtocol\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "ISC" - ], - "authors": [ - { - "name": "Felix Becker", - "email": "felix.b@outlook.com" - } - ], - "description": "PHP classes for the Language Server Protocol", - "keywords": [ - "language", - "microsoft", - "php", - "server" - ], - "support": { - "issues": "https://github.com/felixfbecker/php-language-server-protocol/issues", - "source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2" - }, - "time": "2022-03-02T22:36:06+00:00" - }, { "name": "fidry/cpu-core-counter", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + "reference": "8520451a140d3f46ac33042715115e290cf5785f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", "shasum": "" }, "require": { @@ -12660,7 +12499,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" }, "funding": [ { @@ -12668,20 +12507,20 @@ "type": "github" } ], - "time": "2024-02-07T09:43:46+00:00" + "time": "2024-08-06T10:04:20+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.59.3", + "version": "v3.68.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29" + "reference": "b9db2b2ea3cdba7201067acee46f984ef2397cff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/30ba9ecc2b0e5205e578fe29973c15653d9bfd29", - "reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b9db2b2ea3cdba7201067acee46f984ef2397cff", + "reference": "b9db2b2ea3cdba7201067acee46f984ef2397cff", "shasum": "" }, "require": { @@ -12691,38 +12530,38 @@ "ext-filter": "*", "ext-json": "*", "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.0", + "fidry/cpu-core-counter": "^1.2", "php": "^7.4 || ^8.0", "react/child-process": "^0.6.5", "react/event-loop": "^1.0", "react/promise": "^2.0 || ^3.0", "react/socket": "^1.0", "react/stream": "^1.0", - "sebastian/diff": "^4.0 || ^5.0 || ^6.0", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", - "symfony/finder": "^5.4 || ^6.0 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", - "symfony/polyfill-mbstring": "^1.28", - "symfony/polyfill-php80": "^1.28", - "symfony/polyfill-php81": "^1.28", - "symfony/process": "^5.4 || ^6.0 || ^7.0", - "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + "sebastian/diff": "^4.0 || ^5.1 || ^6.0", + "symfony/console": "^5.4 || ^6.4 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", + "symfony/finder": "^5.4 || ^6.4 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", + "symfony/polyfill-mbstring": "^1.31", + "symfony/polyfill-php80": "^1.31", + "symfony/polyfill-php81": "^1.31", + "symfony/process": "^5.4 || ^6.4 || ^7.2", + "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" }, "require-dev": { - "facile-it/paraunit": "^1.3 || ^2.3", - "infection/infection": "^0.29.5", - "justinrainbow/json-schema": "^5.2", + "facile-it/paraunit": "^1.3.1 || ^2.4", + "infection/infection": "^0.29.8", + "justinrainbow/json-schema": "^5.3 || ^6.0", "keradus/cli-executor": "^2.1", - "mikey179/vfsstream": "^1.6.11", + "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.7", "php-cs-fixer/accessible-object": "^1.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", - "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", - "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + "phpunit/phpunit": "^9.6.22 || ^10.5.40 || ^11.5.2", + "symfony/var-dumper": "^5.4.48 || ^6.4.15 || ^7.2.0", + "symfony/yaml": "^5.4.45 || ^6.4.13 || ^7.2.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -12763,7 +12602,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.59.3" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.68.1" }, "funding": [ { @@ -12771,7 +12610,7 @@ "type": "github" } ], - "time": "2024-06-16T14:17:03+00:00" + "time": "2025-01-17T09:20:36+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -12824,81 +12663,22 @@ }, "time": "2020-07-09T08:09:16+00:00" }, - { - "name": "jean85/pretty-package-versions", - "version": "2.0.6", - "source": { - "type": "git", - "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2.0.0", - "php": "^7.1|^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.2", - "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Jean85\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Alessandro Lai", - "email": "alessandro.lai85@gmail.com" - } - ], - "description": "A library to get pretty versions strings of installed dependencies", - "keywords": [ - "composer", - "package", - "release", - "versions" - ], - "support": { - "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" - }, - "time": "2024-03-08T09:58:59+00:00" - }, { "name": "justinrainbow/json-schema", - "version": "v5.2.13", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", @@ -12909,11 +12689,6 @@ "bin/validate-json" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" @@ -12949,42 +12724,45 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/v5.2.13" + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0" }, - "time": "2023-09-26T02:20:38+00:00" + "time": "2024-07-06T21:00:26+00:00" }, { "name": "larastan/larastan", - "version": "v2.9.7", + "version": "v2.9.12", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "5c805f636095cc2e0b659e3954775cf8f1dad1bb" + "reference": "19012b39fbe4dede43dbe0c126d9681827a5e908" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/5c805f636095cc2e0b659e3954775cf8f1dad1bb", - "reference": "5c805f636095cc2e0b659e3954775cf8f1dad1bb", + "url": "https://api.github.com/repos/larastan/larastan/zipball/19012b39fbe4dede43dbe0c126d9681827a5e908", + "reference": "19012b39fbe4dede43dbe0c126d9681827a5e908", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.16", "php": "^8.0.2", "phpmyadmin/sql-parser": "^5.9.0", - "phpstan/phpstan": "^1.11.1" + "phpstan/phpstan": "^1.12.11" }, "require-dev": { "doctrine/coding-standard": "^12.0", + "laravel/framework": "^9.52.16 || ^10.28.0 || ^11.16", + "mockery/mockery": "^1.5.1", "nikic/php-parser": "^4.19.1", "orchestra/canvas": "^7.11.1 || ^8.11.0 || ^9.0.2", - "orchestra/testbench": "^7.33.0 || ^8.13.0 || ^9.0.3", + "orchestra/testbench-core": "^7.33.0 || ^8.13.0 || ^9.0.9", + "phpstan/phpstan-deprecation-rules": "^1.2", "phpunit/phpunit": "^9.6.13 || ^10.5.16" }, "suggest": { @@ -12992,13 +12770,13 @@ }, "type": "phpstan-extension", "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - }, "phpstan": { "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-master": "2.0-dev" } }, "autoload": { @@ -13020,7 +12798,7 @@ "email": "enunomaduro@gmail.com" } ], - "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan wrapper for Laravel", + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel", "keywords": [ "PHPStan", "code analyse", @@ -13033,40 +12811,28 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v2.9.7" + "source": "https://github.com/larastan/larastan/tree/v2.9.12" }, "funding": [ - { - "url": "https://www.paypal.com/paypalme/enunomaduro", - "type": "custom" - }, { "url": "https://github.com/canvural", "type": "github" - }, - { - "url": "https://github.com/nunomaduro", - "type": "github" - }, - { - "url": "https://www.patreon.com/nunomaduro", - "type": "patreon" } ], - "time": "2024-05-27T18:33:26+00:00" + "time": "2024-11-26T23:09:02+00:00" }, { "name": "league/container", - "version": "4.2.2", + "version": "4.2.4", "source": { "type": "git", "url": "https://github.com/thephpleague/container.git", - "reference": "ff346319ca1ff0e78277dc2311a42107cc1aab88" + "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/ff346319ca1ff0e78277dc2311a42107cc1aab88", - "reference": "ff346319ca1ff0e78277dc2311a42107cc1aab88", + "url": "https://api.github.com/repos/thephpleague/container/zipball/7ea728b013b9a156c409c6f0fc3624071b742dec", + "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec", "shasum": "" }, "require": { @@ -13091,11 +12857,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev", - "dev-4.x": "4.x-dev", - "dev-3.x": "3.x-dev", + "dev-1.x": "1.x-dev", "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" + "dev-3.x": "3.x-dev", + "dev-4.x": "4.x-dev", + "dev-master": "4.x-dev" } }, "autoload": { @@ -13127,7 +12893,7 @@ ], "support": { "issues": "https://github.com/thephpleague/container/issues", - "source": "https://github.com/thephpleague/container/tree/4.2.2" + "source": "https://github.com/thephpleague/container/tree/4.2.4" }, "funding": [ { @@ -13135,7 +12901,7 @@ "type": "github" } ], - "time": "2024-03-13T13:12:53+00:00" + "time": "2024-11-10T12:42:13+00:00" }, { "name": "mockery/mockery", @@ -13222,16 +12988,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -13270,7 +13036,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -13278,71 +13044,20 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" - }, - { - "name": "netresearch/jsonmapper", - "version": "v4.4.1", - "source": { - "type": "git", - "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "reference": "132c75c7dd83e45353ebb9c6c9f591952995bbf0", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=7.1" - }, - "require-dev": { - "phpunit/phpunit": "~7.5 || ~8.0 || ~9.0 || ~10.0", - "squizlabs/php_codesniffer": "~3.5" - }, - "type": "library", - "autoload": { - "psr-0": { - "JsonMapper": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Christian Weiske", - "email": "cweiske@cweiske.de", - "homepage": "http://github.com/cweiske/jsonmapper/", - "role": "Developer" - } - ], - "description": "Map nested JSON structures onto PHP classes", - "support": { - "email": "cweiske@cweiske.de", - "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.4.1" - }, - "time": "2024-01-31T06:18:54+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nunomaduro/phpinsights", - "version": "v2.11.0", + "version": "v2.12.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/phpinsights.git", - "reference": "f476219759a61aad988641476259465c77203383" + "reference": "5c12a8d626712de6db5e6d2db52b1eb4e9596650" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/phpinsights/zipball/f476219759a61aad988641476259465c77203383", - "reference": "f476219759a61aad988641476259465c77203383", + "url": "https://api.github.com/repos/nunomaduro/phpinsights/zipball/5c12a8d626712de6db5e6d2db52b1eb4e9596650", + "reference": "5c12a8d626712de6db5e6d2db52b1eb4e9596650", "shasum": "" }, "require": { @@ -13359,7 +13074,7 @@ "php-parallel-lint/php-parallel-lint": "^1.3.2", "psr/container": "^1.0|^2.0.2", "psr/simple-cache": "^1.0|^2.0|^3.0", - "sebastian/diff": "^4.0|^5.0.3", + "sebastian/diff": "^4.0|^5.0.3|^6.0", "slevomat/coding-standard": "^8.14.1", "squizlabs/php_codesniffer": "^3.7.2", "symfony/cache": "^5.4|^6.0|^7.0", @@ -13419,7 +13134,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/phpinsights/issues", - "source": "https://github.com/nunomaduro/phpinsights/tree/v2.11.0" + "source": "https://github.com/nunomaduro/phpinsights/tree/v2.12.0" }, "funding": [ { @@ -13435,7 +13150,7 @@ "type": "github" } ], - "time": "2023-11-30T10:54:50+00:00" + "time": "2024-11-11T14:42:55+00:00" }, { "name": "phar-io/manifest", @@ -13557,16 +13272,16 @@ }, { "name": "php-mock/php-mock", - "version": "2.5.0", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock.git", - "reference": "fff1a621ebe54100fa3bd852e7be57773a0c0127" + "reference": "8f58972dce4de5a804dc0459383a11bc651416cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/fff1a621ebe54100fa3bd852e7be57773a0c0127", - "reference": "fff1a621ebe54100fa3bd852e7be57773a0c0127", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/8f58972dce4de5a804dc0459383a11bc651416cf", + "reference": "8f58972dce4de5a804dc0459383a11bc651416cf", "shasum": "" }, "require": { @@ -13621,7 +13336,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock/issues", - "source": "https://github.com/php-mock/php-mock/tree/2.5.0" + "source": "https://github.com/php-mock/php-mock/tree/2.5.1" }, "funding": [ { @@ -13629,7 +13344,7 @@ "type": "github" } ], - "time": "2024-02-10T21:07:01+00:00" + "time": "2024-12-07T20:52:37+00:00" }, { "name": "php-mock/php-mock-integration", @@ -13825,16 +13540,16 @@ }, { "name": "phpmyadmin/sql-parser", - "version": "5.9.0", + "version": "5.10.3", "source": { "type": "git", "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "011fa18a4e55591fac6545a821921dd1d61c6984" + "reference": "5346664973d10cf1abff20837fb1183f3c11a055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/011fa18a4e55591fac6545a821921dd1d61c6984", - "reference": "011fa18a4e55591fac6545a821921dd1d61c6984", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/5346664973d10cf1abff20837fb1183f3c11a055", + "reference": "5346664973d10cf1abff20837fb1183f3c11a055", "shasum": "" }, "require": { @@ -13852,8 +13567,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.9.12", "phpstan/phpstan-phpunit": "^1.3.3", - "phpunit/php-code-coverage": "*", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^8.5 || ^9.6", "psalm/plugin-phpunit": "^0.16.1", "vimeo/psalm": "^4.11", "zumba/json-serializer": "~3.0.2" @@ -13909,20 +13623,20 @@ "type": "other" } ], - "time": "2024-01-20T20:34:02+00:00" + "time": "2025-01-19T04:14:02+00:00" }, { "name": "phpstan/phpstan", - "version": "1.11.5", + "version": "1.12.15", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" + "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c91d4e8bc056f46cf653656e6f71004b254574d1", + "reference": "c91d4e8bc056f46cf653656e6f71004b254574d1", "shasum": "" }, "require": { @@ -13967,39 +13681,39 @@ "type": "github" } ], - "time": "2024-06-17T15:10:54+00:00" + "time": "2025-01-05T16:40:22+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.31", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", - "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "nikic/php-parser": "^4.19.1 || ^5.1.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -14008,7 +13722,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -14037,7 +13751,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -14045,32 +13759,32 @@ "type": "github" } ], - "time": "2024-03-02T06:37:42+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -14097,7 +13811,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" }, "funding": [ { @@ -14105,28 +13820,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2023-08-31T06:24:48+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-pcntl": "*" @@ -14134,7 +13849,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -14160,7 +13875,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" }, "funding": [ { @@ -14168,32 +13883,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -14219,7 +13934,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" }, "funding": [ { @@ -14227,32 +13943,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2023-08-31T14:07:24+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -14278,7 +13994,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" }, "funding": [ { @@ -14286,54 +14002,52 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2023-02-03T06:57:52+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.19", + "version": "10.5.41", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + "reference": "e76586fa3d49714f230221734b44892e384109d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e76586fa3d49714f230221734b44892e384109d7", + "reference": "e76586fa3d49714f230221734b44892e384109d7", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "myclabs/deep-copy": "^1.12.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.16", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.3", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -14341,7 +14055,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "10.5-dev" } }, "autoload": { @@ -14373,7 +14087,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.41" }, "funding": [ { @@ -14389,7 +14103,7 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:35:58+00:00" + "time": "2025-01-13T09:33:05+00:00" }, { "name": "react/cache", @@ -14465,33 +14179,33 @@ }, { "name": "react/child-process", - "version": "v0.6.5", + "version": "v0.6.6", "source": { "type": "git", "url": "https://github.com/reactphp/child-process.git", - "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43" + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", - "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", + "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", "react/event-loop": "^1.2", - "react/stream": "^1.2" + "react/stream": "^1.4" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", - "react/socket": "^1.8", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/socket": "^1.16", "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" }, "type": "library", "autoload": { "psr-4": { - "React\\ChildProcess\\": "src" + "React\\ChildProcess\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14528,19 +14242,15 @@ ], "support": { "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.5" + "source": "https://github.com/reactphp/child-process/tree/v0.6.6" }, "funding": [ { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" + "url": "https://opencollective.com/reactphp", + "type": "open_collective" } ], - "time": "2022-09-16T13:41:56+00:00" + "time": "2025-01-01T16:37:48+00:00" }, { "name": "react/dns", @@ -14765,31 +14475,31 @@ }, { "name": "react/socket", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/dns": "^1.11", + "react/dns": "^1.13", "react/event-loop": "^1.2", - "react/promise": "^3 || ^2.6 || ^1.2.1", - "react/stream": "^1.2" + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, "require-dev": { "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4 || ^3 || ^2", + "react/async": "^4.3 || ^3.3 || ^2", "react/promise-stream": "^1.4", - "react/promise-timer": "^1.10" + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { @@ -14833,7 +14543,7 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.15.0" + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, "funding": [ { @@ -14841,7 +14551,7 @@ "type": "open_collective" } ], - "time": "2023-12-15T11:02:10+00:00" + "time": "2024-07-26T10:38:09+00:00" }, { "name": "react/stream", @@ -14923,28 +14633,28 @@ }, { "name": "sebastian/cli-parser", - "version": "1.0.2", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", - "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -14967,7 +14677,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" }, "funding": [ { @@ -14975,32 +14686,32 @@ "type": "github" } ], - "time": "2024-03-02T06:27:43+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -15023,7 +14734,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" }, "funding": [ { @@ -15031,435 +14742,27 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "support": { - "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-09-28T05:30:19+00:00" - }, - { - "name": "sebastian/complexity", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", - "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-22T06:19:30+00:00" - }, - { - "name": "sebastian/environment", - "version": "5.1.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:03:51+00:00" - }, - { - "name": "sebastian/global-state", - "version": "5.0.7", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2024-03-02T06:35:11+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "1.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-12-22T06:20:34+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", - "shasum": "" - }, - "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:12:34+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:14:26+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { @@ -15482,10 +14785,11 @@ "email": "sebastian@phpunit.de" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" }, "funding": [ { @@ -15493,32 +14797,386 @@ "type": "github" } ], - "time": "2024-03-14T16:00:52+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { - "name": "sebastian/type", - "version": "3.2.1", + "name": "sebastian/complexity", + "version": "3.2.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "php": ">=7.3" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" } }, "autoload": { @@ -15541,7 +15199,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { @@ -15549,29 +15207,29 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -15594,7 +15252,7 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -15602,7 +15260,7 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { "name": "slevomat/coding-standard", @@ -15669,86 +15327,18 @@ ], "time": "2024-03-09T15:20:58+00:00" }, - { - "name": "spatie/array-to-xml", - "version": "3.3.0", - "source": { - "type": "git", - "url": "https://github.com/spatie/array-to-xml.git", - "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/f56b220fe2db1ade4c88098d83413ebdfc3bf876", - "reference": "f56b220fe2db1ade4c88098d83413ebdfc3bf876", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "php": "^8.0" - }, - "require-dev": { - "mockery/mockery": "^1.2", - "pestphp/pest": "^1.21", - "spatie/pest-plugin-snapshots": "^1.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Spatie\\ArrayToXml\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Freek Van der Herten", - "email": "freek@spatie.be", - "homepage": "https://freek.dev", - "role": "Developer" - } - ], - "description": "Convert an array to xml", - "homepage": "https://github.com/spatie/array-to-xml", - "keywords": [ - "array", - "convert", - "xml" - ], - "support": { - "source": "https://github.com/spatie/array-to-xml/tree/3.3.0" - }, - "funding": [ - { - "url": "https://spatie.be/open-source/support-us", - "type": "custom" - }, - { - "url": "https://github.com/spatie", - "type": "github" - } - ], - "time": "2024-05-01T10:20:27+00:00" - }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.1", + "version": "3.11.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", - "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079", + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079", "shasum": "" }, "require": { @@ -15815,35 +15405,36 @@ "type": "open_collective" } ], - "time": "2024-05-22T21:24:41+00:00" + "time": "2024-12-11T16:04:26+00:00" }, { "name": "symfony/cache", - "version": "v6.4.8", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "287142df5579ce223c485b3872df3efae8390984" + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/287142df5579ce223c485b3872df3efae8390984", - "reference": "287142df5579ce223c485b3872df3efae8390984", + "url": "https://api.github.com/repos/symfony/cache/zipball/e7e983596b744c4539f31e79b0350a6cf5878a20", + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.3.6|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" }, "provide": { "psr/cache-implementation": "2.0|3.0", @@ -15852,15 +15443,16 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -15895,7 +15487,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.8" + "source": "https://github.com/symfony/cache/tree/v7.2.1" }, "funding": [ { @@ -15911,20 +15503,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-12-07T08:08:50+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197" + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/df6a1a44c890faded49a5fca33c2d5c5fd3c2197", - "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", "shasum": "" }, "require": { @@ -15933,12 +15525,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -15971,7 +15563,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" }, "funding": [ { @@ -15987,7 +15579,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/dom-crawler", @@ -16065,25 +15657,25 @@ }, { "name": "symfony/filesystem", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3" + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/4d37529150e7081c51b3c5d5718c55a04a9503f3", - "reference": "4d37529150e7081c51b3c5d5718c55a04a9503f3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, "require-dev": { - "symfony/process": "^5.4|^6.4|^7.0" + "symfony/process": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -16111,7 +15703,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.8" + "source": "https://github.com/symfony/filesystem/tree/v7.2.0" }, "funding": [ { @@ -16127,32 +15719,33 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-25T15:15:23+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.8", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05" + "reference": "339ba21476eb184290361542f732ad12c97591ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05", - "reference": "61faba993e620fc22d4f0ab3b6bcf8fbb0d44b05", + "url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec", + "reference": "339ba21476eb184290361542f732ad12c97591ec", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3.4.1", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { + "amphp/amp": "<2.5", "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.3" + "symfony/http-foundation": "<6.4" }, "provide": { "php-http/async-client-implementation": "*", @@ -16161,19 +15754,20 @@ "symfony/http-client-implementation": "3.0" }, "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", "amphp/socket": "^1.1", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -16204,7 +15798,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.8" + "source": "https://github.com/symfony/http-client/tree/v7.2.2" }, "funding": [ { @@ -16220,20 +15814,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-12-30T18:35:15+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.0", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "20414d96f391677bf80078aa55baece78b82647d" + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", - "reference": "20414d96f391677bf80078aa55baece78b82647d", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", "shasum": "" }, "require": { @@ -16241,12 +15835,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -16282,7 +15876,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" }, "funding": [ { @@ -16298,24 +15892,24 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-12-07T08:49:48+00:00" }, { "name": "symfony/options-resolver", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b" + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22ab9e9101ab18de37839074f8a1197f55590c1b", - "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -16349,7 +15943,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.4.8" + "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" }, "funding": [ { @@ -16365,30 +15959,30 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-20T11:17:29+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -16425,7 +16019,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" }, "funding": [ { @@ -16441,24 +16035,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/stopwatch", - "version": "v6.4.8", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "63e069eb616049632cde9674c46957819454b8aa" + "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/63e069eb616049632cde9674c46957819454b8aa", - "reference": "63e069eb616049632cde9674c46957819454b8aa", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e46690d5b9d7164a6d061cab1e8d46141b9f49df", + "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -16487,7 +16081,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.4.8" + "source": "https://github.com/symfony/stopwatch/tree/v7.2.2" }, "funding": [ { @@ -16503,30 +16097,29 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-12-18T14:28:33+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "792ca836f99b340f2e9ca9497c7953948c49a504" + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/792ca836f99b340f2e9ca9497c7953948c49a504", - "reference": "792ca836f99b340f2e9ca9497c7953948c49a504", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.2" }, "require-dev": { "symfony/property-access": "^6.4|^7.0", "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -16564,7 +16157,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.8" + "source": "https://github.com/symfony/var-exporter/tree/v7.2.0" }, "funding": [ { @@ -16580,7 +16173,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-10-18T07:58:17+00:00" }, { "name": "theseer/tokenizer", @@ -16631,133 +16224,25 @@ } ], "time": "2024-03-03T12:36:25+00:00" - }, - { - "name": "vimeo/psalm", - "version": "5.25.0", - "source": { - "type": "git", - "url": "https://github.com/vimeo/psalm.git", - "reference": "01a8eb06b9e9cc6cfb6a320bf9fb14331919d505" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/01a8eb06b9e9cc6cfb6a320bf9fb14331919d505", - "reference": "01a8eb06b9e9cc6cfb6a320bf9fb14331919d505", - "shasum": "" - }, - "require": { - "amphp/amp": "^2.4.2", - "amphp/byte-stream": "^1.5", - "composer-runtime-api": "^2", - "composer/semver": "^1.4 || ^2.0 || ^3.0", - "composer/xdebug-handler": "^2.0 || ^3.0", - "dnoegel/php-xdg-base-dir": "^0.1.1", - "ext-ctype": "*", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-tokenizer": "*", - "felixfbecker/advanced-json-rpc": "^3.1", - "felixfbecker/language-server-protocol": "^1.5.2", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", - "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.16", - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", - "sebastian/diff": "^4.0 || ^5.0 || ^6.0", - "spatie/array-to-xml": "^2.17.0 || ^3.0", - "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" - }, - "conflict": { - "nikic/php-parser": "4.17.0" - }, - "provide": { - "psalm/psalm": "self.version" - }, - "require-dev": { - "amphp/phpunit-util": "^2.0", - "bamarni/composer-bin-plugin": "^1.4", - "brianium/paratest": "^6.9", - "ext-curl": "*", - "mockery/mockery": "^1.5", - "nunomaduro/mock-final-classes": "^1.1", - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpstan/phpdoc-parser": "^1.6", - "phpunit/phpunit": "^9.6", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.6", - "symfony/process": "^4.4 || ^5.0 || ^6.0 || ^7.0" - }, - "suggest": { - "ext-curl": "In order to send data to shepherd", - "ext-igbinary": "^2.0.5 is required, used to serialize caching data" - }, - "bin": [ - "psalm", - "psalm-language-server", - "psalm-plugin", - "psalm-refactor", - "psalter" - ], - "type": "project", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev", - "dev-4.x": "4.x-dev", - "dev-3.x": "3.x-dev", - "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psalm\\": "src/Psalm/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Matthew Brown" - } - ], - "description": "A static analysis tool for finding errors in PHP applications", - "keywords": [ - "code", - "inspection", - "php", - "static analysis" - ], - "support": { - "docs": "https://psalm.dev/docs", - "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm" - }, - "time": "2024-06-16T15:08:35+00:00" } ], "aliases": [], "minimum-stability": "stable", "stability-flags": { - "arietimmerman/laravel-scim-server": 20 + "arietimmerman/laravel-scim-server": 20, + "laravelcollective/html": 20 }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.1", + "php": "^8.2", "ext-curl": "*", "ext-fileinfo": "*", + "ext-iconv": "*", "ext-json": "*", "ext-mbstring": "*", "ext-pdo": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/app.php b/config/app.php index f17b856d05..9de088089f 100755 --- a/config/app.php +++ b/config/app.php @@ -242,7 +242,7 @@ return [ | */ - 'min_php' => '7.4.0', + 'min_php' => '8.2.0', /* @@ -280,7 +280,6 @@ return [ Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, -// Illuminate\Translation\TranslationServiceProvider::class, //replaced on next line App\Providers\SnipeTranslationServiceProvider::class, //we REPLACE the default Laravel translator with our own Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, @@ -311,10 +310,13 @@ return [ App\Providers\ValidationServiceProvider::class, /* - * Custom service provider + * Custom Service Providers... */ + App\Providers\BladeServiceProvider::class, + App\Providers\LivewireServiceProvider::class, App\Providers\MacroServiceProvider::class, App\Providers\SamlServiceProvider::class, + App\Providers\BreadcrumbsServiceProvider::class, ], @@ -339,7 +341,7 @@ return [ 'Cache' => Illuminate\Support\Facades\Cache::class, 'Config' => Illuminate\Support\Facades\Config::class, 'Cookie' => Illuminate\Support\Facades\Cookie::class, - 'Crypt' => Illuminate\Support\FacadesCrypt::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, 'Date' => Illuminate\Support\Facades\Date::class, 'DB' => Illuminate\Support\Facades\DB::class, 'Eloquent' => Illuminate\Database\Eloquent\Model::class, @@ -370,7 +372,9 @@ return [ 'Google2FA' => PragmaRX\Google2FALaravel\Facade::class, 'Image' => Intervention\Image\ImageServiceProvider::class, 'Carbon' => Carbon\Carbon::class, - 'Helper' => App\Helpers\Helper::class, // makes it much easier to use 'Helper::blah' in blades (which is where we usually use this) + 'Helper' => App\Helpers\Helper::class, + 'StorageHelper' => App\Helpers\StorageHelper::class, + 'Icon' => App\Helpers\IconHelper::class, 'Socialite' => Laravel\Socialite\Facades\Socialite::class, @@ -426,5 +430,5 @@ return [ */ 'escape_formulas' => env('CSV_ESCAPE_FORMULAS', true), - + ]; diff --git a/config/backup.php b/config/backup.php index 10ebdde038..5641134be8 100644 --- a/config/backup.php +++ b/config/backup.php @@ -139,7 +139,7 @@ return [ 'to' => env('MAIL_BACKUP_NOTIFICATION_ADDRESS', null), 'from' => [ - 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'address' => env('MAIL_FROM_ADDR', 'hello@example.com'), 'name' => env('MAIL_FROM_NAME', 'Example'), ], ], @@ -207,34 +207,36 @@ return [ /* * The number of days for which backups must be kept. */ - 'keep_all_backups_for_days' => 7, + 'keep_all_backups_for_days' => env('ALL_BACKUP_KEEP_DAYS', 7), /* * The number of days for which daily backups must be kept. */ - 'keep_daily_backups_for_days' => 16, + 'keep_daily_backups_for_days' => env('DAILY_BACKUP_KEEP_DAYS', 16), /* * The number of weeks for which one weekly backup must be kept. */ - 'keep_weekly_backups_for_weeks' => 8, + 'keep_weekly_backups_for_weeks' => env('WEEKLY_BACKUP_KEEP_WEEKS', 8), /* * The number of months for which one monthly backup must be kept. */ - 'keep_monthly_backups_for_months' => 4, + 'keep_monthly_backups_for_months' => env('MONTHLY_BACKUP_KEEP_MONTHS', 4), /* * The number of years for which one yearly backup must be kept. */ - 'keep_yearly_backups_for_years' => 2, + 'keep_yearly_backups_for_years' => env('YEARLY_BACKUP_KEEP_YEARS', 2), /* * After cleaning up the backups remove the oldest backup until * this amount of megabytes has been reached. */ - 'delete_oldest_backups_when_using_more_megabytes_than' => 5000, + 'delete_oldest_backups_when_using_more_megabytes_than' => env('BACKUP_PURGE_OLDEST_AT_MEGS', 5000), ], ], + 'sanitize_by_default' => env('DB_SANITIZE_BY_DEFAULT', false), + ]; diff --git a/config/database.php b/config/database.php index de3c40bb25..fd7ff6aa0f 100755 --- a/config/database.php +++ b/config/database.php @@ -8,6 +8,22 @@ | be modified directly. */ + +// This is used by the mysql dump options in spatie backup +$dump_options = [ + 'dump_binary_path' => env('DB_DUMP_PATH', '/usr/local/bin'), // only the path, so without 'mysqldump' + 'use_single_transaction' => false, + 'timeout' => 60 * 5, // 5 minute timeout + //'exclude_tables' => ['table1', 'table2'], + //'add_extra_option' => '--optionname=optionvalue', +]; + +// Some versions of mysql do not support the --skip-ssl option and will fail if it is +if (env('DB_DUMP_SKIP_SSL') == 'true') { + $dump_options['skip_ssl'] = true; +} + + return [ /* @@ -79,14 +95,7 @@ return [ 'strict' => false, 'engine' => 'InnoDB', 'unix_socket' => env('DB_SOCKET', ''), - 'dump' => [ - 'dump_binary_path' => env('DB_DUMP_PATH', '/usr/local/bin'), // only the path, so without 'mysqldump' - 'use_single_transaction' => false, - 'timeout' => 60 * 5, // 5 minute timeout - //'exclude_tables' => ['table1', 'table2'], - //'add_extra_option' => '--optionname=optionvalue', - ], - + 'dump' => $dump_options, 'dump_command_timeout' => 60 * 5, // 5 minute timeout 'dump_using_single_transaction' => true, // perform dump using a single transaction 'options' => (env('DB_SSL')) ? ((env('DB_SSL_IS_PAAS')) ? [ diff --git a/config/livewire.php b/config/livewire.php index 8cc482183b..d9db384bd6 100644 --- a/config/livewire.php +++ b/config/livewire.php @@ -157,4 +157,21 @@ return [ */ 'pagination_theme' => 'tailwind', + + /* + |--------------------------------------------------------------------------- + | URL Prefix + |--------------------------------------------------------------------------- + | + | By default, Livewire sends network requests to {app.com}/livewire/update + | while javascript assets are served via {app.com}/livewire/livewire.js + | If you need to adjust the prefix of those urls you can do it below. + | + | Defining a prefix will result in the following url + | {app.com}/{prefix}/livewire/{update|livewire.js} + | Note: do not include the leading or trailing / + | + */ + + 'url_prefix' => env('LIVEWIRE_URL_PREFIX', null), ]; diff --git a/config/logging.php b/config/logging.php index afaf09ed27..c5991145a3 100644 --- a/config/logging.php +++ b/config/logging.php @@ -140,5 +140,11 @@ if ((env('APP_ENV')=='production') && (env('ROLLBAR_TOKEN'))) { } +if (env('LOG_DEPRECATIONS')=='true') { + $config['channels']['deprecations'] = [ + 'driver' => 'single', + 'path' => storage_path('logs/deprecations.log') + ]; +} return $config; \ No newline at end of file diff --git a/config/passport.php b/config/passport.php index d410cfe20e..8de731b49a 100644 --- a/config/passport.php +++ b/config/passport.php @@ -14,4 +14,5 @@ return [ 'private_key' => env('PASSPORT_PRIVATE_KEY'), 'public_key' => env('PASSPORT_PUBLIC_KEY'), 'expiration_years' => env('API_TOKEN_EXPIRATION_YEARS', 20), + 'cookie_name' => env('PASSPORT_COOKIE_NAME', 'snipeit_passport_token'), ]; diff --git a/config/services.php b/config/services.php index 21acb6778a..07b1c1069f 100644 --- a/config/services.php +++ b/config/services.php @@ -44,12 +44,6 @@ return [ 'secret' => env('STRIPE_SECRET'), ], - 'baremetrics' => [ - 'enabled' => env('ENABLE_BMPAY', false), - 'app_key' => env('BMPAY_PUBLIC_KEY', null), - 'stripe_id' => env('BMPAY_STRIPE_ID', null), - ], - 'google' => [ 'maps_api_key' => env('GOOGLE_MAPS_API'), ], diff --git a/config/session.php b/config/session.php index 5c6cb27a9f..10d0d1acfb 100644 --- a/config/session.php +++ b/config/session.php @@ -172,7 +172,7 @@ return [ | More info: https://bootstrap-table.com/docs/extensions/cookie/#cookiestorage */ - 'bs_table_storage' => env('BS_TABLE_STORAGE', 'cookieStorage'), + 'bs_table_storage' => env('BS_TABLE_STORAGE', 'localStorage'), /* diff --git a/config/version.php b/config/version.php index efa17069ea..9d0b165893 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v7.0.6', - 'full_app_version' => 'v7.0.6 - build 14112-ge1a6b441d', - 'build_version' => '14112', + 'app_version' => 'v8.1.15', + 'full_app_version' => 'v8.1.15 - build 18405-ge4314cf42', + 'build_version' => '18405', 'prerelease_version' => '', - 'hash_version' => 'ge1a6b441d', - 'full_hash' => 'v7.0.6-6-ge1a6b441d', + 'hash_version' => 'ge4314cf42', + 'full_hash' => 'v8.1.15-13-ge4314cf42', 'branch' => 'develop', ); \ No newline at end of file diff --git a/database/factories/AccessoryFactory.php b/database/factories/AccessoryFactory.php index ce0d60cc18..f2c01dc8fb 100644 --- a/database/factories/AccessoryFactory.php +++ b/database/factories/AccessoryFactory.php @@ -3,6 +3,7 @@ namespace Database\Factories; use App\Models\Accessory; +use App\Models\Asset; use App\Models\Category; use App\Models\Location; use App\Models\Manufacturer; @@ -33,7 +34,7 @@ class AccessoryFactory extends Factory $this->faker->randomElement(['Bluetooth', 'Wired']), $this->faker->randomElement(['Keyboard', 'Wired']) ), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'category_id' => Category::factory()->forAccessories(), 'model_number' => $this->faker->numberBetween(1000000, 50000000), 'location_id' => Location::factory(), @@ -125,11 +126,12 @@ class AccessoryFactory extends Factory })->afterCreating(function ($accessory) { $user = User::factory()->create(); - $accessory->users()->attach($accessory->id, [ + $accessory->checkouts()->create([ 'accessory_id' => $accessory->id, - 'created_at' => now(), - 'user_id' => $user->id, + 'created_at' => Carbon::now(), + 'created_by' => $user->id, 'assigned_to' => $user->id, + 'assigned_type' => User::class, 'note' => '', ]); }); @@ -145,11 +147,53 @@ class AccessoryFactory extends Factory public function checkedOutToUser(User $user = null) { return $this->afterCreating(function (Accessory $accessory) use ($user) { - $accessory->users()->attach($accessory->id, [ + $accessory->checkouts()->create([ 'accessory_id' => $accessory->id, 'created_at' => Carbon::now(), - 'user_id' => 1, + 'created_by' => 1, 'assigned_to' => $user->id ?? User::factory()->create()->id, + 'assigned_type' => User::class, + ]); + }); + } + + public function checkedOutToUsers(array $users) + { + return $this->afterCreating(function (Accessory $accessory) use ($users) { + foreach ($users as $user) { + $accessory->checkouts()->create([ + 'accessory_id' => $accessory->id, + 'created_at' => Carbon::now(), + 'created_by' => 1, + 'assigned_to' => $user->id, + 'assigned_type' => User::class, + ]); + } + }); + } + + public function checkedOutToAsset(Asset $asset = null) + { + return $this->afterCreating(function (Accessory $accessory) use ($asset) { + $accessory->checkouts()->create([ + 'accessory_id' => $accessory->id, + 'created_at' => Carbon::now(), + 'created_by' => 1, + 'assigned_to' => $asset->id ?? Asset::factory()->create()->id, + 'assigned_type' => Asset::class, + ]); + }); + } + + public function checkedOutToLocation(Location $location = null) + { + return $this->afterCreating(function (Accessory $accessory) use ($location) { + $accessory->checkouts()->create([ + 'accessory_id' => $accessory->id, + 'created_at' => Carbon::now(), + 'created_by' => 1, + 'assigned_to' => $location->id ?? Location::factory()->create()->id, + 'assigned_type' => Location::class, ]); }); } diff --git a/database/factories/ActionlogFactory.php b/database/factories/ActionlogFactory.php index 1a4007888c..ad07f7082b 100644 --- a/database/factories/ActionlogFactory.php +++ b/database/factories/ActionlogFactory.php @@ -29,7 +29,7 @@ class ActionlogFactory extends Factory return [ 'item_id' => Asset::factory(), 'item_type' => Asset::class, - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'action_type' => 'uploaded', ]; } @@ -92,7 +92,7 @@ class ActionlogFactory extends Factory $licenseSeat->update([ 'assigned_to' => $target->id, - 'user_id' => 1, // not ideal but works + 'created_by' => 1, // not ideal but works ]); return [ @@ -105,4 +105,64 @@ class ActionlogFactory extends Factory ]; }); } + + public function filesUploaded() + { + return $this->state(function () { + + return [ + 'created_at' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get()), + 'action_type' => 'uploaded', + 'item_type' => User::class, + 'filename' => $this->faker->unixTime('now'), + ]; + }); + } + + public function acceptedSignature() + { + return $this->state(function () { + + $asset = Asset::factory()->create(); + + return [ + 'created_at' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get()), + 'action_type' => 'accepted', + 'item_id' => $asset->id, + 'item_type' => Asset::class, + 'target_type' => User::class, + 'accept_signature' => $this->faker->unixTime('now'), + ]; + }); + } + + public function acceptedEula() + { + return $this->state(function () { + + $asset = Asset::factory()->create(); + + return [ + 'created_at' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get()), + 'action_type' => 'accepted', + 'item_id' => $asset->id, + 'item_type' => Asset::class, + 'target_type' => User::class, + 'filename' => $this->faker->unixTime('now'), + ]; + }); + } + + public function userUpdated() + { + return $this->state(function () { + + return [ + 'created_at' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get()), + 'action_type' => 'update', + 'target_type' => User::class, + 'item_type' => User::class, + ]; + }); + } } diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php index 43845c3075..ca85c23584 100644 --- a/database/factories/AssetFactory.php +++ b/database/factories/AssetFactory.php @@ -4,6 +4,7 @@ namespace Database\Factories; use App\Models\Asset; use App\Models\AssetModel; +use App\Models\Category; use App\Models\CustomField; use App\Models\Location; use App\Models\Statuslabel; @@ -36,7 +37,7 @@ class AssetFactory extends Factory 'status_id' => function () { return Statuslabel::where('name', 'Ready to Deploy')->first() ?? Statuslabel::factory()->rtd()->create(['name' => 'Ready to Deploy']); }, - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'asset_tag' => $this->faker->unixTime('now'), 'notes' => 'Created by DB seeder', 'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'), @@ -48,6 +49,7 @@ class AssetFactory extends Factory 'assigned_type' => null, 'next_audit_date' => null, 'last_checkout' => null, + 'asset_eol_date' => null ]; } @@ -300,11 +302,11 @@ class AssetFactory extends Factory }); } - public function assignedToLocation() + public function assignedToLocation(Location $location = null) { - return $this->state(function () { + return $this->state(function () use ($location) { return [ - 'assigned_to' => Location::factory(), + 'assigned_to' => $location->id ?? Location::factory(), 'assigned_type' => Location::class, ]; }); @@ -332,6 +334,15 @@ class AssetFactory extends Factory }); } + public function doesNotRequireAcceptance() + { + return $this->state(function () { + return [ + 'model_id' => AssetModel::factory()->doesNotRequireAcceptance(), + ]; + }); + } + public function deleted() { return $this->state(function () { @@ -346,14 +357,35 @@ class AssetFactory extends Factory public function requestable() { - return $this->state(['requestable' => true]); + $id = Statuslabel::factory()->create([ + 'archived' => false, + 'deployable' => true, + 'pending' => true, + ])->id; + return $this->state(['status_id' => $id, 'requestable' => true]); } public function nonrequestable() { - return $this->state(['requestable' => false]); + $id = Statuslabel::factory()->create([ + 'archived' => true, + 'deployable' => false, + 'pending' => false, + ])->id; + return $this->state(['status_id' => $id, 'requestable' => false]); } + public function noPurchaseOrEolDate() + { + return $this->afterCreating(function (Asset $asset) { + $asset->update([ + 'purchase_date' => null, + 'asset_eol_date' => null + ]); + }); + } + + public function hasEncryptedCustomField(CustomField $field = null) { return $this->state(function () use ($field) { @@ -372,7 +404,6 @@ class AssetFactory extends Factory }); } - /** * This allows bypassing model level validation if you want to purposefully * create an asset in an invalid state. Validation is turned back on diff --git a/database/factories/AssetModelFactory.php b/database/factories/AssetModelFactory.php index 6790897567..3adb933e3e 100644 --- a/database/factories/AssetModelFactory.php +++ b/database/factories/AssetModelFactory.php @@ -28,7 +28,7 @@ class AssetModelFactory extends Factory public function definition() { return [ - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'name' => $this->faker->catchPhrase(), 'category_id' => Category::factory(), 'model_number' => $this->faker->creditCardNumber(), @@ -448,4 +448,13 @@ class AssetModelFactory extends Factory ]; }); } + + public function doesNotRequireAcceptance() + { + return $this->state(function () { + return [ + 'category_id' => Category::factory()->doesNotRequireAcceptance(), + ]; + }); + } } diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index fe6bc255b7..733c52668e 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -25,11 +25,12 @@ class CategoryFactory extends Factory return [ 'name' => $this->faker->catchPhrase(), 'category_type' => 'asset', - 'checkin_email' => $this->faker->boolean(), + 'checkin_email' => true, 'eula_text' => $this->faker->paragraph(), 'require_acceptance' => false, - 'use_default_eula' => $this->faker->boolean(), - 'user_id' => User::factory()->superuser(), + 'use_default_eula' => false, + 'created_by' => User::factory()->superuser(), + 'notes' => 'Created by DB seeder', ]; } @@ -178,4 +179,39 @@ class CategoryFactory extends Factory 'category_type' => 'accessory', ]); } + + public function forAssets() + { + return $this->state([ + 'category_type' => 'asset', + ]); + } + + public function forLicenses() + { + return $this->state([ + 'category_type' => 'license', + ]); + } + + public function forComponents() + { + return $this->state([ + 'category_type' => 'component', + ]); + } + + public function forConsumables() + { + return $this->state([ + 'category_type' => 'consumable', + ]); + } + + public function doesNotRequireAcceptance() + { + return $this->state([ + 'require_acceptance' => false, + ]); + } } diff --git a/database/factories/CheckoutAcceptanceFactory.php b/database/factories/CheckoutAcceptanceFactory.php index b5744527f8..a15e942a84 100644 --- a/database/factories/CheckoutAcceptanceFactory.php +++ b/database/factories/CheckoutAcceptanceFactory.php @@ -4,6 +4,7 @@ namespace Database\Factories; use App\Models\Accessory; use App\Models\Asset; +use App\Models\CheckoutAcceptance; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -23,6 +24,22 @@ class CheckoutAcceptanceFactory extends Factory ]; } + public function configure(): static + { + return $this->afterCreating(function (CheckoutAcceptance $acceptance) { + if ($acceptance->checkoutable instanceof Asset) { + $this->createdAssociatedActionLogEntry($acceptance); + } + + if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) { + $acceptance->checkoutable->update([ + 'assigned_to' => $acceptance->assigned_to_id, + 'assigned_type' => get_class($acceptance->assignedTo), + ]); + } + }); + } + public function forAccessory() { return $this->state([ @@ -38,4 +55,23 @@ class CheckoutAcceptanceFactory extends Factory 'declined_at' => null, ]); } + + public function accepted() + { + return $this->state([ + 'accepted_at' => now()->subDay(), + 'declined_at' => null, + ]); + } + + private function createdAssociatedActionLogEntry(CheckoutAcceptance $acceptance): void + { + $acceptance->checkoutable->assetlog()->create([ + 'action_type' => 'checkout', + 'target_id' => $acceptance->assigned_to_id, + 'target_type' => get_class($acceptance->assignedTo), + 'item_id' => $acceptance->checkoutable_id, + 'item_type' => $acceptance->checkoutable_type, + ]); + } } diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 607822fef1..12195d78a5 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -23,6 +23,8 @@ class CompanyFactory extends Factory { return [ 'name' => $this->faker->unique()->company(), + 'created_by' => 1, + 'notes' => 'Created by DB seeder', ]; } } diff --git a/database/factories/ComponentFactory.php b/database/factories/ComponentFactory.php index caac70078f..786e780895 100644 --- a/database/factories/ComponentFactory.php +++ b/database/factories/ComponentFactory.php @@ -2,10 +2,16 @@ namespace Database\Factories; +use App\Models\Accessory; +use App\Models\Asset; use App\Models\Category; use App\Models\Company; use App\Models\Component; +use App\Models\Manufacturer; +use App\Models\Consumable; use App\Models\Location; +use App\Models\User; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Supplier; @@ -25,6 +31,7 @@ class ComponentFactory extends Factory */ public function definition() { + return [ 'name' => $this->faker->text(20), 'category_id' => Category::factory(), @@ -37,12 +44,14 @@ class ComponentFactory extends Factory 'min_amt' => $this->faker->numberBetween($min = 1, $max = 2), 'company_id' => Company::factory(), 'supplier_id' => Supplier::factory(), + 'model_number' => $this->faker->numberBetween(1000000, 50000000), ]; } public function ramCrucial4() { - return $this->state(function () { + $manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']); + return $this->state(function () use ($manufacturer) { return [ 'name' => 'Crucial 4GB DDR3L-1600 SODIMM', 'category_id' => function () { @@ -50,6 +59,7 @@ class ComponentFactory extends Factory }, 'qty' => 10, 'min_amt' => 2, + 'manufacturer_id' => $manufacturer->id, 'location_id' => Location::factory(), ]; }); @@ -57,7 +67,8 @@ class ComponentFactory extends Factory public function ramCrucial8() { - return $this->state(function () { + $manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']); + return $this->state(function () use ($manufacturer) { return [ 'name' => 'Crucial 8GB DDR3L-1600 SODIMM Memory for Mac', 'category_id' => function () { @@ -65,13 +76,15 @@ class ComponentFactory extends Factory }, 'qty' => 10, 'min_amt' => 2, + 'manufacturer_id' => $manufacturer->id, ]; }); } public function ssdCrucial120() { - return $this->state(function () { + $manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']); + return $this->state(function () use ($manufacturer) { return [ 'name' => 'Crucial BX300 120GB SATA Internal SSD', 'category_id' => function () { @@ -79,13 +92,15 @@ class ComponentFactory extends Factory }, 'qty' => 10, 'min_amt' => 2, + 'manufacturer_id' => $manufacturer->id, ]; }); } public function ssdCrucial240() { - return $this->state(function () { + $manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']); + return $this->state(function () use ($manufacturer) { return [ 'name' => 'Crucial BX300 240GB SATA Internal SSD', 'category_id' => function () { @@ -93,9 +108,21 @@ class ComponentFactory extends Factory }, 'qty' => 10, 'min_amt' => 2, + 'manufacturer_id' => $manufacturer->id, ]; }); } + public function checkedOutToAsset(Asset $asset = null) + { + return $this->afterCreating(function (Component $component) use ($asset) { + $component->assets()->attach($component->id, [ + 'component_id' => $component->id, + 'created_at' => Carbon::now(), + 'created_by' => 1, + 'asset_id' => $asset->id ?? Asset::factory()->create()->id, + ]); + }); + } } diff --git a/database/factories/ConsumableFactory.php b/database/factories/ConsumableFactory.php index d9aec36afa..4a4b3ef872 100644 --- a/database/factories/ConsumableFactory.php +++ b/database/factories/ConsumableFactory.php @@ -7,6 +7,7 @@ use App\Models\Company; use App\Models\Consumable; use App\Models\Manufacturer; use App\Models\User; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\Factory; use App\Models\Supplier; @@ -29,7 +30,7 @@ class ConsumableFactory extends Factory return [ 'name' => $this->faker->words(3, true), 'category_id' => Category::factory(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'item_no' => $this->faker->numberBetween(1000000, 50000000), 'order_number' => $this->faker->numberBetween(1000000, 50000000), 'purchase_date' => $this->faker->dateTimeBetween('-1 years', 'now', date_default_timezone_get())->format('Y-m-d'), @@ -103,7 +104,7 @@ class ConsumableFactory extends Factory $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, - 'user_id' => $user->id, + 'created_by' => $user->id, 'assigned_to' => $user->id, 'note' => '', ]); @@ -116,4 +117,16 @@ class ConsumableFactory extends Factory $consumable->category->update(['require_acceptance' => 1]); }); } + + public function checkedOutToUser(User $user = null) + { + return $this->afterCreating(function (Consumable $consumable) use ($user) { + $consumable->users()->attach($consumable->id, [ + 'consumable_id' => $consumable->id, + 'created_at' => Carbon::now(), + 'created_by' => User::factory()->create()->id, + 'assigned_to' => $user->id ?? User::factory()->create()->id, + ]); + }); + } } diff --git a/database/factories/DepartmentFactory.php b/database/factories/DepartmentFactory.php index afcc9cbd33..7e6c2f0025 100644 --- a/database/factories/DepartmentFactory.php +++ b/database/factories/DepartmentFactory.php @@ -25,8 +25,9 @@ class DepartmentFactory extends Factory { return [ 'name' => $this->faker->unique()->word() . ' Department', - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'location_id' => Location::factory(), + 'notes' => 'Created by DB seeder', ]; } diff --git a/database/factories/DepreciationFactory.php b/database/factories/DepreciationFactory.php index 6359e2326b..52258e784b 100644 --- a/database/factories/DepreciationFactory.php +++ b/database/factories/DepreciationFactory.php @@ -24,7 +24,7 @@ class DepreciationFactory extends Factory { return [ 'name' => $this->faker->unique()->catchPhrase(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'months' => 36, ]; } diff --git a/database/factories/GroupFactory.php b/database/factories/GroupFactory.php index 9dee88a965..fecf8239bb 100644 --- a/database/factories/GroupFactory.php +++ b/database/factories/GroupFactory.php @@ -23,6 +23,8 @@ class GroupFactory extends Factory { return [ 'name' => $this->faker->name(), + 'permissions' => json_encode([]), + 'notes' => 'Created by DB seeder', ]; } } diff --git a/database/factories/ImportFactory.php b/database/factories/ImportFactory.php new file mode 100644 index 0000000000..5355cac834 --- /dev/null +++ b/database/factories/ImportFactory.php @@ -0,0 +1,236 @@ + + */ +class ImportFactory extends Factory +{ + /** + * @inheritdoc + */ + protected $model = Import::class; + + /** + * @inheritdoc + */ + public function definition() + { + return [ + 'name' => $this->faker->company, + 'file_path' => Str::random().'.csv', + 'filesize' => $this->faker->randomDigitNotNull(), + 'field_map' => null, + ]; + } + + /** + * Create an accessory import type. + * + * @return static + */ + public function accessory() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\AccessoriesImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Accessories"; + $attributes['import_type'] = 'accessory'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create an asset import type. + * + * @return static + */ + public function asset() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\AssetsImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Assets"; + $attributes['import_type'] = 'asset'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a component import type. + * + * @return static + */ + public function component() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\ComponentsImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Components"; + $attributes['import_type'] = 'component'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a consumable import type. + * + * @return static + */ + public function consumable() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\ConsumablesImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Consumables"; + $attributes['import_type'] = 'consumable'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a license import type. + * + * @return static + */ + public function license() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\LicensesImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Licenses"; + $attributes['import_type'] = 'license'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a users import type. + * + * @return static + */ + public function users() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\UsersImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Employees"; + $attributes['import_type'] = 'user'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + + + /** + * Create an asset model import type. + * + * @return static + */ + public function assetmodel() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\AssetModelsImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Asset Model"; + $attributes['import_type'] = 'assetModel'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a supplier import type. + * + * @return static + */ + public function suppliers() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\SuppliersImportFileBuilder::new(); + $attributes['name'] = "Supplier {$attributes['name']}"; + $attributes['import_type'] = 'supplier'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create an supplier import type. + * + * @return static + */ + public function locations() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\SuppliersImportFileBuilder::new(); + $attributes['name'] = "Location {$attributes['name']}"; + $attributes['import_type'] = 'location'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + /** + * Create a supplier import type. + * + * @return static + */ + public function manufacturers() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\ManufacturersImportFileBuilder::new(); + $attributes['name'] = "Manufacturer {$attributes['name']}"; + $attributes['import_type'] = 'manufacturer'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + public function categories() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\CategoriesImportFileBuilder::new(); + $attributes['name'] = "Category {$attributes['name']}"; + $attributes['import_type'] = 'category'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + + +} diff --git a/database/factories/LicenseFactory.php b/database/factories/LicenseFactory.php index 6360735c5f..1f5b105f42 100644 --- a/database/factories/LicenseFactory.php +++ b/database/factories/LicenseFactory.php @@ -25,7 +25,7 @@ class LicenseFactory extends Factory public function definition() { return [ - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'name' => $this->faker->name(), 'license_email' => $this->faker->safeEmail(), 'serial' => $this->faker->uuid(), diff --git a/database/factories/LicenseSeatFactory.php b/database/factories/LicenseSeatFactory.php index f9560af472..aaf75bdc2d 100644 --- a/database/factories/LicenseSeatFactory.php +++ b/database/factories/LicenseSeatFactory.php @@ -4,6 +4,7 @@ namespace Database\Factories; use App\Models\Asset; use App\Models\License; +use App\Models\LicenseSeat; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -33,4 +34,18 @@ class LicenseSeatFactory extends Factory ]; }); } + + public function reassignable() + { + return $this->afterMaking(function (LicenseSeat $seat) { + $seat->license->update(['reassignable' => true]); + }); + } + + public function notReassignable() + { + return $this->afterMaking(function (LicenseSeat $seat) { + $seat->license->update(['reassignable' => false]); + }); + } } diff --git a/database/factories/LocationFactory.php b/database/factories/LocationFactory.php index 6db268e8c1..3d0b444f70 100644 --- a/database/factories/LocationFactory.php +++ b/database/factories/LocationFactory.php @@ -23,6 +23,22 @@ class LocationFactory extends Factory 'currency' => $this->faker->currencyCode(), 'zip' => $this->faker->postcode(), 'image' => rand(1, 9).'.jpg', + 'notes' => 'Created by DB seeder', ]; } + + // one of these can eventuall go away - left temporarily for conflict resolution + public function deleted(): self + { + return $this->state(['deleted_at' => $this->faker->dateTime()]); + } + + public function deletedLocation() + { + return $this->state(function () { + return [ + 'deleted_at' => $this->faker->dateTime(), + ]; + }); + } } diff --git a/database/factories/ManufacturerFactory.php b/database/factories/ManufacturerFactory.php index 7d6892426d..0a8a0a543e 100644 --- a/database/factories/ManufacturerFactory.php +++ b/database/factories/ManufacturerFactory.php @@ -24,10 +24,11 @@ class ManufacturerFactory extends Factory { return [ 'name' => $this->faker->unique()->company(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'support_phone' => $this->faker->phoneNumber(), 'url' => $this->faker->url(), 'support_email' => $this->faker->safeEmail(), + 'notes' => 'Created by DB seeder', ]; } @@ -165,4 +166,49 @@ class ManufacturerFactory extends Factory ]; }); } + + public function samsung() + { + return $this->state(function () { + return [ + 'name' => 'Samsung', + 'url' => 'https://www.samsung.com', + 'support_url' => 'https://www.samsung.com/support/', + 'image' => 'samsung.png', + ]; + }); + } + + public function google() + { + return $this->state(function () { + return [ + 'name' => 'Google', + 'url' => 'https://www.google.com', + 'image' => 'google.webp', + ]; + }); + } + + public function huawei() + { + return $this->state(function () { + return [ + 'name' => 'Huawei', + 'url' => 'https://consumer.huawei.com/', + 'image' => 'huawei.webp', + ]; + }); + } + + public function sony() + { + return $this->state(function () { + return [ + 'name' => 'Sony', + 'url' => 'https://electronics.sony.com', + 'image' => 'sony.png', + ]; + }); + } } diff --git a/database/factories/PredefinedKitFactory.php b/database/factories/PredefinedKitFactory.php new file mode 100644 index 0000000000..32e192655f --- /dev/null +++ b/database/factories/PredefinedKitFactory.php @@ -0,0 +1,23 @@ + + */ +class PredefinedKitFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => $this->faker->words(3, true), + ]; + } +} diff --git a/database/factories/ReportTemplateFactory.php b/database/factories/ReportTemplateFactory.php new file mode 100644 index 0000000000..7044af6342 --- /dev/null +++ b/database/factories/ReportTemplateFactory.php @@ -0,0 +1,25 @@ + $this->faker->word(), + 'options' => [ + 'id' => '1', + ], + 'created_by' => User::factory(), + ]; + } +} diff --git a/database/factories/StatuslabelFactory.php b/database/factories/StatuslabelFactory.php index fa2e5d5e1a..1f04f4564e 100644 --- a/database/factories/StatuslabelFactory.php +++ b/database/factories/StatuslabelFactory.php @@ -26,7 +26,7 @@ class StatuslabelFactory extends Factory 'name' => $this->faker->sentence(), 'created_at' => $this->faker->dateTime(), 'updated_at' => $this->faker->dateTime(), - 'user_id' => User::factory()->superuser(), + 'created_by' => User::factory()->superuser(), 'deleted_at' => null, 'deployable' => 0, 'pending' => 0, diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 5c885666df..d8b1ac141c 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -7,6 +7,9 @@ use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use \Auth; +/** + * @extends Factory + */ class UserFactory extends Factory { /** @@ -35,6 +38,7 @@ class UserFactory extends Factory 'state' => $this->faker->stateAbbr(), 'username' => $this->faker->unique()->username(), 'zip' => $this->faker->postcode(), + 'created_by' => 1, ]; } @@ -141,6 +145,16 @@ class UserFactory extends Factory return $this->appendPermission(['assets.view.requestable' => '1']); } + public function deleteAssetModels() + { + return $this->appendPermission(['models.delete' => '1']); + } + + public function viewAssetModels() + { + return $this->appendPermission(['models.view' => '1']); + } + public function viewAccessories() { return $this->appendPermission(['accessories.view' => '1']); @@ -201,6 +215,11 @@ class UserFactory extends Factory return $this->appendPermission(['consumables.checkout' => '1']); } + public function deleteDepartments() + { + return $this->appendPermission(['departments.delete' => '1']); + } + public function viewDepartments() { return $this->appendPermission(['departments.view' => '1']); @@ -266,6 +285,21 @@ class UserFactory extends Factory return $this->appendPermission(['components.checkout' => '1']); } + public function createCompanies() + { + return $this->appendPermission(['companies.create' => '1']); + } + + public function deleteCompanies() + { + return $this->appendPermission(['companies.delete' => '1']); + } + + public function editCompanies() + { + return $this->appendPermission(['companies.edit' => '1']); + } + public function viewUsers() { return $this->appendPermission(['users.view' => '1']); @@ -286,6 +320,16 @@ class UserFactory extends Factory return $this->appendPermission(['users.delete' => '1']); } + public function deleteCategories() + { + return $this->appendPermission(['categories.delete' => '1']); + } + + public function deleteLocations() + { + return $this->appendPermission(['locations.delete' => '1']); + } + public function canEditOwnLocation() { return $this->appendPermission(['self.edit_location' => '1']); @@ -296,6 +340,57 @@ class UserFactory extends Factory return $this->appendPermission(['reports.view' => '1']); } + public function canImport() + { + return $this->appendPermission(['import' => '1']); + } + + public function deleteCustomFields() + { + return $this->appendPermission(['customfields.delete' => '1']); + } + + public function deleteCustomFieldsets() + { + return $this->appendPermission(['customfields.delete' => '1']); + } + + public function deleteDepreciations() + { + return $this->appendPermission(['depreciations.delete' => '1']); + } + + public function deleteManufacturers() + { + return $this->appendPermission(['manufacturers.delete' => '1']); + } + + public function deletePredefinedKits() + { + return $this->appendPermission(['kits.delete' => '1']); + } + + public function viewPredefinedKits() + { + return $this->appendPermission(['kits.view' => '1']); + } + + public function deleteStatusLabels() + { + return $this->appendPermission(['statuslabels.delete' => '1']); + } + + public function deleteSuppliers() + { + return $this->appendPermission(['suppliers.delete' => '1']); + } + + public function auditAssets() + { + return $this->appendPermission(['assets.audit' => '1']); + } + + private function appendPermission(array $permission) { return $this->state(function ($currentState) use ($permission) { @@ -309,4 +404,9 @@ class UserFactory extends Factory ]; }); } + + public function deleted(): self + { + return $this->state(['deleted_at' => $this->faker->dateTime()]); + } } diff --git a/database/migrations/2015_02_07_013537_add_soft_deleted_to_log.php b/database/migrations/2015_02_07_013537_add_soft_deleted_to_log.php index 7040550e60..160e680761 100644 --- a/database/migrations/2015_02_07_013537_add_soft_deleted_to_log.php +++ b/database/migrations/2015_02_07_013537_add_soft_deleted_to_log.php @@ -12,8 +12,8 @@ class AddSoftDeletedToLog extends Migration */ public function up() { - $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); - $platform->registerDoctrineTypeMapping('enum', 'string'); + //$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); + //$platform->registerDoctrineTypeMapping('enum', 'string'); Schema::table('asset_logs', function ($table) { $table->string('asset_type', 100)->nullable()->change(); diff --git a/database/migrations/2015_02_12_024100_change_license_notes_type.php b/database/migrations/2015_02_12_024100_change_license_notes_type.php index ba2f4b3188..7389dc8087 100644 --- a/database/migrations/2015_02_12_024100_change_license_notes_type.php +++ b/database/migrations/2015_02_12_024100_change_license_notes_type.php @@ -14,7 +14,7 @@ class ChangeLicenseNotesType extends Migration { // Schema::table('licenses', function ($table) { - $table->text('notes')->change(); + $table->text('notes')->nullable()->change(); }); } diff --git a/database/migrations/2015_09_22_003413_migrate_mac_address.php b/database/migrations/2015_09_22_003413_migrate_mac_address.php index 3c4bf93e15..4c65c29736 100644 --- a/database/migrations/2015_09_22_003413_migrate_mac_address.php +++ b/database/migrations/2015_09_22_003413_migrate_mac_address.php @@ -12,7 +12,7 @@ class MigrateMacAddress extends Migration */ public function up() { - DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); + //DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); $f2 = new \App\Models\CustomFieldset(['name' => 'Asset with MAC Address']); $f2->timestamps = false; //when this model was first created, it had no timestamps. But later on it gets them. diff --git a/database/migrations/2016_03_02_220517_update_ldap_filter_to_longer_field.php b/database/migrations/2016_03_02_220517_update_ldap_filter_to_longer_field.php index a17830f114..bc2b17f1b8 100644 --- a/database/migrations/2016_03_02_220517_update_ldap_filter_to_longer_field.php +++ b/database/migrations/2016_03_02_220517_update_ldap_filter_to_longer_field.php @@ -13,7 +13,7 @@ class UpdateLdapFilterToLongerField extends Migration public function up() { Schema::table('settings', function (Blueprint $table) { - $table->text('ldap_filter')->default(null)->change(); + $table->text('ldap_filter')->nullable()->default(null)->change(); }); } diff --git a/database/migrations/2016_08_30_084634_make_purchase_cost_nullable.php b/database/migrations/2016_08_30_084634_make_purchase_cost_nullable.php index 525242c647..742885b245 100644 --- a/database/migrations/2016_08_30_084634_make_purchase_cost_nullable.php +++ b/database/migrations/2016_08_30_084634_make_purchase_cost_nullable.php @@ -12,8 +12,8 @@ class MakePurchaseCostNullable extends Migration */ public function up() { - $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); - $platform->registerDoctrineTypeMapping('enum', 'string'); + //$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); + //$platform->registerDoctrineTypeMapping('enum', 'string'); Schema::table('assets', function ($table) { $table->decimal('purchase_cost', 8, 2)->nullable()->default(null)->change(); diff --git a/database/migrations/2016_11_16_172119_increase_purchase_cost_size.php b/database/migrations/2016_11_16_172119_increase_purchase_cost_size.php index 37036da601..d463a97eb6 100644 --- a/database/migrations/2016_11_16_172119_increase_purchase_cost_size.php +++ b/database/migrations/2016_11_16_172119_increase_purchase_cost_size.php @@ -12,8 +12,8 @@ class IncreasePurchaseCostSize extends Migration */ public function up() { - $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); - $platform->registerDoctrineTypeMapping('enum', 'string'); + //$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); + //$platform->registerDoctrineTypeMapping('enum', 'string'); Schema::table('assets', function ($table) { $table->decimal('purchase_cost', 20, 2)->nullable()->default(null)->change(); @@ -47,8 +47,8 @@ class IncreasePurchaseCostSize extends Migration */ public function down() { - $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); - $platform->registerDoctrineTypeMapping('enum', 'string'); + //$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); + //$platform->registerDoctrineTypeMapping('enum', 'string'); Schema::table('assets', function ($table) { $table->decimal('purchase_cost', 8, 2)->nullable()->default(null)->change(); diff --git a/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php b/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php index 4725cccfe1..0df09864a5 100644 --- a/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php +++ b/database/migrations/2017_01_25_063357_fix_utf8_custom_field_column_names.php @@ -43,8 +43,8 @@ class FixUtf8CustomFieldColumnNames extends Migration */ public function up() { - $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); - $platform->registerDoctrineTypeMapping('enum', 'string'); + //$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); + //$platform->registerDoctrineTypeMapping('enum', 'string'); if (! Schema::hasColumn('custom_fields', 'db_column')) { Schema::table('custom_fields', function ($table) { diff --git a/database/migrations/2017_08_03_160105_set_asset_archived_to_zero_default.php b/database/migrations/2017_08_03_160105_set_asset_archived_to_zero_default.php index f60b9856de..1e794ef695 100644 --- a/database/migrations/2017_08_03_160105_set_asset_archived_to_zero_default.php +++ b/database/migrations/2017_08_03_160105_set_asset_archived_to_zero_default.php @@ -13,11 +13,11 @@ class SetAssetArchivedToZeroDefault extends Migration */ public function up() { - $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); - $platform->registerDoctrineTypeMapping('enum', 'string'); + //$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); + //$platform->registerDoctrineTypeMapping('enum', 'string'); Schema::table('assets', function (Blueprint $table) { - $table->boolean('archived')->default(0)->change(); + $table->boolean('archived')->default(0)->nullable()->change(); }); } diff --git a/database/migrations/2019_02_20_234421_make_serial_nullable.php b/database/migrations/2019_02_20_234421_make_serial_nullable.php index f1419009cc..295e66f27c 100644 --- a/database/migrations/2019_02_20_234421_make_serial_nullable.php +++ b/database/migrations/2019_02_20_234421_make_serial_nullable.php @@ -13,8 +13,8 @@ class MakeSerialNullable extends Migration */ public function up() { - $platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); - $platform->registerDoctrineTypeMapping('enum', 'string'); + //$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform(); + //$platform->registerDoctrineTypeMapping('enum', 'string'); Schema::table('assets', function (Blueprint $table) { $table->string('serial')->nullable()->default(null)->change(); diff --git a/database/migrations/2019_12_04_223111_passport_upgrade.php b/database/migrations/2019_12_04_223111_passport_upgrade.php index 02cbe56dec..3abeca4703 100644 --- a/database/migrations/2019_12_04_223111_passport_upgrade.php +++ b/database/migrations/2019_12_04_223111_passport_upgrade.php @@ -29,7 +29,7 @@ class PassportUpgrade extends Migration { if (Schema::hasTable('oauth_clients')) { Schema::table('oauth_clients', function (Blueprint $table) { - $table->string('secret', 100)->change(); + $table->string('secret', 100)->nullable(false)->change(); }); } } diff --git a/database/migrations/2022_02_10_110210_add_company_id_to_locations.php b/database/migrations/2022_02_10_110210_add_company_id_to_locations.php new file mode 100644 index 0000000000..8ef5822812 --- /dev/null +++ b/database/migrations/2022_02_10_110210_add_company_id_to_locations.php @@ -0,0 +1,35 @@ +integer('company_id')->unsigned()->nullable(); + $table->index(['company_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('locations', function (Blueprint $table) { + $table->dropIndex(['company_id']); + $table->dropColumn('company_id'); + }); + } +} + diff --git a/database/migrations/2023_02_27_092130_add_scope_locations_setting.php b/database/migrations/2023_02_27_092130_add_scope_locations_setting.php new file mode 100644 index 0000000000..c1e2ff83e9 --- /dev/null +++ b/database/migrations/2023_02_27_092130_add_scope_locations_setting.php @@ -0,0 +1,32 @@ +boolean('scope_locations_fmcs')->default('0')->after('full_multiple_companies_support'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('scope_locations_fmcs'); + }); + } +} \ No newline at end of file diff --git a/database/migrations/2023_08_01_174150_change_webhook_settings_variable_type.php b/database/migrations/2023_08_01_174150_change_webhook_settings_variable_type.php index 59c9728e2e..1b84ce7211 100644 --- a/database/migrations/2023_08_01_174150_change_webhook_settings_variable_type.php +++ b/database/migrations/2023_08_01_174150_change_webhook_settings_variable_type.php @@ -14,7 +14,7 @@ class ChangeWebhookSettingsVariableType extends Migration public function up() { Schema::table('settings', function (Blueprint $table) { - $table->text('webhook_endpoint')->change(); + $table->text('webhook_endpoint')->nullable()->change(); }); } @@ -26,7 +26,7 @@ class ChangeWebhookSettingsVariableType extends Migration public function down() { Schema::table('settings', function (Blueprint $table) { - $table->varchar('webhook_endpoint')->change(); + $table->string('webhook_endpoint')->nullable()->change(); }); } diff --git a/database/migrations/2023_08_23_232739_create_report_templates_table.php b/database/migrations/2023_08_23_232739_create_report_templates_table.php new file mode 100644 index 0000000000..e2537594a5 --- /dev/null +++ b/database/migrations/2023_08_23_232739_create_report_templates_table.php @@ -0,0 +1,48 @@ +id(); + $table->integer('created_by')->nullable(); + $table->string('name'); + + /* + * The "options" column was originally json but this causes issues + * with older versions of mariadb so it was changed text. + * + * A follow-up migration definitively changes it to a text column + * for the systems that had successfully run the migration: + * 2025_01_06_210534_change_report_templates_options_to_column_text_field. + * + * https://github.com/grokability/snipe-it/issues/16015 + */ + $table->text('options'); + + $table->softDeletes(); + $table->timestamps(); + $table->index('created_by'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('report_templates'); + } +} diff --git a/database/migrations/2024_07_04_103729_add_default_avatar_to_settings.php b/database/migrations/2024_07_04_103729_add_default_avatar_to_settings.php new file mode 100644 index 0000000000..423f45e9ee --- /dev/null +++ b/database/migrations/2024_07_04_103729_add_default_avatar_to_settings.php @@ -0,0 +1,28 @@ +string('default_avatar')->after('favicon')->default('default.png')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('default_avatar'); + }); + } +}; diff --git a/database/migrations/2024_07_16_184145_add_deprecitation_type_to_depreciations_table.php b/database/migrations/2024_07_16_184145_add_deprecitation_type_to_depreciations_table.php new file mode 100644 index 0000000000..1a5355f739 --- /dev/null +++ b/database/migrations/2024_07_16_184145_add_deprecitation_type_to_depreciations_table.php @@ -0,0 +1,28 @@ +string('depreciation_type')->after('depreciation_min')->default('amount'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('depreciations', function (Blueprint $table) { + $table->dropColumn('depreciation_type'); + }); + } +}; diff --git a/database/migrations/2024_07_23_172032_change_no__n_o_to_nb__n_o.php b/database/migrations/2024_07_23_172032_change_no__n_o_to_nb__n_o.php new file mode 100644 index 0000000000..6ed7e2b3a1 --- /dev/null +++ b/database/migrations/2024_07_23_172032_change_no__n_o_to_nb__n_o.php @@ -0,0 +1,28 @@ +update(['locale' => 'nb-NO']); + User::where('locale', 'no-NO')->update(['locale' => 'nb-NO']); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + + } +}; diff --git a/database/migrations/2024_07_26_143301_add_checkout_for_all_types_to_accessories.php b/database/migrations/2024_07_26_143301_add_checkout_for_all_types_to_accessories.php new file mode 100644 index 0000000000..bde3bc2c28 --- /dev/null +++ b/database/migrations/2024_07_26_143301_add_checkout_for_all_types_to_accessories.php @@ -0,0 +1,43 @@ +string('assigned_type')->nullable(); + } + }); + } + + DB::update('update '.DB::getTablePrefix().'accessories_checkout set assigned_type = \'App\\\\Models\\\\User\' where assigned_type is null', []); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (Schema::hasTable('accessories_checkout')) { + Schema::table('accessories_checkout', function (Blueprint $table) { + $table->dropColumn('assigned_type'); + }); + + Schema::rename('accessories_checkout', 'accessories_users'); + } + } +}; diff --git a/database/migrations/2024_08_01_201721_add_required_notes_setting.php b/database/migrations/2024_08_01_201721_add_required_notes_setting.php new file mode 100644 index 0000000000..38fdd38c11 --- /dev/null +++ b/database/migrations/2024_08_01_201721_add_required_notes_setting.php @@ -0,0 +1,30 @@ +boolean('require_checkinout_notes')->nullable()->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + if (Schema::hasColumn('settings', 'require_checkinout_notes')) { + $table->dropColumn('require_checkinout_notes'); + } + }); + } +}; diff --git a/database/migrations/2024_08_06_175114_add_shortcuts_enabled_to_settings_table.php b/database/migrations/2024_08_06_175114_add_shortcuts_enabled_to_settings_table.php new file mode 100644 index 0000000000..1d38c6b891 --- /dev/null +++ b/database/migrations/2024_08_06_175114_add_shortcuts_enabled_to_settings_table.php @@ -0,0 +1,27 @@ +boolean('shortcuts_enabled')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('shortcuts_enabled'); + }); + } +}; diff --git a/database/migrations/2024_08_07_204014_add_play_sounds_to_profile.php b/database/migrations/2024_08_07_204014_add_play_sounds_to_profile.php new file mode 100644 index 0000000000..a6e03fd697 --- /dev/null +++ b/database/migrations/2024_08_07_204014_add_play_sounds_to_profile.php @@ -0,0 +1,28 @@ +boolean('enable_sounds')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('enable_sounds'); + }); + } +}; diff --git a/database/migrations/2024_08_15_111816_add_confetti_to_users.php b/database/migrations/2024_08_15_111816_add_confetti_to_users.php new file mode 100644 index 0000000000..a3aa85d4f4 --- /dev/null +++ b/database/migrations/2024_08_15_111816_add_confetti_to_users.php @@ -0,0 +1,28 @@ +boolean('enable_confetti')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('enable_confetti'); + }); + } +}; diff --git a/database/migrations/2024_08_16_104137_add_due_checkin_days_to_settings.php b/database/migrations/2024_08_16_104137_add_due_checkin_days_to_settings.php new file mode 100644 index 0000000000..09314b88fe --- /dev/null +++ b/database/migrations/2024_08_16_104137_add_due_checkin_days_to_settings.php @@ -0,0 +1,28 @@ +integer('due_checkin_days')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('due_checkin_days'); + }); + } +}; diff --git a/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php b/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php new file mode 100644 index 0000000000..3bef5948b6 --- /dev/null +++ b/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php @@ -0,0 +1,93 @@ +add_to_table_list() as $add_table) { + if (!Schema::hasColumn($add_table, 'created_by')) { + Schema::table($add_table, function (Blueprint $add_table) { + $add_table->unsignedBigInteger('created_by')->nullable()->before('created_at'); + }); + } + } + + foreach ($this->existing_table_list() as $table) { + if (Schema::hasColumn($table, 'user_id')) { + Schema::table($table, function (Blueprint $table) { + $table->renameColumn('user_id', 'created_by'); + }); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + foreach ($this->add_to_table_list() as $add_table) { + if (Schema::hasColumn($add_table, 'created_by')) { + Schema::table($add_table, function (Blueprint $add_table) { + $add_table->dropColumn('created_by'); + }); + } + } + + foreach ($this->existing_table_list() as $table) { + if (Schema::hasColumn($table, 'created_by')) { + Schema::table($table, function (Blueprint $table) { + $table->renameColumn('created_by', 'user_id'); + }); + } + } + } + + public function existing_table_list() { + return [ + 'accessories', + 'accessories_checkout', + 'action_logs', + 'asset_maintenances', + 'assets', + 'categories', + 'components', + 'components_assets', + 'consumables', + 'consumables_users', + 'custom_fields', + 'custom_fieldsets', + 'departments', + 'depreciations', + 'license_seats', + 'licenses', + 'locations', + 'manufacturers', + 'models', + 'settings', + 'status_labels', + 'suppliers', + 'users', + ]; + } + + public function add_to_table_list() { + return [ + 'companies', + 'imports', + 'kits', + 'kits_accessories', + 'kits_consumables', + 'kits_licenses', + 'kits_models', + 'users_groups', + ]; + } +}; diff --git a/database/migrations/2024_10_23_162301_add_manufacturer_id_model_number_to_consumables.php b/database/migrations/2024_10_23_162301_add_manufacturer_id_model_number_to_consumables.php new file mode 100644 index 0000000000..0180ac0edd --- /dev/null +++ b/database/migrations/2024_10_23_162301_add_manufacturer_id_model_number_to_consumables.php @@ -0,0 +1,30 @@ +integer('manufacturer_id')->after('purchase_cost')->nullable()->default(null); + $table->string('model_number')->after('purchase_cost')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('components', function (Blueprint $table) { + $table->dropColumn('manufacturer_id'); + $table->dropColumn('model_number'); + }); + } +}; diff --git a/database/migrations/2024_10_31_212512_update_new_and_drop_old_barcode_settings.php b/database/migrations/2024_10_31_212512_update_new_and_drop_old_barcode_settings.php new file mode 100644 index 0000000000..a802f04290 --- /dev/null +++ b/database/migrations/2024_10_31_212512_update_new_and_drop_old_barcode_settings.php @@ -0,0 +1,46 @@ +whereNull('label2_2d_type')->orWhere('label2_2d_type', '')->update([ + 'label2_2d_type' => DB::raw('barcode_type') + ]); + + DB::table('settings')->whereNull('label2_1d_type')->orWhere('label2_1d_type', '')->update([ + 'label2_1d_type' => DB::raw('alt_barcode') + ]); + + + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn(['barcode_type', 'alt_barcode']); + }); + } + + public function down() + { + Schema::table('settings', function (Blueprint $table) { + // Re-add the columns that were dropped in case of rollback + $table->string('barcode_type')->nullable(); + $table->string('alt_barcode')->nullable(); + }); + + DB::table('settings')->whereNull('barcode_type')->orWhere('barcode_type', '')->update([ + 'barcode_type' => DB::raw('label2_2d_type') + ]); + + DB::table('settings')->whereNull('alt_barcode')->orWhere('alt_barcode', '')->update([ + 'alt_barcode' => DB::raw('label2_1d_type') + ]); + } +}; diff --git a/database/migrations/2024_11_06_211457_add_manager_indexes_to_location_and_user.php b/database/migrations/2024_11_06_211457_add_manager_indexes_to_location_and_user.php new file mode 100644 index 0000000000..d1a151c126 --- /dev/null +++ b/database/migrations/2024_11_06_211457_add_manager_indexes_to_location_and_user.php @@ -0,0 +1,34 @@ +index('manager_id'); + }); + Schema::table('users', function (Blueprint $table) { + $table->index('manager_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('locations', function (Blueprint $table) { + $table->dropIndex(['manager_id']); + }); + Schema::table('users', function (Blueprint $table) { + $table->dropIndex(['manager_id']); + }); + } +}; diff --git a/database/migrations/2024_11_07_113631_improve_manager_indexes_on_users_and_locations.php b/database/migrations/2024_11_07_113631_improve_manager_indexes_on_users_and_locations.php new file mode 100644 index 0000000000..cc2963fecc --- /dev/null +++ b/database/migrations/2024_11_07_113631_improve_manager_indexes_on_users_and_locations.php @@ -0,0 +1,39 @@ +dropIndex(['manager_id']); + $table->index(['manager_id','deleted_at']); + }); + Schema::table('users', function (Blueprint $table) { + $table->dropIndex(['manager_id']); + $table->index(['manager_id','deleted_at']); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('locations', function (Blueprint $table) { + $table->dropIndex(['manager_id','deleted_at']); + $table->index(['manager_id']); + }); + Schema::table('users', function (Blueprint $table) { + $table->dropIndex(['manager_id','deleted_at']); + $table->index(['manager_id']); + }); + } +}; diff --git a/database/migrations/2025_01_06_210534_change_report_templates_options_to_column_text_field.php b/database/migrations/2025_01_06_210534_change_report_templates_options_to_column_text_field.php new file mode 100644 index 0000000000..024214ce3a --- /dev/null +++ b/database/migrations/2025_01_06_210534_change_report_templates_options_to_column_text_field.php @@ -0,0 +1,39 @@ +text('options')->nullable(false)->change(); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('report_templates', function (Blueprint $table) { + // Instead of attempting to roll this back to json let's just + // keep it as text since that works for mysql, mariadb, and sqlite. + }); + } +}; diff --git a/database/migrations/2025_01_07_172419_fix_label_types_on_settings_table.php b/database/migrations/2025_01_07_172419_fix_label_types_on_settings_table.php new file mode 100644 index 0000000000..dbbde4f88e --- /dev/null +++ b/database/migrations/2025_01_07_172419_fix_label_types_on_settings_table.php @@ -0,0 +1,43 @@ +where('label2_2d_type', 'default')->update([ + 'label2_2d_type' => 'QRCODE', + ]); + + DB::table('settings')->where('label2_1d_type', 'default')->update([ + 'label2_1d_type' => 'C128', + ]); + + DB::table('settings')->whereNull('label2_2d_type')->orWhere('label2_2d_type', '')->update([ + 'label2_2d_type' => 'none', + ]); + + DB::table('settings')->whereNull('label2_1d_type')->orWhere('label2_1d_type', '')->update([ + 'label2_1d_type' => 'none', + ]); + + Schema::table('settings', function (Blueprint $table) { + $table->string('label2_2d_type')->default('QRCODE')->nullable(false)->change(); + $table->string('label2_1d_type')->default('C128')->nullable(false)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/migrations/2025_02_10_230155_add_notes_to_locations_companies_categories_manufacturers_groups.php b/database/migrations/2025_02_10_230155_add_notes_to_locations_companies_categories_manufacturers_groups.php new file mode 100644 index 0000000000..bbffc50305 --- /dev/null +++ b/database/migrations/2025_02_10_230155_add_notes_to_locations_companies_categories_manufacturers_groups.php @@ -0,0 +1,60 @@ +text('notes')->nullable()->default(null); + }); + + Schema::table('companies', function (Blueprint $table) { + $table->text('notes')->nullable()->default(null); + }); + + Schema::table('categories', function (Blueprint $table) { + $table->text('notes')->nullable()->default(null); + }); + + Schema::table('manufacturers', function (Blueprint $table) { + $table->text('notes')->nullable()->default(null); + }); + + Schema::table('permission_groups', function (Blueprint $table) { + $table->text('notes')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('locations', function (Blueprint $table) { + $table->dropColumn('notes'); + }); + + Schema::table('companies', function (Blueprint $table) { + $table->dropColumn('notes'); + }); + + Schema::table('categories', function (Blueprint $table) { + $table->dropColumn('notes'); + }); + + Schema::table('manufacturers', function (Blueprint $table) { + $table->dropColumn('notes'); + }); + + Schema::table('permission_groups', function (Blueprint $table) { + $table->dropColumn('notes'); + }); + } +}; diff --git a/database/migrations/2025_02_22_144518_add_checkin_checkout_to_customfields.php b/database/migrations/2025_02_22_144518_add_checkin_checkout_to_customfields.php new file mode 100644 index 0000000000..28aceee43b --- /dev/null +++ b/database/migrations/2025_02_22_144518_add_checkin_checkout_to_customfields.php @@ -0,0 +1,30 @@ +boolean('display_checkin')->default(0); + $table->boolean('display_checkout')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('custom_fields', function (Blueprint $table) { + $table->dropColumn('display_checkin'); + $table->dropColumn('display_checkout'); + }); + } +}; diff --git a/database/migrations/2025_02_26_153413_add_ldap_invert_active_flag_to_setting_table.php b/database/migrations/2025_02_26_153413_add_ldap_invert_active_flag_to_setting_table.php new file mode 100644 index 0000000000..7ca354bfea --- /dev/null +++ b/database/migrations/2025_02_26_153413_add_ldap_invert_active_flag_to_setting_table.php @@ -0,0 +1,28 @@ +boolean('ldap_invert_active_flag')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('ldap_invert_active_flag'); + }); + } +}; diff --git a/database/migrations/2025_03_04_231256_purge_action_logs_of_report_template_activity.php b/database/migrations/2025_03_04_231256_purge_action_logs_of_report_template_activity.php new file mode 100644 index 0000000000..150df41a48 --- /dev/null +++ b/database/migrations/2025_03_04_231256_purge_action_logs_of_report_template_activity.php @@ -0,0 +1,26 @@ +where("item_type", ReportTemplate::class) + ->whereIn("action_type", ["create", "update", "delete"]) + ->delete(); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // nothing to do here... + } +}; diff --git a/database/migrations/2025_03_06_152922_copy_created_at_to_action_date_in_action_logs.php b/database/migrations/2025_03_06_152922_copy_created_at_to_action_date_in_action_logs.php new file mode 100644 index 0000000000..8ce7de13bd --- /dev/null +++ b/database/migrations/2025_03_06_152922_copy_created_at_to_action_date_in_action_logs.php @@ -0,0 +1,31 @@ +index(['action_date']); + }); + + DB::update('update '.DB::getTablePrefix().'action_logs set action_date = created_at where created_at IS NOT NULL and action_date IS NULL'); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + + } +}; diff --git a/database/migrations/2025_03_12_184851_purge_action_logs_table_of_consumable_checkin_entries.php b/database/migrations/2025_03_12_184851_purge_action_logs_table_of_consumable_checkin_entries.php new file mode 100644 index 0000000000..6f2b1324bf --- /dev/null +++ b/database/migrations/2025_03_12_184851_purge_action_logs_table_of_consumable_checkin_entries.php @@ -0,0 +1,30 @@ +where([ + 'action_type' => 'checkin from', + 'note' => 'Bulk checkin items', + 'item_type' => Consumable::class, + ]) + ->delete(); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // nothing to do here... + } +}; diff --git a/database/migrations/2025_04_02_113438_add_acceptance_pdf_logo_to_settings.php b/database/migrations/2025_04_02_113438_add_acceptance_pdf_logo_to_settings.php new file mode 100644 index 0000000000..26ce894e58 --- /dev/null +++ b/database/migrations/2025_04_02_113438_add_acceptance_pdf_logo_to_settings.php @@ -0,0 +1,28 @@ +char('acceptance_pdf_logo')->after('label_logo')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('acceptance_pdf_logo'); + }); + } +}; diff --git a/database/migrations/2025_04_07_145418_add_custom_fields_to_audit.php b/database/migrations/2025_04_07_145418_add_custom_fields_to_audit.php new file mode 100644 index 0000000000..d5594fc035 --- /dev/null +++ b/database/migrations/2025_04_07_145418_add_custom_fields_to_audit.php @@ -0,0 +1,28 @@ +boolean('display_audit')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('custom_fields', function (Blueprint $table) { + $table->dropColumn('display_audit'); + }); + } +}; diff --git a/database/migrations/2025_04_22_170731_add_empty_row_count_to_settings_table.php b/database/migrations/2025_04_22_170731_add_empty_row_count_to_settings_table.php new file mode 100644 index 0000000000..49b228b42d --- /dev/null +++ b/database/migrations/2025_04_22_170731_add_empty_row_count_to_settings_table.php @@ -0,0 +1,21 @@ +unsignedInteger('label2_empty_row_count')->default(0)->after('label2_fields'); + }); + } + + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('label2_empty_row_count'); + }); + } +}; diff --git a/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php b/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php new file mode 100644 index 0000000000..be14ea04ea --- /dev/null +++ b/database/migrations/2025_05_20_190317_repopulate_webhook_selected_setting.php @@ -0,0 +1,41 @@ +first(); + + if ($settings) { + /** If webhook settings were cleared via the integration settings page, + * the webhook_selected was cleared as well when it should have reset to "slack". + */ + if ( + empty($settings->webhook_selected) && + (empty($settings->webhook_botname) && empty($settings->webhook_channel) && empty($settings->webhook_endpoint)) + ) { + DB::table('settings')->update(['webhook_selected' => 'slack']); + } + + /** If webhook settings were cleared via the integration settings page, + * then slack settings were re-added; then webhook_selected was not being set to "slack" as needed. + */ + if (str_contains($settings->webhook_endpoint, 'slack.com')) { + DB::table('settings')->update(['webhook_selected' => 'slack']); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php b/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php new file mode 100644 index 0000000000..d28115a6d2 --- /dev/null +++ b/database/migrations/2025_06_02_233556_add_admin_cc_always_to_settings_table.php @@ -0,0 +1,27 @@ +boolean('admin_cc_always')->after('admin_cc_email')->default(1); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->dropColumn('admin_cc_always'); + }); + } +}; diff --git a/database/migrations/2025_06_03_053438_fix_assigned_type_without_assigned_to.php b/database/migrations/2025_06_03_053438_fix_assigned_type_without_assigned_to.php new file mode 100644 index 0000000000..9d6a1c8046 --- /dev/null +++ b/database/migrations/2025_06_03_053438_fix_assigned_type_without_assigned_to.php @@ -0,0 +1,23 @@ +whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + + } +}; diff --git a/database/migrations/2025_06_04_101736_add_deleted_at_index_to_action_logs.php b/database/migrations/2025_06_04_101736_add_deleted_at_index_to_action_logs.php new file mode 100644 index 0000000000..c9f853599b --- /dev/null +++ b/database/migrations/2025_06_04_101736_add_deleted_at_index_to_action_logs.php @@ -0,0 +1,28 @@ +index('deleted_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('action_logs', function (Blueprint $table) { + $table->dropIndex('deleted_at'); + }); + } +}; diff --git a/database/migrations/2025_06_06_155058_make_supplier_id_nullable.php b/database/migrations/2025_06_06_155058_make_supplier_id_nullable.php new file mode 100644 index 0000000000..f71eeb8d96 --- /dev/null +++ b/database/migrations/2025_06_06_155058_make_supplier_id_nullable.php @@ -0,0 +1,27 @@ +integer('supplier_id')->nullable()->default(null)->change(); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/seeders/AccessorySeeder.php b/database/seeders/AccessorySeeder.php index 31f0c478d4..5f4cca8cf6 100644 --- a/database/seeders/AccessorySeeder.php +++ b/database/seeders/AccessorySeeder.php @@ -16,7 +16,7 @@ class AccessorySeeder extends Seeder public function run() { Accessory::truncate(); - DB::table('accessories_users')->truncate(); + DB::table('accessories_checkout')->truncate(); if (! Location::count()) { $this->call(LocationSeeder::class); @@ -35,25 +35,25 @@ class AccessorySeeder extends Seeder Accessory::factory()->appleUsbKeyboard()->create([ 'location_id' => $locationIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Accessory::factory()->appleBtKeyboard()->create([ 'location_id' => $locationIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Accessory::factory()->appleMouse()->create([ 'location_id' => $locationIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Accessory::factory()->microsoftMouse()->create([ 'location_id' => $locationIds->random(), 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); diff --git a/database/seeders/ActionlogSeeder.php b/database/seeders/ActionlogSeeder.php index 28191d53b0..3903980007 100644 --- a/database/seeders/ActionlogSeeder.php +++ b/database/seeders/ActionlogSeeder.php @@ -27,16 +27,16 @@ class ActionlogSeeder extends Seeder Actionlog::factory() ->count(300) ->assetCheckoutToUser() - ->create(['user_id' => $admin->id]); + ->create(['created_by' => $admin->id]); Actionlog::factory() ->count(100) ->assetCheckoutToLocation() - ->create(['user_id' => $admin->id]); + ->create(['created_by' => $admin->id]); Actionlog::factory() ->count(20) ->licenseCheckoutToUser() - ->create(['user_id' => $admin->id]); + ->create(['created_by' => $admin->id]); } } diff --git a/database/seeders/AssetModelSeeder.php b/database/seeders/AssetModelSeeder.php index 1fc0b28cd3..f2902ffe7c 100755 --- a/database/seeders/AssetModelSeeder.php +++ b/database/seeders/AssetModelSeeder.php @@ -17,34 +17,34 @@ class AssetModelSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); // Laptops - AssetModel::factory()->count(1)->mbp13Model()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->mbpAirModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->surfaceModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->xps13Model()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->spectreModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->zenbookModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->yogaModel()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->mbp13Model()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->mbpAirModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->surfaceModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->xps13Model()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->spectreModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->zenbookModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->yogaModel()->create(['created_by' => $admin->id]); // Desktops - AssetModel::factory()->count(1)->macproModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->lenovoI5Model()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->optiplexModel()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->macproModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->lenovoI5Model()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->optiplexModel()->create(['created_by' => $admin->id]); // Conference Phones - AssetModel::factory()->count(1)->polycomModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->polycomcxModel()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->polycomModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->polycomcxModel()->create(['created_by' => $admin->id]); // Tablets - AssetModel::factory()->count(1)->ipadModel()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->tab3Model()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->ipadModel()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->tab3Model()->create(['created_by' => $admin->id]); // Phones - AssetModel::factory()->count(1)->iphone11Model()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->iphone12Model()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->iphone11Model()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->iphone12Model()->create(['created_by' => $admin->id]); // Displays - AssetModel::factory()->count(1)->ultrafine()->create(['user_id' => $admin->id]); - AssetModel::factory()->count(1)->ultrasharp()->create(['user_id' => $admin->id]); + AssetModel::factory()->count(1)->ultrafine()->create(['created_by' => $admin->id]); + AssetModel::factory()->count(1)->ultrasharp()->create(['created_by' => $admin->id]); $src = public_path('/img/demo/models/'); $dst = 'models'.'/'; diff --git a/database/seeders/AssetSeeder.php b/database/seeders/AssetSeeder.php index 5fdc09bdb3..9d21e7f9fa 100644 --- a/database/seeders/AssetSeeder.php +++ b/database/seeders/AssetSeeder.php @@ -25,7 +25,7 @@ class AssetSeeder extends Seeder $this->ensureLocationsSeeded(); $this->ensureSuppliersSeeded(); - $this->admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); + $this->adminuser = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); $this->locationIds = Location::all()->pluck('id'); $this->supplierIds = Supplier::all()->pluck('id'); @@ -82,7 +82,7 @@ class AssetSeeder extends Seeder return fn($sequence) => [ 'rtd_location_id' => $this->locationIds->random(), 'supplier_id' => $this->supplierIds->random(), - 'user_id' => $this->admin->id, + 'created_by' => $this->adminuser->id, ]; } } diff --git a/database/seeders/CategorySeeder.php b/database/seeders/CategorySeeder.php index da542cff9e..137dea2aba 100755 --- a/database/seeders/CategorySeeder.php +++ b/database/seeders/CategorySeeder.php @@ -14,20 +14,20 @@ class CategorySeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); - Category::factory()->count(1)->assetLaptopCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetDesktopCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetTabletCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetMobileCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetDisplayCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetVoipCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->assetConferenceCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->accessoryKeyboardCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->accessoryMouseCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->consumablePaperCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->consumableInkCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->componentHddCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->componentRamCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->licenseGraphicsCategory()->create(['user_id' => $admin->id]); - Category::factory()->count(1)->licenseOfficeCategory()->create(['user_id' => $admin->id]); + Category::factory()->count(1)->assetLaptopCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetDesktopCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetTabletCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetMobileCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetDisplayCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetVoipCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->assetConferenceCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->accessoryKeyboardCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->accessoryMouseCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->consumablePaperCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->consumableInkCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->componentHddCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->componentRamCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->licenseGraphicsCategory()->create(['created_by' => $admin->id]); + Category::factory()->count(1)->licenseOfficeCategory()->create(['created_by' => $admin->id]); } } diff --git a/database/seeders/ConsumableSeeder.php b/database/seeders/ConsumableSeeder.php index 42527e1df8..de20141c7a 100644 --- a/database/seeders/ConsumableSeeder.php +++ b/database/seeders/ConsumableSeeder.php @@ -16,8 +16,8 @@ class ConsumableSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); - Consumable::factory()->count(1)->cardstock()->create(['user_id' => $admin->id]); - Consumable::factory()->count(1)->paper()->create(['user_id' => $admin->id]); - Consumable::factory()->count(1)->ink()->create(['user_id' => $admin->id]); + Consumable::factory()->count(1)->cardstock()->create(['created_by' => $admin->id]); + Consumable::factory()->count(1)->paper()->create(['created_by' => $admin->id]); + Consumable::factory()->count(1)->ink()->create(['created_by' => $admin->id]); } } diff --git a/database/seeders/DepartmentSeeder.php b/database/seeders/DepartmentSeeder.php index 7406b97afb..7f20ee8cb9 100644 --- a/database/seeders/DepartmentSeeder.php +++ b/database/seeders/DepartmentSeeder.php @@ -23,32 +23,32 @@ class DepartmentSeeder extends Seeder Department::factory()->count(1)->hr()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->engineering()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->marketing()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->client()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->product()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Department::factory()->count(1)->silly()->create([ 'location_id' => $locationIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); } } diff --git a/database/seeders/DepreciationSeeder.php b/database/seeders/DepreciationSeeder.php index 349d8aff53..ed78c0b115 100644 --- a/database/seeders/DepreciationSeeder.php +++ b/database/seeders/DepreciationSeeder.php @@ -14,8 +14,8 @@ class DepreciationSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); - Depreciation::factory()->count(1)->computer()->create(['user_id' => $admin->id]); - Depreciation::factory()->count(1)->display()->create(['user_id' => $admin->id]); - Depreciation::factory()->count(1)->mobilePhones()->create(['user_id' => $admin->id]); + Depreciation::factory()->count(1)->computer()->create(['created_by' => $admin->id]); + Depreciation::factory()->count(1)->display()->create(['created_by' => $admin->id]); + Depreciation::factory()->count(1)->mobilePhones()->create(['created_by' => $admin->id]); } } diff --git a/database/seeders/LicenseSeeder.php b/database/seeders/LicenseSeeder.php index 4868dd41e1..94e1e05cc1 100644 --- a/database/seeders/LicenseSeeder.php +++ b/database/seeders/LicenseSeeder.php @@ -8,6 +8,7 @@ use App\Models\LicenseSeat; use App\Models\Supplier; use App\Models\User; use Illuminate\Database\Seeder; +use Illuminate\Support\Facades\Log; class LicenseSeeder extends Seeder { @@ -20,7 +21,20 @@ class LicenseSeeder extends Seeder $this->call(CategorySeeder::class); } - $categoryIds = Category::all()->pluck('id'); + $categories = Category::where('category_type', 'license')->get(); + + $graphicsSoftwareCategory = $categories->first(fn($category) => $category->name === 'Graphics Software'); + $officeSoftwareCategory = $categories->first(fn($category) => $category->name === 'Office Software'); + + if (!$graphicsSoftwareCategory) { + Log::info('Graphics Software category not created. Using random category for seeding.'); + $graphicsSoftwareCategory = Category::inRandomOrder()->first(); + } + + if (!$officeSoftwareCategory) { + Log::info('Office Software category not created. Using random category for seeding.'); + $officeSoftwareCategory = Category::inRandomOrder()->first(); + } if (! Supplier::count()) { $this->call(SupplierSeeder::class); @@ -31,27 +45,27 @@ class LicenseSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); License::factory()->count(1)->photoshop()->create([ - 'category_id' => $categoryIds->random(), + 'category_id' => $graphicsSoftwareCategory->id, 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); License::factory()->count(1)->acrobat()->create([ - 'category_id' => $categoryIds->random(), + 'category_id' => $officeSoftwareCategory->id, 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); License::factory()->count(1)->indesign()->create([ - 'category_id' => $categoryIds->random(), + 'category_id' => $graphicsSoftwareCategory->id, 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); License::factory()->count(1)->office()->create([ - 'category_id' => $categoryIds->random(), + 'category_id' => $officeSoftwareCategory->id, 'supplier_id' => $supplierIds->random(), - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); } } diff --git a/database/seeders/ManufacturerSeeder.php b/database/seeders/ManufacturerSeeder.php index cbd70f4c3d..cdf7596d45 100644 --- a/database/seeders/ManufacturerSeeder.php +++ b/database/seeders/ManufacturerSeeder.php @@ -16,17 +16,21 @@ class ManufacturerSeeder extends Seeder $admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create(); - Manufacturer::factory()->count(1)->apple()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->microsoft()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->dell()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->asus()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->hp()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->lenovo()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->lg()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->polycom()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->adobe()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->avery()->create(['user_id' => $admin->id]); - Manufacturer::factory()->count(1)->crucial()->create(['user_id' => $admin->id]); + Manufacturer::factory()->count(1)->apple()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->microsoft()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->dell()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->asus()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->hp()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->lenovo()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->lg()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->polycom()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->adobe()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->avery()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->crucial()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->samsung()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->google()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->huawei()->create(['created_by' => $admin->id]); + Manufacturer::factory()->count(1)->sony()->create(['created_by' => $admin->id]); $src = public_path('/img/demo/manufacturers/'); $dst = 'manufacturers'.'/'; diff --git a/database/seeders/SettingsSeeder.php b/database/seeders/SettingsSeeder.php index c90ce52d07..37069ce378 100644 --- a/database/seeders/SettingsSeeder.php +++ b/database/seeders/SettingsSeeder.php @@ -19,12 +19,12 @@ class SettingsSeeder extends Seeder $settings->logo = 'snipe-logo.png'; $settings->alert_email = 'service@snipe-it.io'; $settings->header_color = null; - $settings->barcode_type = 'QRCODE'; + $settings->label2_2d_type = 'QRCODE'; $settings->default_currency = 'USD'; $settings->brand = 3; $settings->ldap_enabled = 0; $settings->full_multiple_companies_support = 0; - $settings->alt_barcode = 'C128'; + $settings->label2_1d_type = 'C128'; $settings->skin = ''; $settings->email_domain = 'example.org'; $settings->email_format = 'filastname'; @@ -36,6 +36,7 @@ class SettingsSeeder extends Seeder $settings->version_footer = 'on'; $settings->support_footer = 'on'; $settings->pwd_secure_min = '8'; + $settings->default_avatar = 'default.png'; $settings->save(); if ($user = User::where('username', '=', 'admin')->first()) { diff --git a/database/seeders/StatuslabelSeeder.php b/database/seeders/StatuslabelSeeder.php index fbc6a9fb66..be36e7790d 100755 --- a/database/seeders/StatuslabelSeeder.php +++ b/database/seeders/StatuslabelSeeder.php @@ -16,22 +16,22 @@ class StatuslabelSeeder extends Seeder Statuslabel::factory()->rtd()->create([ 'name' => 'Ready to Deploy', - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Statuslabel::factory()->pending()->create([ 'name' => 'Pending', - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); Statuslabel::factory()->archived()->create([ 'name' => 'Archived', - 'user_id' => $admin->id, + 'created_by' => $admin->id, ]); - Statuslabel::factory()->outForDiagnostics()->create(['user_id' => $admin->id]); - Statuslabel::factory()->outForRepair()->create(['user_id' => $admin->id]); - Statuslabel::factory()->broken()->create(['user_id' => $admin->id]); - Statuslabel::factory()->lost()->create(['user_id' => $admin->id]); + Statuslabel::factory()->outForDiagnostics()->create(['created_by' => $admin->id]); + Statuslabel::factory()->outForRepair()->create(['created_by' => $admin->id]); + Statuslabel::factory()->broken()->create(['created_by' => $admin->id]); + Statuslabel::factory()->lost()->create(['created_by' => $admin->id]); } } diff --git a/dev.docker-compose.yml b/dev.docker-compose.yml index 15272ce5c5..0cd8139c0e 100644 --- a/dev.docker-compose.yml +++ b/dev.docker-compose.yml @@ -1,3 +1,5 @@ +# Compose file to spin up a local Snipe-IT for development. + version: '3' services: @@ -7,44 +9,40 @@ services: dockerfile: Dockerfile.alpine container_name: snipeit ports: - - "8000:80" - volumes: - - ./storage/logs:/var/www/html/storage/logs + - "8000:80" depends_on: - - mariadb - - redis + redis: + # The default needs to be stated. + condition: service_started + mariadb: + condition: service_healthy + restart: true env_file: - - .env.docker - networks: - - snipeit-backend + - .env.dev.docker mariadb: - image: mariadb:10.6.4-focal + image: mariadb:11.4.7 volumes: - - db:/var/lib/mysql + - db:/var/lib/mysql env_file: - - .env.docker - networks: - - snipeit-backend + - .env.dev.docker ports: - "3306:3306" + healthcheck: + # https://mariadb.com/kb/en/using-healthcheck-sh/#compose-file-example + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 5s + timeout: 2s + retries: 5 redis: - image: redis:6.2.5-buster - networks: - - snipeit-backend + image: redis:7.4.3 mailhog: image: mailhog/mailhog:v1.0.1 ports: - # - 1025:1025 - - "8025:8025" - networks: - - snipeit-backend - + # - 1025:1025 + - "8025:8025" volumes: db: {} - -networks: - snipeit-backend: {} diff --git a/docker-compose.yml b/docker-compose.yml index c7c1983a4e..d874d2e008 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,13 @@ +# Compose file for production. + volumes: db_data: storage: services: app: - image: snipe/snipe-it:${APP_VERSION:-v6.4.1} - restart: always + image: snipe/snipe-it:${APP_VERSION:-latest} + restart: unless-stopped volumes: - storage:/var/lib/snipeit ports: @@ -18,8 +20,8 @@ services: - .env db: - image: mariadb:10.6.4-focal - restart: always + image: mariadb:11.4.7 + restart: unless-stopped volumes: - db_data:/var/lib/mysql environment: @@ -28,7 +30,8 @@ services: MYSQL_PASSWORD: ${DB_PASSWORD} MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} healthcheck: - test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD + # https://mariadb.com/kb/en/using-healthcheck-sh/#compose-file-example + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 5s timeout: 1s retries: 5 diff --git a/docker/entrypoint_alpine.sh b/docker/entrypoint_alpine.sh deleted file mode 100644 index 783b50b69c..0000000000 --- a/docker/entrypoint_alpine.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -# fix key if needed -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 - exit -fi - -#if [ ! -f /var/lib/snipeit/ssl/snipeit-ssl.crt -o ! -f /var/lib/snipeit/ssl/snipeit-ssl.key ] -#then - # rm /etc/apache2/conf.d/ssl.conf && rm /etc/apache2/conf.d/default-ssl.conf -#fi - -# create data directories -for dir in \ - 'data/private_uploads' \ - 'data/uploads/accessories' \ - 'data/uploads/avatars' \ - 'data/uploads/barcodes' \ - 'data/uploads/categories' \ - 'data/uploads/companies' \ - 'data/uploads/components' \ - 'data/uploads/consumables' \ - 'data/uploads/departments' \ - 'data/uploads/locations' \ - 'data/uploads/manufacturers' \ - 'data/uploads/models' \ - 'data/uploads/suppliers' \ - 'dumps' \ - 'keys' -do - [ ! -d "/var/lib/snipeit/$dir" ] && mkdir -p "/var/lib/snipeit/$dir" -done - -chown -R apache:root /var/lib/snipeit/data/* -chown -R apache:root /var/lib/snipeit/dumps -chown -R apache:root /var/lib/snipeit/keys - -# If the Oauth DB files are not present copy the vendor files over to the db migrations -if [ ! -f "/var/www/html/database/migrations/*create_oauth*" ] -then - cp -a /var/www/html/vendor/laravel/passport/database/migrations/* /var/www/html/database/migrations/ -fi - -if [ "${SESSION_DRIVER}" == "database" ] -then - cp -a /var/www/html/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub /var/www/html/database/migrations/2021_05_06_0000_create_sessions_table.php -fi - -php artisan migrate --force -php artisan config:clear -php artisan config:cache - -chown -R apache:root /var/www/html/storage/logs/laravel.log - -export APACHE_LOG_DIR=/var/log/apache2 -exec httpd -DNO_DETACH < /dev/null diff --git a/docker/startup.sh b/docker/startup.sh index 62002a2ba0..5329e37c2c 100644 --- a/docker/startup.sh +++ b/docker/startup.sh @@ -1,7 +1,48 @@ #!/bin/bash +# Cribbed from nextcloud docker official repo +# https://github.com/nextcloud/docker/blob/master/docker-entrypoint.sh +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + local varValue=$(env | grep -E "^${var}=" | sed -E -e "s/^${var}=//") + local fileVarValue=$(env | grep -E "^${fileVar}=" | sed -E -e "s/^${fileVar}=//") + if [ -n "${varValue}" ] && [ -n "${fileVarValue}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + if [ -n "${varValue}" ]; then + export "$var"="${varValue}" + elif [ -n "${fileVarValue}" ]; then + export "$var"="$(cat "${fileVarValue}")" + elif [ -n "${def}" ]; then + export "$var"="$def" + fi + unset "$fileVar" +} + +# Add docker secrets support for the variables below: +file_env APP_KEY +file_env DB_HOST +file_env DB_PORT +file_env DB_DATABASE +file_env DB_USERNAME +file_env DB_PASSWORD +file_env REDIS_HOST +file_env REDIS_PASSWORD +file_env REDIS_PORT +file_env MAIL_HOST +file_env MAIL_PORT +file_env MAIL_USERNAME +file_env MAIL_PASSWORD + # fix key if needed -if [ -z "$APP_KEY" ] +if [ -z "$APP_KEY" -a -z "$APP_KEY_FILE" ] then echo "Please re-run this container with an environment variable \$APP_KEY" echo "An example APP_KEY you could use is: " @@ -18,7 +59,7 @@ fi # create data directories # Note: Keep in sync with expected directories by the app -# https://github.com/snipe/snipe-it/blob/master/app/Console/Commands/RestoreFromBackup.php#L232 +# https://github.com/grokability/snipe-it/blob/master/app/Console/Commands/RestoreFromBackup.php#L232 for dir in \ 'data/private_uploads' \ 'data/private_uploads/assets' \ @@ -59,9 +100,13 @@ chown -R docker:root /var/www/html/storage/framework/cache # Fix php settings if [ -v "PHP_UPLOAD_LIMIT" ] then - echo "Changing upload limit to ${PHP_UPLOAD_LIMIT}" - sed -i "s/^upload_max_filesize.*/upload_max_filesize = ${PHP_UPLOAD_LIMIT}M/" /etc/php/*/apache2/php.ini - sed -i "s/^post_max_size.*/post_max_size = ${PHP_UPLOAD_LIMIT}M/" /etc/php/*/apache2/php.ini + find /etc/php -type f -name php.ini | while IFS= read -r ini; do + echo "Changing upload limit to ${PHP_UPLOAD_LIMIT}M in $ini" + sed -i \ + -e "s/^;\? *upload_max_filesize *=.*/upload_max_filesize = ${PHP_UPLOAD_LIMIT}M/" \ + -e "s/^;\? *post_max_size *=.*/post_max_size = ${PHP_UPLOAD_LIMIT}M/" \ + "$ini" + done fi # If the Oauth DB files are not present copy the vendor files over to the db migrations @@ -79,4 +124,9 @@ php artisan migrate --force php artisan config:clear php artisan config:cache +# we do this after the artisan commands to ensure that if the laravel +# log got created by root, we set the permissions back +touch /var/www/html/storage/logs/laravel.log +chown -R docker:root /var/www/html/storage/logs/laravel.log + exec supervisord -c /supervisord.conf diff --git a/docker/startup_alpine.sh b/docker/startup_alpine.sh new file mode 100644 index 0000000000..bb9574f387 --- /dev/null +++ b/docker/startup_alpine.sh @@ -0,0 +1,115 @@ +#!/bin/sh + +# Cribbed from nextcloud docker official repo +# https://github.com/nextcloud/docker/blob/master/docker-entrypoint.sh +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + local varValue=$(env | grep -E "^${var}=" | sed -E -e "s/^${var}=//") + local fileVarValue=$(env | grep -E "^${fileVar}=" | sed -E -e "s/^${fileVar}=//") + if [ -n "${varValue}" ] && [ -n "${fileVarValue}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + if [ -n "${varValue}" ]; then + export "$var"="${varValue}" + elif [ -n "${fileVarValue}" ]; then + export "$var"="$(cat "${fileVarValue}")" + elif [ -n "${def}" ]; then + export "$var"="$def" + fi + unset "$fileVar" +} + +# Add docker secrets support for the variables below: +file_env APP_KEY +file_env DB_HOST +file_env DB_PORT +file_env DB_DATABASE +file_env DB_USERNAME +file_env DB_PASSWORD +file_env REDIS_HOST +file_env REDIS_PASSWORD +file_env REDIS_PORT +file_env MAIL_HOST +file_env MAIL_PORT +file_env MAIL_USERNAME +file_env MAIL_PASSWORD + +# fix key if needed +if [ -z "$APP_KEY" -a -z "$APP_KEY_FILE" ] +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 + exit +fi + +#if [ ! -f /var/lib/snipeit/ssl/snipeit-ssl.crt -o ! -f /var/lib/snipeit/ssl/snipeit-ssl.key ] +#then + # rm /etc/apache2/conf.d/ssl.conf && rm /etc/apache2/conf.d/default-ssl.conf +#fi + +# create data directories +for dir in \ + 'data/private_uploads' \ + 'data/uploads/accessories' \ + 'data/uploads/avatars' \ + 'data/uploads/barcodes' \ + 'data/uploads/categories' \ + 'data/uploads/companies' \ + 'data/uploads/components' \ + 'data/uploads/consumables' \ + 'data/uploads/departments' \ + 'data/uploads/locations' \ + 'data/uploads/manufacturers' \ + 'data/uploads/models' \ + 'data/uploads/suppliers' \ + 'dumps' \ + 'keys' +do + [ ! -d "/var/lib/snipeit/$dir" ] && mkdir -p "/var/lib/snipeit/$dir" +done + +chown -R apache:root /var/lib/snipeit/data/* +chown -R apache:root /var/lib/snipeit/dumps +chown -R apache:root /var/lib/snipeit/keys +chown -R apache:root /var/www/html/storage/framework/cache + +# Fix php settings +if [ ! -z "${PHP_UPLOAD_LIMIT}" ] +then + find /etc -type f -name php.ini | while IFS= read -r ini; do + echo "Changing upload limit to ${PHP_UPLOAD_LIMIT}M in $ini" + sed -i \ + -e "s/^;\? *upload_max_filesize *=.*/upload_max_filesize = ${PHP_UPLOAD_LIMIT}M/" \ + -e "s/^;\? *post_max_size *=.*/post_max_size = ${PHP_UPLOAD_LIMIT}M/" \ + "$ini" + done +fi + +# If the Oauth DB files are not present copy the vendor files over to the db migrations +if [ ! -f "/var/www/html/database/migrations/*create_oauth*" ] +then + cp -a /var/www/html/vendor/laravel/passport/database/migrations/* /var/www/html/database/migrations/ +fi + +if [ "${SESSION_DRIVER}" == "database" ] +then + cp -a /var/www/html/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub /var/www/html/database/migrations/2021_05_06_0000_create_sessions_table.php +fi + +php artisan migrate --force +php artisan config:clear +php artisan config:cache + +touch /var/www/html/storage/logs/laravel.log +chown -R apache:root /var/www/html/storage/logs/laravel.log + +export APACHE_LOG_DIR=/var/log/apache2 +exec httpd -DNO_DETACH < /dev/null diff --git a/docker/docker-entrypoint.sh b/docker/startup_alpine_fpm.sh similarity index 97% rename from docker/docker-entrypoint.sh rename to docker/startup_alpine_fpm.sh index 3b783419eb..981d8bb4f8 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/startup_alpine_fpm.sh @@ -99,10 +99,7 @@ then cp -a /var/www/html/vendor/laravel/passport/database/migrations/* /var/www/html/database/migrations/ fi -# Create laravel log file -touch /var/www/html/storage/logs/laravel.log # Add correct permissions for files and directories -chown www-data:www-data /var/www/html/storage/logs/laravel.log chown -R www-data:www-data \ /var/lib/snipeit/data \ /var/lib/snipeit/dumps \ @@ -114,6 +111,11 @@ php artisan migrate --force php artisan config:clear php artisan config:cache +# Create laravel log file +touch /var/www/html/storage/logs/laravel.log +# ensure it's owned by www:data in case it was created by root +chown www-data:www-data /var/www/html/storage/logs/laravel.log + echo [INFO docker entrypoint] End script execution -exec "$@" \ No newline at end of file +exec "$@" diff --git a/install.sh b/install.sh index 160c8c2ee1..601816ea5a 100755 --- a/install.sh +++ b/install.sh @@ -8,6 +8,6 @@ if [ "$(id -u)" != "0" ]; then fi fi -wget https://raw.githubusercontent.com/snipe/snipe-it/master/snipeit.sh +wget https://raw.githubusercontent.com/grokability/snipe-it/master/snipeit.sh chmod 744 snipeit.sh -./snipeit.sh 2>&1 | tee -a /var/log/snipeit-install.log \ No newline at end of file +./snipeit.sh 2>&1 | tee -a /var/log/snipeit-install.log diff --git a/pa11y.js b/pa11y.js new file mode 100644 index 0000000000..4d5a1dbba9 --- /dev/null +++ b/pa11y.js @@ -0,0 +1,24 @@ +const pa11y = require('pa11y'); + +pa11y('http://snipe-it.test', { + standard: "WCAG2AA", + level: "error", + defaults: { + "timeout": 500000, + "wait": 2000, + "ignore": [ + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18", + "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail", + ], + "viewport": { + "width": 1280, + "height": 1024 + }, + }, + actions: [ + 'set field #username to admin', + 'set field #password to password', + 'click element #submit', + 'wait for path to be /', + ] +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f8eddcc97b..8b6219adaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,8 +5,8 @@ "packages": { "": { "dependencies": { - "@fortawesome/fontawesome-free": "^6.5.2", - "acorn": "^8.11.2", + "@fortawesome/fontawesome-free": "^6.7.2", + "acorn": "^8.14.1", "acorn-import-assertions": "^1.9.0", "admin-lte": "^2.4.18", "ajv": "^6.12.6", @@ -15,28 +15,29 @@ "bootstrap-colorpicker": "^2.5.3", "bootstrap-datepicker": "^1.10.0", "bootstrap-less": "^3.3.8", - "bootstrap-table": "1.22.5", + "bootstrap-table": "1.24.1", + "canvas-confetti": "^1.9.3", "chart.js": "^2.9.4", "clipboard": "^2.0.11", "css-loader": "^5.0.0", "ekko-lightbox": "^5.1.1", "imagemin": "^8.0.1", "jquery-slimscroll": "^1.3.8", - "jquery-ui": "^1.13.3", - "jquery-validation": "^1.20.0", + "jquery-ui": "^1.14.1", + "jquery-validation": "^1.21.0", "jquery.iframe-transport": "^1.0.0", - "jspdf-autotable": "^3.8.2", - "less": "^4.2.0", + "jspdf-autotable": "^3.8.4", + "less": "^4.2.2", "less-loader": "^6.0", "list.js": "^1.5.0", "morris.js": "github:morrisjs/morris.js", - "papaparse": "5.4.1", + "papaparse": "5.5.2", "select2": "4.0.13", "sheetjs": "^2.0.0", "signature_pad": "^4.2.0", - "tableexport.jquery.plugin": "1.30.0", + "tableexport.jquery.plugin": "1.32.0", "tether": "^1.4.0", - "webpack": "^5.90.2" + "webpack": "^5.98.0" }, "devDependencies": { "all-contributors-cli": "^6.26.1", @@ -1918,9 +1919,9 @@ } }, "node_modules/@fortawesome/fontawesome-free": { - "version": "6.5.2", - "hasInstallScript": true, - "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz", + "integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==", "engines": { "node": ">=6" } @@ -2105,8 +2106,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.56.10", - "license": "MIT", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -2114,15 +2116,17 @@ }, "node_modules/@types/eslint-scope": { "version": "3.7.7", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "node_modules/@types/estree": { - "version": "1.0.5", - "license": "MIT" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/express": { "version": "4.17.21", @@ -2312,118 +2316,133 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "license": "MIT", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "license": "MIT" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "license": "MIT", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "license": "MIT", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "license": "MIT", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "license": "Apache-2.0", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "license": "MIT", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "license": "MIT", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "license": "MIT", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "license": "MIT", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "license": "MIT", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -2462,11 +2481,13 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "node_modules/@xtuc/long": { "version": "4.2.2", - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/accepts": { "version": "1.3.8", @@ -2481,8 +2502,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "license": "MIT", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "bin": { "acorn": "bin/acorn" }, @@ -2583,7 +2605,6 @@ }, "node_modules/ajv-formats": { "version": "2.1.1", - "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -2599,7 +2620,6 @@ }, "node_modules/ajv-formats/node_modules/ajv": { "version": "8.13.0", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2614,7 +2634,6 @@ }, "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", - "dev": true, "license": "MIT" }, "node_modules/ajv-keywords": { @@ -3683,9 +3702,9 @@ "license": "MIT" }, "node_modules/bootstrap-table": { - "version": "1.22.5", - "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.22.5.tgz", - "integrity": "sha512-iaQBfZzNuMRVughNYdonPGvgL6A7xfsruqYKaSuDuUWqQDTt8WvTBVwV61XiDv2aks7RaAQoZhoi2jo9nF6U7w==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.24.1.tgz", + "integrity": "sha512-que7o2Z6R0I3cfwfm6qg4XnoUK9A/8162HUErFYg3fGtzjk5OjLJx6Ji9p3oKz+IAIVvBCr9sqMqujEG47k3zA==", "peerDependencies": { "jquery": "3" } @@ -3945,7 +3964,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "funding": [ { "type": "opencollective", @@ -3960,12 +3981,11 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4072,7 +4092,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001616", + "version": "1.0.30001677", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", + "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", "funding": [ { "type": "opencollective", @@ -4086,8 +4108,16 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] + }, + "node_modules/canvas-confetti": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.9.3.tgz", + "integrity": "sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==", + "funding": { + "type": "donate", + "url": "https://www.paypal.me/kirilvatev" + } }, "node_modules/canvg": { "version": "3.0.10", @@ -5250,8 +5280,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.756", - "license": "ISC" + "version": "1.5.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz", + "integrity": "sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==" }, "node_modules/elliptic": { "version": "6.5.5", @@ -5291,8 +5322,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "license": "MIT", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5383,8 +5415,9 @@ "license": "MIT" }, "node_modules/escalade": { - "version": "3.1.2", - "license": "MIT", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } @@ -5592,6 +5625,21 @@ "version": "2.1.1", "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, "node_modules/fastclick": { "version": "1.0.6" }, @@ -7047,15 +7095,17 @@ "license": "BSD-2-Clause" }, "node_modules/jquery-ui": { - "version": "1.13.3", - "license": "MIT", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.14.1.tgz", + "integrity": "sha512-DhzsYH8VeIvOaxwi+B/2BCsFFT5EGjShdzOcm5DssWjtcpGWIMsn66rJciDA6jBruzNiLf1q0KvwMoX1uGNvnQ==", "dependencies": { - "jquery": ">=1.8.0 <4.0.0" + "jquery": ">=1.12.0 <5.0.0" } }, "node_modules/jquery-validation": { - "version": "1.20.0", - "license": "MIT", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.21.0.tgz", + "integrity": "sha512-xNot0rlUIgu7duMcQ5qb6MGkGL/Z1PQaRJQoZAURW9+a/2PGOUxY36o/WyNeP2T9R6jvWB8Z9lUVvvQWI/Zs5w==", "peerDependencies": { "jquery": "^1.7 || ^2.0 || ^3.1" } @@ -7160,8 +7210,9 @@ } }, "node_modules/jspdf-autotable": { - "version": "3.8.2", - "license": "MIT", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.4.tgz", + "integrity": "sha512-rSffGoBsJYX83iTRv8Ft7FhqfgEL2nLpGAIiqruEQQ3e4r0qdLFbPUB7N9HAle0I3XgpisvyW751VHCqKUVOgQ==", "peerDependencies": { "jspdf": "^2.5.1" } @@ -7429,8 +7480,9 @@ } }, "node_modules/less": { - "version": "4.2.0", - "license": "Apache-2.0", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.2.tgz", + "integrity": "sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==", "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -8089,8 +8141,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "license": "MIT" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -8327,9 +8380,9 @@ "license": "(MIT AND Zlib)" }, "node_modules/papaparse": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", - "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-PZXg8UuAc4PcVwLosEEDYjPyfWnTEhOrUfdv+3Bx+NuAb+5NhDmXzg5fHWmdCh1mP5p7JAZfFr3IMQfcntNAdA==" }, "node_modules/param-case": { "version": "3.0.4", @@ -8541,8 +8594,9 @@ "optional": true }, "node_modules/picocolors": { - "version": "1.0.0", - "license": "ISC" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -9478,7 +9532,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10381,8 +10434,9 @@ } }, "node_modules/tableexport.jquery.plugin": { - "version": "1.30.0", - "license": "MIT", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/tableexport.jquery.plugin/-/tableexport.jquery.plugin-1.32.0.tgz", + "integrity": "sha512-TiZpNFzazMv+gipe9u2JHgBL0guh/kloZDBA0vayWZp35nOSZkIq9XrqjxLc8oX57r7JP58QcM6qEfAgI3xEqw==", "dependencies": { "file-saver": ">=2.0.4", "html2canvas": ">=1.0.0", @@ -10394,14 +10448,16 @@ }, "node_modules/tapable": { "version": "2.2.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "engines": { "node": ">=6" } }, "node_modules/terser": { - "version": "5.31.0", - "license": "BSD-2-Clause", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -10416,14 +10472,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "license": "MIT", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -10447,6 +10504,55 @@ } } }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "license": "MIT" @@ -10694,7 +10800,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.15", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -10709,10 +10817,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -10857,19 +10964,19 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.91.0", - "license": "MIT", + "version": "5.98.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", + "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.21.10", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -10879,9 +10986,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", + "schema-utils": "^4.3.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", + "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, @@ -11164,6 +11271,55 @@ "source-map": "~0.6.1" } }, + "node_modules/webpack/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/webpack/node_modules/webpack-sources": { "version": "3.2.3", "license": "MIT", diff --git a/package.json b/package.json index 9f1ce36f2b..68687095c0 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "postcss": "^8.4.5" }, "dependencies": { - "@fortawesome/fontawesome-free": "^6.5.2", - "acorn": "^8.11.2", + "@fortawesome/fontawesome-free": "^6.7.2", + "acorn": "^8.14.1", "acorn-import-assertions": "^1.9.0", "admin-lte": "^2.4.18", "ajv": "^6.12.6", @@ -35,27 +35,28 @@ "bootstrap-colorpicker": "^2.5.3", "bootstrap-datepicker": "^1.10.0", "bootstrap-less": "^3.3.8", - "bootstrap-table": "1.22.5", + "bootstrap-table": "1.24.1", + "canvas-confetti": "^1.9.3", "chart.js": "^2.9.4", "clipboard": "^2.0.11", "css-loader": "^5.0.0", "ekko-lightbox": "^5.1.1", "imagemin": "^8.0.1", "jquery-slimscroll": "^1.3.8", - "jquery-ui": "^1.13.3", - "jquery-validation": "^1.20.0", + "jquery-ui": "^1.14.1", + "jquery-validation": "^1.21.0", "jquery.iframe-transport": "^1.0.0", - "jspdf-autotable": "^3.8.2", - "less": "^4.2.0", + "jspdf-autotable": "^3.8.4", + "less": "^4.2.2", "less-loader": "^6.0", "list.js": "^1.5.0", "morris.js": "github:morrisjs/morris.js", - "papaparse": "5.4.1", + "papaparse": "5.5.2", "select2": "4.0.13", "sheetjs": "^2.0.0", "signature_pad": "^4.2.0", - "tableexport.jquery.plugin": "1.30.0", + "tableexport.jquery.plugin": "1.32.0", "tether": "^1.4.0", - "webpack": "^5.90.2" + "webpack": "^5.98.0" } } diff --git a/phpunit.xml b/phpunit.xml index 9d4fa91144..30a78b7a63 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,21 +1,14 @@ - - - app/ - - ./tests/Unit @@ -28,9 +21,15 @@ + + + + app/ + + diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt index 5b8d0c144a..4dddddbe71 100644 --- a/public/.well-known/security.txt +++ b/public/.well-known/security.txt @@ -2,6 +2,6 @@ Contact: mailto:security@snipeitapp.com Expires: 2025-05-16T11:30:00.000Z Acknowledgments: https://snipeitapp.com/thanks Preferred-Languages: en-US, pt-PT, de-DE -Canonical: https://github.com/snipe/snipe-it/blob/master/public/.well-known/security.txt +Canonical: https://github.com/grokability/snipe-it/blob/master/public/.well-known/security.txt Policy: https://snipeitapp.com/security -Hiring: https://snipeitapp.com/company/careers \ No newline at end of file +Hiring: https://snipeitapp.com/company/careers diff --git a/public/css/build/app.css b/public/css/build/app.css index 2b8dc09873..f139c052cf 100644 --- a/public/css/build/app.css +++ b/public/css/build/app.css @@ -312,7 +312,19 @@ a.accordion-header { a.logo.no-hover a:hover { background-color: transparent; } -.required { +.index-block { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.index-block:hover { + overflow: visible; + white-space: normal; + height: auto; +} +input:required, +select:required, +textarea:required { border-right: 6px solid orange; } .sidebar-menu { @@ -395,7 +407,7 @@ img.navbar-brand-img, padding: 10px; background: #f4f4f4; margin-bottom: 3px; - border-left: 2px solid #e6e7e8; + border-inline: 2px solid #e6e7e8; color: #444; cursor: move; } @@ -686,8 +698,13 @@ a.link-danger:hover { a.logo.no-hover a:hover { background-color: transparent; } -.required { - border-right: 6px solid orange; +input:required, +select:required { + border-right: 5px solid orange; +} +select:required + .select2-container .select2-selection, +select:required + .select2-container .select2-selection .select2-selection--multiple { + border-right: 5px solid orange !important; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; @@ -697,18 +714,156 @@ body { font-size: 14px; white-space: normal; } +.modal-warning .modal-help { + color: #fff8af; +} .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading { z-index: 0 !important; } @media print { - a[href]:after { - content: none; + @page { + size: A4; + margin: 0mm; } .tab-content > .tab-pane { display: block !important; opacity: 1 !important; visibility: visible !important; } + .img-responsive { + width: 200px; + } + html, + body { + width: 1024px; + } + body { + margin: 0 auto; + line-height: 1em; + word-spacing: 1px; + letter-spacing: 0.2px; + font: 15px "Times New Roman", Times, serif; + background: white; + color: black; + width: 100%; + float: none; + } + /* avoid page-breaks inside a listingContainer*/ + .listingContainer { + page-break-inside: avoid; + } + h1 { + font: 28px "Times New Roman", Times, serif; + } + h2 { + font: 24px "Times New Roman", Times, serif; + } + h3 { + font: 20px "Times New Roman", Times, serif; + } + /* Improve colour contrast of links */ + a:link, + a:visited { + color: #781351; + } + /* URL */ + a:link, + a:visited { + background: transparent; + color: #333; + text-decoration: none; + } + a[href]:after { + content: "" !important; + } + a[href^="http://"] { + color: #000; + } + #header { + height: 75px; + font-size: 24pt; + color: black; + } + div.row-new-striped { + margin: 0px; + padding: 0px; + } + .pagination-detail, + .fixed-table-toolbar { + visibility: hidden; + } + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11, + .col-sm-12 .col-sm-pull-3 .col-sm-push-9 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } +} +.select2-selection__choice__remove { + color: white !important; +} +.select2-selection--multiple { + border-color: #d2d6de !important; + overflow-y: auto; +} +.select2-selection__choice { + border-radius: 0px !important; +} +.select2-search select2-search--inline { + height: 35px !important; + float: left; + margin: 0; +} +.select2-results__option { + padding: 5px; + -moz-user-select: none; + user-select: none; + -webkit-user-select: none; + margin: 0px; } img.navbar-brand-img, .navbar-brand > img { @@ -716,8 +871,10 @@ img.navbar-brand-img, padding: 5px 5px 5px 0; max-height: 50px; } -.input-daterange { - border-radius: 0px; +.input-daterange, +.input-daterange input:first-child, +.input-daterange input:last-child { + border-radius: 0px !important; } .btn.bg-maroon, .btn.bg-purple { @@ -796,20 +953,21 @@ h4 { background-color: #f9f9f9; border-top: 1px solid #dddddd; display: table-cell; + word-wrap: break-word; } .row-striped .row:nth-of-type(even) div { background: #FFFFFF; border-top: 1px solid #dddddd; display: table-cell; + word-wrap: break-word; } .row-new-striped { vertical-align: top; - line-height: 2.6; - padding: 0px; - margin-left: 20px; + padding: 3px; display: table; width: 100%; - padding-right: 20px; + word-wrap: break-word; + table-layout: fixed; } /** * NEW STRIPING @@ -819,20 +977,25 @@ h4 { .row-new-striped > .row:nth-of-type(even) { background: #FFFFFF; border-top: 1px solid #dddddd; + line-height: 1.9; display: table-row; } .row-new-striped > .row:nth-of-type(odd) { background-color: #F8F8F8; border-top: 1px solid #dddddd; display: table-row; + line-height: 1.9; + padding: 2px; } .row-new-striped div { display: table-cell; border-top: 1px solid #dddddd; + padding: 6px; } .row-new-striped div { display: table-cell; border-top: 1px solid #dddddd; + padding: 6px; } .row-new-striped div[class^="col"]:first-child { font-weight: bold; @@ -872,18 +1035,24 @@ h4 { any HTML used in the UserPresenter "title" attribute breaks the column selector HTML. Instead, we use CSS to add the icon into the table header, which leaves the column selector - "title" text as-is. - - See https://github.com/snipe/snipe-it/issues/7989 + "title" text as-is and hides the icon. + See https://github.com/grokability/snipe-it/issues/7989 */ +th.css-accessory > .th-inner, +th.css-accessory-alt > .th-inner, th.css-barcode > .th-inner, -th.css-license > .th-inner, +th.css-component > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, -th.css-users > .th-inner, +th.css-house-flag > .th-inner, +th.css-house-laptop > .th-inner, +th.css-house-user > .th-inner, +th.css-license > .th-inner, th.css-location > .th-inner, -th.css-accessory > .th-inner { +th.css-users > .th-inner, +th.css-currency > .th-inner, +th.css-history > .th-inner { font-size: 0px; line-height: 0.75 !important; text-align: left; @@ -891,26 +1060,27 @@ th.css-accessory > .th-inner { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -th.css-padlock > .th-inner::before, +th.css-location > .th-inner::before, +th.css-accessory > .th-inner::before, +th.css-accessory-alt > .th-inner::before, th.css-barcode > .th-inner::before, -th.css-license > .th-inner::before, +th.css-component > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, -th.css-users > .th-inner::before, +th.css-house-flag > .th-inner::before, +th.css-house-laptop > .th-inner::before, +th.css-house-user > .th-inner::before, +th.css-license > .th-inner::before, th.css-location > .th-inner::before, -th.css-accessory > .th-inner::before { +th.css-padlock > .th-inner::before, +th.css-users > .th-inner::before, +th.css-currency > .th-inner::before, +th.css-history > .th-inner::before { display: inline-block; font-size: 20px; font-family: "Font Awesome 5 Free"; font-weight: 900; } -th.css-padlock > .th-inner::before { - content: "\f023"; - font-family: "Font Awesome 5 Free"; - font-weight: 900; - padding-right: 4px; - font-size: 12px; -} /** BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). @@ -951,6 +1121,52 @@ th.css-location > .th-inner::before { font-size: 19px; margin-bottom: 0px; } +th.css-component > .th-inner::before { + content: "\f0a0"; + font-family: "Font Awesome 5 Free"; + font-weight: 500; +} +th.css-padlock > .th-inner::before { + content: "\f023"; + font-family: "Font Awesome 5 Free"; + font-weight: 900; +} +th.css-house-user > .th-inner::before { + content: "\e1b0"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-house-flag > .th-inner::before { + content: "\e50d"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-house-laptop > .th-inner::before { + content: "\e066"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-accessory-alt > .th-inner::before { + content: "\f11c"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-currency > .th-inner::before { + content: "\24"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-history > .th-inner::before { + content: "\f1da"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -969,7 +1185,8 @@ th.css-location > .th-inner::before { padding: 6px 12px; height: 34px; } -.form-group.has-error label { +.form-group.has-error label, +.form-group.has-error .help-block { color: #a94442; } .select2-container--default .select2-selection--multiple { @@ -999,10 +1216,41 @@ th.css-location > .th-inner::before { margin-top: 50px; } } -.ellipsis { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; +@media screen and (max-width: 992px) { + .info-stack-container { + flex-direction: column; + } + .col-md-3.col-xs-12.col-sm-push-9.info-stack { + left: auto; + order: 1; + } + .col-md-9.col-xs-12.col-sm-pull-3.info-stack { + right: auto; + order: 2; + } + .info-stack-container > .col-md-9.col-xs-12.col-sm-pull-3.info-stack > .row-new-striped > .row > .col-sm-2 { + width: auto; + float: none; + } +} +@media screen and (max-width: 992px) { + .row-new-striped div { + width: 100%; + } +} +@media screen and (max-width: 1318px) and (min-width: 1200px) { + .admin.box { + height: 170px; + } +} +@media screen and (max-width: 1494px) and (min-width: 1200px) { + .dashboard.small-box { + white-space: nowrap; + text-overflow: ellipsis; + max-width: 188px; + display: block; + overflow: hidden; + } } /** Form-stuff overrides for checkboxes and stuff **/ label.form-control { @@ -1140,6 +1388,9 @@ input[type="radio"]:checked::before { .select2-container .select2-search--inline .select2-search__field { padding-left: 15px; } +.nav-tabs-custom > .nav-tabs > li.active { + font-weight: bold; +} /** --------------------------------------- **/ /** End checkbox styles to replace iCheck **/ /** --------------------------------------- **/ @@ -1174,4 +1425,45 @@ input[type="radio"]:checked::before { filter: brightness(70%); font-size: 70%; } +/** this is needed to override ekko-lightboxes card view styles **/ +.bootstrap-table .fixed-table-container .table tbody tr .card-view { + display: table-row !important; +} +td.text-right.text-padding-number-cell { + padding-right: 30px !important; + white-space: nowrap; +} +th.text-right.text-padding-number-footer-cell { + padding-right: 20px !important; + white-space: nowrap; +} +code.single-line { + white-space: pre-wrap; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + max-width: 400px; +} +p.monospace, +span.monospace { + font-family: monospace, monospace; +} +legend.highlight { + background: repeating-linear-gradient(45deg, #222d32, #222d32 10px, #444 10px, #444 11px); + color: #fff; + font-size: 18px; + padding: 6px 6px 6px 10px; +} +legend.highlight a { + color: #fff; + cursor: pointer; +} +fieldset.bottom-padded { + padding-bottom: 20px; +} +caption.tableCaption { + font-size: 18px; + padding-left: 8px; +} diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index 24696882ae..aa8e28acbb 100644 --- a/public/css/build/overrides.css +++ b/public/css/build/overrides.css @@ -28,7 +28,7 @@ padding: 10px; background: #f4f4f4; margin-bottom: 3px; - border-left: 2px solid #e6e7e8; + border-inline: 2px solid #e6e7e8; color: #444; cursor: move; } @@ -319,8 +319,13 @@ a.link-danger:hover { a.logo.no-hover a:hover { background-color: transparent; } -.required { - border-right: 6px solid orange; +input:required, +select:required { + border-right: 5px solid orange; +} +select:required + .select2-container .select2-selection, +select:required + .select2-container .select2-selection .select2-selection--multiple { + border-right: 5px solid orange !important; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; @@ -330,18 +335,156 @@ body { font-size: 14px; white-space: normal; } +.modal-warning .modal-help { + color: #fff8af; +} .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading { z-index: 0 !important; } @media print { - a[href]:after { - content: none; + @page { + size: A4; + margin: 0mm; } .tab-content > .tab-pane { display: block !important; opacity: 1 !important; visibility: visible !important; } + .img-responsive { + width: 200px; + } + html, + body { + width: 1024px; + } + body { + margin: 0 auto; + line-height: 1em; + word-spacing: 1px; + letter-spacing: 0.2px; + font: 15px "Times New Roman", Times, serif; + background: white; + color: black; + width: 100%; + float: none; + } + /* avoid page-breaks inside a listingContainer*/ + .listingContainer { + page-break-inside: avoid; + } + h1 { + font: 28px "Times New Roman", Times, serif; + } + h2 { + font: 24px "Times New Roman", Times, serif; + } + h3 { + font: 20px "Times New Roman", Times, serif; + } + /* Improve colour contrast of links */ + a:link, + a:visited { + color: #781351; + } + /* URL */ + a:link, + a:visited { + background: transparent; + color: #333; + text-decoration: none; + } + a[href]:after { + content: "" !important; + } + a[href^="http://"] { + color: #000; + } + #header { + height: 75px; + font-size: 24pt; + color: black; + } + div.row-new-striped { + margin: 0px; + padding: 0px; + } + .pagination-detail, + .fixed-table-toolbar { + visibility: hidden; + } + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11, + .col-sm-12 .col-sm-pull-3 .col-sm-push-9 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } +} +.select2-selection__choice__remove { + color: white !important; +} +.select2-selection--multiple { + border-color: #d2d6de !important; + overflow-y: auto; +} +.select2-selection__choice { + border-radius: 0px !important; +} +.select2-search select2-search--inline { + height: 35px !important; + float: left; + margin: 0; +} +.select2-results__option { + padding: 5px; + -moz-user-select: none; + user-select: none; + -webkit-user-select: none; + margin: 0px; } img.navbar-brand-img, .navbar-brand > img { @@ -349,8 +492,10 @@ img.navbar-brand-img, padding: 5px 5px 5px 0; max-height: 50px; } -.input-daterange { - border-radius: 0px; +.input-daterange, +.input-daterange input:first-child, +.input-daterange input:last-child { + border-radius: 0px !important; } .btn.bg-maroon, .btn.bg-purple { @@ -429,20 +574,21 @@ h4 { background-color: #f9f9f9; border-top: 1px solid #dddddd; display: table-cell; + word-wrap: break-word; } .row-striped .row:nth-of-type(even) div { background: #FFFFFF; border-top: 1px solid #dddddd; display: table-cell; + word-wrap: break-word; } .row-new-striped { vertical-align: top; - line-height: 2.6; - padding: 0px; - margin-left: 20px; + padding: 3px; display: table; width: 100%; - padding-right: 20px; + word-wrap: break-word; + table-layout: fixed; } /** * NEW STRIPING @@ -452,20 +598,25 @@ h4 { .row-new-striped > .row:nth-of-type(even) { background: #FFFFFF; border-top: 1px solid #dddddd; + line-height: 1.9; display: table-row; } .row-new-striped > .row:nth-of-type(odd) { background-color: #F8F8F8; border-top: 1px solid #dddddd; display: table-row; + line-height: 1.9; + padding: 2px; } .row-new-striped div { display: table-cell; border-top: 1px solid #dddddd; + padding: 6px; } .row-new-striped div { display: table-cell; border-top: 1px solid #dddddd; + padding: 6px; } .row-new-striped div[class^="col"]:first-child { font-weight: bold; @@ -505,18 +656,24 @@ h4 { any HTML used in the UserPresenter "title" attribute breaks the column selector HTML. Instead, we use CSS to add the icon into the table header, which leaves the column selector - "title" text as-is. - - See https://github.com/snipe/snipe-it/issues/7989 + "title" text as-is and hides the icon. + See https://github.com/grokability/snipe-it/issues/7989 */ +th.css-accessory > .th-inner, +th.css-accessory-alt > .th-inner, th.css-barcode > .th-inner, -th.css-license > .th-inner, +th.css-component > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, -th.css-users > .th-inner, +th.css-house-flag > .th-inner, +th.css-house-laptop > .th-inner, +th.css-house-user > .th-inner, +th.css-license > .th-inner, th.css-location > .th-inner, -th.css-accessory > .th-inner { +th.css-users > .th-inner, +th.css-currency > .th-inner, +th.css-history > .th-inner { font-size: 0px; line-height: 0.75 !important; text-align: left; @@ -524,26 +681,27 @@ th.css-accessory > .th-inner { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -th.css-padlock > .th-inner::before, +th.css-location > .th-inner::before, +th.css-accessory > .th-inner::before, +th.css-accessory-alt > .th-inner::before, th.css-barcode > .th-inner::before, -th.css-license > .th-inner::before, +th.css-component > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, -th.css-users > .th-inner::before, +th.css-house-flag > .th-inner::before, +th.css-house-laptop > .th-inner::before, +th.css-house-user > .th-inner::before, +th.css-license > .th-inner::before, th.css-location > .th-inner::before, -th.css-accessory > .th-inner::before { +th.css-padlock > .th-inner::before, +th.css-users > .th-inner::before, +th.css-currency > .th-inner::before, +th.css-history > .th-inner::before { display: inline-block; font-size: 20px; font-family: "Font Awesome 5 Free"; font-weight: 900; } -th.css-padlock > .th-inner::before { - content: "\f023"; - font-family: "Font Awesome 5 Free"; - font-weight: 900; - padding-right: 4px; - font-size: 12px; -} /** BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). @@ -584,6 +742,52 @@ th.css-location > .th-inner::before { font-size: 19px; margin-bottom: 0px; } +th.css-component > .th-inner::before { + content: "\f0a0"; + font-family: "Font Awesome 5 Free"; + font-weight: 500; +} +th.css-padlock > .th-inner::before { + content: "\f023"; + font-family: "Font Awesome 5 Free"; + font-weight: 900; +} +th.css-house-user > .th-inner::before { + content: "\e1b0"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-house-flag > .th-inner::before { + content: "\e50d"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-house-laptop > .th-inner::before { + content: "\e066"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-accessory-alt > .th-inner::before { + content: "\f11c"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-currency > .th-inner::before { + content: "\24"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-history > .th-inner::before { + content: "\f1da"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -602,7 +806,8 @@ th.css-location > .th-inner::before { padding: 6px 12px; height: 34px; } -.form-group.has-error label { +.form-group.has-error label, +.form-group.has-error .help-block { color: #a94442; } .select2-container--default .select2-selection--multiple { @@ -632,10 +837,41 @@ th.css-location > .th-inner::before { margin-top: 50px; } } -.ellipsis { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; +@media screen and (max-width: 992px) { + .info-stack-container { + flex-direction: column; + } + .col-md-3.col-xs-12.col-sm-push-9.info-stack { + left: auto; + order: 1; + } + .col-md-9.col-xs-12.col-sm-pull-3.info-stack { + right: auto; + order: 2; + } + .info-stack-container > .col-md-9.col-xs-12.col-sm-pull-3.info-stack > .row-new-striped > .row > .col-sm-2 { + width: auto; + float: none; + } +} +@media screen and (max-width: 992px) { + .row-new-striped div { + width: 100%; + } +} +@media screen and (max-width: 1318px) and (min-width: 1200px) { + .admin.box { + height: 170px; + } +} +@media screen and (max-width: 1494px) and (min-width: 1200px) { + .dashboard.small-box { + white-space: nowrap; + text-overflow: ellipsis; + max-width: 188px; + display: block; + overflow: hidden; + } } /** Form-stuff overrides for checkboxes and stuff **/ label.form-control { @@ -773,6 +1009,9 @@ input[type="radio"]:checked::before { .select2-container .select2-search--inline .select2-search__field { padding-left: 15px; } +.nav-tabs-custom > .nav-tabs > li.active { + font-weight: bold; +} /** --------------------------------------- **/ /** End checkbox styles to replace iCheck **/ /** --------------------------------------- **/ @@ -807,4 +1046,45 @@ input[type="radio"]:checked::before { filter: brightness(70%); font-size: 70%; } +/** this is needed to override ekko-lightboxes card view styles **/ +.bootstrap-table .fixed-table-container .table tbody tr .card-view { + display: table-row !important; +} +td.text-right.text-padding-number-cell { + padding-right: 30px !important; + white-space: nowrap; +} +th.text-right.text-padding-number-footer-cell { + padding-right: 20px !important; + white-space: nowrap; +} +code.single-line { + white-space: pre-wrap; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + max-width: 400px; +} +p.monospace, +span.monospace { + font-family: monospace, monospace; +} +legend.highlight { + background: repeating-linear-gradient(45deg, #222d32, #222d32 10px, #444 10px, #444 11px); + color: #fff; + font-size: 18px; + padding: 6px 6px 6px 10px; +} +legend.highlight a { + color: #fff; + cursor: pointer; +} +fieldset.bottom-padded { + padding-bottom: 20px; +} +caption.tableCaption { + font-size: 18px; + padding-left: 8px; +} diff --git a/public/css/build/overrides.css.map b/public/css/build/overrides.css.map new file mode 100644 index 0000000000..7fcc29bb45 --- /dev/null +++ b/public/css/build/overrides.css.map @@ -0,0 +1 @@ +{"version":3,"file":"css/build/overrides.css","mappings":"AAAA;EAkBE;AAhBF;AAkBA;EACE;EACA;EACA;EACA;EACA;AAhBF;AAiBE;;;EACE;AAbJ;AAgBA;EACE;AAdF;AAiBA;EACE;EACA;AAfF;AAkBA;EACE;AAhBF;AAoBA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;AAlBF;AAqBA;EACE;EACA;EACA;EACA;EACA;AAnBF;AAsBA;EACE;AApBF;AAuBA;EACE;AArBF;AAwBA;EACE;EACA;AAtBF;AA0BA;EACE;AAxBF;AA2BA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AAzBF;AA2BA;EACE;AAzBF;AA2BA;EACE;AAzBF;AA6BA;EACE;AA3BF;AA6BA;EACE;AA3BF;AA+BA;EACE;EACA;EACA;AA7BF;AAgCA;EACE;EACA;EACA;AA9BF;AAqDA;EACE;AAnDF;AAsDA;EACE;EACA;EACA;AApDF;AAuDA;EACE;EACA;AArDF;AAyDA;EACE;AAvDF;AAyDA;EACE;EACA;AAvDF;AA0DA;;EACE;EACA;AAvDF;AA0DA;EACE;EACA;AAxDF;AA0DA;EACE;AAxDF;AA2DA;EACE;EACA;EACA;AAzDF;AA4DA;EACE;AA1DF;AA6DA;EACE;AA3DF;AA8DA;EACE;AA5DF;AA8DA;EACE;AA5DF;AA+DA;EACE;AA7DF;AAgEA;;;;EACE;AA3DF;AA8DA;;;;;EACE;AAxDF;AA2DA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AAzDF;AA2DA;EACE;EACA;EACA;EACA;EACA;EACA;AAzDF;AA2DA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AAzDF;AA2DA;EACE;AAzDF;AA2DA;EACE;EACA;EACA;EACA;AAzDF;AA2DA;EACE;EACA;AAzDF;AA2DA;EACE;EACA;EACA;EACA;EACA;AAzDF;AA2DA;EACE;EACA;AAzDF;AA2DA;EACE;EACA;EACA;EACA;AAzDF;AA4DA;EACE;EACA;AA1DF;AA+DA;EAAY;AA5DZ;AACA,cAAc;AA8Dd;EAAY;EAAkC;AA1D9C;AA2DA;EAA8B;EAAY;AAvD1C;AAyDA;EAAiD;EAAgB;EAAiB;AApDlF;AAqDA;EAA8C;EAAa;AAjD3D;AAkDA;EAA+C;EAAoB;EAAa;EAAc;EAAgB;EAAqB;EAAW;EAAW;EAAmB;EAAoB;AAtChM;AAuCA;EAAqD;EAAc;EAAa;EAAc;EAAqB;EAAqB;EAAoB;EAAU;AA7BtK;AA8BA;EAA0C;EAAoB;EAAoB;EAAa;EAAkB;AAvBjH;AAwBA;EAA0D;EAAW;EAAkB;AAnBvF;AAoBA;EAAmE;AAjBnE;AAkBA;EAAiE;AAfjE;AAgBA;EAA6E;AAb7E;AAcA;EAA4E;AAX5E;AAYA;EAAwD;AATxD;AAUA;EAA8D;AAP9D;AAQA;EAAuD;EAAW;AAJlE;AAKA;EAAsD;AAFtD;AAGA;EAAuD;AAAvD;AACA,kBAAkB;AAElB;EACE;EACA;EACA;EACA;EACA;EAAA,gCAAgC;AAClC;AAGA;EAkBE;AAlBF;AAqBA;EACE;AAnBF;AAuBA;EACE;AArBF;AAwBA;EACE;EAIA;AAzBF;AA4BA;EACE;EACA;AA1BF;AA6BA;EACE;AA3BF;AA8BA;EACE;AA5BF;AA+BA;EAEE;IACE;IACA;EA9BF;EAiCA;IACE;IACA;IACA;EA/BF;EAkCA;IACE;EAhCF;EAmCA;;IACE;EAhCF;EAmCA;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EAjCF;EACA,+CAA+C;EAoC/C;IACE;EAlCF;EAqCA;IACE;EAnCF;EAsCA;IACE;EApCF;EAuCA;IACE;EArCF;EACA,qCAAqC;EAwCrC;;IACE;EArCF;EACA,QAAQ;EAwCR;;IACE;IACA;IACA;EArCF;EAwCA;IACE;EAtCF;EAyCA;IACE;EAvCF;EA0CA;IACE;IACA;IACA;EAxCF;EA2CA;IACE;IACA;EAzCF;EA4CA;;IACE;EAzCF;EA2CA;;;;;;;;;;;;IACE;EA9BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;EAiCA;IACE;EA/BF;AACF;AAoCA;EACE;AAlCF;AAqCA;EACI;EACA;AAnCJ;AAsCA;EACE;AApCF;AAuCA;EACE;EACA;EACA;AArCF;AA0CA;EACE;EACA;OAAA;EACA;EACA;AAxCF;AA2CA;;EACE;EACA;EACA;AAxCF;AA2CA;;;EACE;AAvCF;AA0CA;;EACE;AAvCF;AA0CA;EACE;AAxCF;AA2CA;EACE;AAzCF;AA4CA;EACE;EACA;EACA;AA1CF;AA6CA;EACE;EACA;AA3CF;AA8CA;EACE;EACA;EACA;AA5CF;AAgDA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;AA9CF;AAgDA;;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AA7CF;AAgDA;EACE;AA9CF;AAiDA;EACE;AA/CF;AAkDA;EACE;AAhDF;AAmDA;EACE;AAjDF;AAoDA;EACE;AAlDF;AAsDA;EACE;EACA;EACA;EACA;EACA;EAGA;AAtDF;AA0DA;EACE;EACA;EACA;AAxDF;AA4DA;EACE;EACA;EACA;AA1DF;AA8DA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;AA5DF;AACA;;;;EAIE;AA+DF;EACE;EACA;EACA;AA7DF;AAiEA;EACE;EACA;EACA;AA/DF;AAmEA;EACE;EACA;AAjEF;AAoEA;EACE;EACA;AAlEF;AAsEA;EACE;AApEF;AACA;;EAEE;AAyEF;EACE;IACE;IACA;EAvEF;EA0EA;IACE;EAxEF;EA2EA;IACE;EAzEF;EA4EA;IACE;EA1EF;AACF;AA6EA;EACE;EACA;EACA;AA3EF;AA8EA;EACE;EACA;AA5EF;AACA;;;;;;;;;;;;EAYE;AAkFF;;;;;;;;EASE;EACA;EACA;EACA;EACA;EACA;AAjFF;AAqFA;;;;;;;;;EAWE;EACA;EACA;EACA;AArFF;AAwFA;EAEE;EACA;EACA;EACA;EACA;AAvFF;AACA;;;EAGE;AA0FF;EAEE;EAAkB;EAAoC;AAvFxD;AA0FA;EAEE;EAAkB;EAAoC;AAvFxD;AA0FA;EAEE;EAAkB;EAAoC;AAvFxD;AA0FA;EAEE;EAAkB;EAAoC;AAvFxD;AA0FA;EAEE;EAAkB;EAAoC;AAvFxD;AA0FA;EACE;EAAkB;EAAoC;AAtFxD;AAyFA;EACE;EAAkB;EAAoC;EAAiB;AApFzE;AAuFA;EAEE;EAAkB;EAAoC;AApFxD;AAwFA;EACE;EACA;EACA;EACA;AAtFF;AA0FA;;;EACE;AAtFF;AAyFA;;EACE;EACA;EACA;EACA;AAtFF;AAyFA;EACE;AAvFF;AA0FA;EACE;AAxFF;AA2FA;EACE;IACE;EAzFF;EA2FA;IACE;EAzFF;AACF;AA2FA;EACE;IACE;EAzFF;EA2FA;IACE;EAzFF;EA2FA;IACE;EAzFF;AACF;AA4FA;EACE;IACE;EA1FF;AACF;AA4FA;EACE;IACE;IACA;EA1FF;EA4FA;IACE;IACA;EA1FF;EA4FA;IACE;IACA;EA1FF;EA4FA;IACE;IACA;EA1FF;AACF;AA4FA;EACE;IACE;EA1FF;AACF;AA4FA;EACE;IACE;IACA;IACA;IACA;IACA;EA1FF;AACF;AACA,oDAAoD;AA8FpD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AA5FF;AA+FA;EACE;EACA;EACA;AA7FF;AACA,8CAA8C;AAC9C,8CAA8C;AAC9C,8CAA8C;AAgG9C;EA9FE,kCAAkC;EAgGlC;EACA;OAAA;EA9FA,+CAA+C;EAgG/C;EA9FA,+BAA+B;EAgG/B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EA9FA,6BAA6B;AAC/B;AACA,yFAAyF;AAkGzF;EAhGE,2EAA2E;EAwG3E;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EAvGA,+BAA+B;EAyG/B;AAvGF;AACA,wEAAwE;AA0GxE;EACE;AAxGF;AACA,wEAAwE;AA2GxE;;EACE;EACA;EACA;EACA;EACA;AAxGF;AACA,6EAA6E;AA2G7E;;EACE;EACA;EACA;EACA;AAxGF;AACA,+EAA+E;AA2G/E;;EACE;EACA;EACA;EACA;AAxGF;AACA,qCAAqC;AA6GrC;EACE;KAAA;UAAA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AA3GF;AA8GA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;AA5GF;AA+GA;EACE;AA7GF;AACA;;;;EAIE;AAiHF;EACE;AA/GF;AAkHA;EACE;EACA;EACA;EACA;AAhHF;AAmHA;EACE;AAjHF;AAoHA;EACE;AAlHF;AAqHA;EACE;AAnHF;AAsHA;EACE;AApHF;AACA,8CAA8C;AAC9C,8CAA8C;AAC9C,8CAA8C;AAC9C;;;EAGE;AAyHF;EACE;EACA;EACA;EACA;EACA;AAvHF;AA0HA;;EAEE;EACA;EACA;AAxHF;AA2HA;EACE;AAzHF;AA4HA;EACE;AA1HF;AA4HA;EACE;AA1HF;AA6HA;EACE;EACA;EACA;AA3HF","sources":["webpack:///./resources/assets/less/overrides.less"],"sourcesContent":[".skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n\n.logo {\n background-color: inherit;\n}\n.main-header .logo {\n width: 100% !important;\n white-space: nowrap;\n text-align: left;\n display: block;\n clear: both;\n &a:link, a:hover, a:visited {\n color: #fff\n }\n}\n.huge {\n font-size: 40px;\n}\n\n.btn-file {\n position: relative;\n overflow: hidden;\n}\n\n.dropdown-menu>li>a {\n color: #354044;\n}\n\n\n#sort tr.cansort {\n border-radius: 2px;\n padding: 10px;\n background: #f4f4f4;\n margin-bottom: 3px;\n border-left: 2px solid #e6e7e8;\n color: #444;\n cursor: move;\n}\n\n.user-image-inline {\n float: left;\n width: 25px;\n height: 25px;\n border-radius: 50%;\n margin-right: 10px;\n}\n\n.input-group .input-group-addon {\n background-color: #f4f4f4;\n}\n\na.accordion-header {\n color: #333;\n}\n\n.dynamic-form-row {\n padding: 10px;\n margin: 20px;\n}\n\n\n.handle {\n padding-left: 10px;\n}\n\n.btn-file input[type=file] {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 100%;\n min-height: 100%;\n font-size: 100px;\n text-align: right;\n filter: alpha(opacity=0);\n opacity: 0;\n outline: none;\n background: white;\n cursor: inherit;\n display: block;\n}\n.main-footer {\n font-size: 13px;\n}\n.main-header {\n max-height: 150px;\n}\n\n\n.navbar-nav>.user-menu>.dropdown-menu {\n width: inherit;\n}\n.main-header .logo {\n padding: 0px 5px 0px 15px;\n}\n\n\n.sidebar-toggle {\n margin-left: -48px;\n z-index: 100;\n background-color: inherit;\n}\n\n.sidebar-toggle-mobile {\n z-index: 100;\n width: 50px;\n padding-top: 10px;\n}\n\n.skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n.navbar\n.dropdown-menu li a {\n //color: inherit;\n}\n.pull-text-right{\n text-align: right !important;\n}\n\n.main-header .sidebar-toggle:before {\n content: \"\\f0c9\";\n font-weight: 900;\n font-family: 'Font Awesome\\ 5 Free';\n}\n\n.direct-chat-contacts {\n padding: 10px;\n height: 150px;\n}\n\n\n.select2-container {\n width: 100%;\n}\n.error input {\n color: #a94442;\n border: 2px solid #a94442 !important;\n}\n\n.error label, .alert-msg {\n color: #a94442;\n display: block;\n}\n\n.input-group[class*=\"col-\"] {\n padding-right: 15px;\n padding-left: 15px;\n}\n.control-label.multiline {\n padding-top: 10px;\n}\n\n.btn-outline {\n color: inherit;\n background-color: transparent;\n transition: all .5s;\n}\n\n.btn-primary.btn-outline {\n color: #428bca;\n}\n\n.btn-success.btn-outline {\n color: #5cb85c;\n}\n\n.btn-info.btn-outline {\n color: #5bc0de;\n}\n.btn-warning{\n background-color:#f39c12 !important;\n}\n\n.btn-warning.btn-outline {\n color: #f0ad4e;\n}\n\n.btn-danger.btn-outline, a.link-danger:link, a.link-danger:visited, a.link-danger:hover {\n color: #dd4b39;\n}\n\n.btn-primary.btn-outline:hover, .btn-success.btn-outline:hover, .btn-info.btn-outline:hover, .btn-warning.btn-outline:hover, .btn-danger.btn-outline:hover {\n color: #fff;\n}\n\n.slideout-menu {\n position: fixed;\n top: 0;\n right: -250px;\n width: 250px;\n height: 100%;\n background: #333;\n z-index: 100;\n margin-top: 100px;\n color: white;\n padding: 10px;\n}\n.slideout-menu h3 {\n position: relative;\n padding: 5px 5px;\n color: #fff;\n font-size: 1.2em;\n font-weight: 400;\n border-bottom: 4px solid #222;\n}\n.slideout-menu .slideout-menu-toggle {\n position: absolute;\n top: 12px;\n right: 10px;\n display: inline-block;\n padding: 6px 9px 5px;\n font-family: Arial, sans-serif;\n font-weight: bold;\n line-height: 1;\n background: #222;\n color: #999;\n text-decoration: none;\n vertical-align: top;\n}\n.slideout-menu .slideout-menu-toggle:hover {\n color: #fff;\n}\n.slideout-menu ul {\n list-style: none;\n font-weight: 300;\n border-top: 1px solid #151515;\n border-bottom: 1px solid #454545;\n}\n.slideout-menu ul li {\n border-top: 1px solid #454545;\n border-bottom: 1px solid #151515;\n}\n.slideout-menu ul li a {\n position: relative;\n display: block;\n padding: 10px;\n color: #999;\n text-decoration: none;\n}\n.slideout-menu ul li a:hover {\n background: #000;\n color: #fff;\n}\n.slideout-menu ul li a i {\n position: absolute;\n top: 15px;\n right: 10px;\n opacity: .5;\n}\n\n.btn-box-tool-lg {\n font-size: 16px;\n color: orange;\n}\n\n\n\n.bs-wizard {margin-top: 20px;}\n\n/*Form Wizard*/\n.bs-wizard {border-bottom: solid 1px #e0e0e0; padding: 0 0 10px 0;}\n.bs-wizard > .bs-wizard-step {padding: 0; position: relative;}\n.bs-wizard > .bs-wizard-step + .bs-wizard-step {}\n.bs-wizard > .bs-wizard-step .bs-wizard-stepnum {color: #595959; font-size: 16px; margin-bottom: 5px;}\n.bs-wizard > .bs-wizard-step .bs-wizard-info {color: #999; font-size: 14px;}\n.bs-wizard > .bs-wizard-step > .bs-wizard-dot {position: absolute; width: 30px; height: 30px; display: block; background: #fbe8aa; top: 45px; left: 50%; margin-top: -15px; margin-left: -15px; border-radius: 50%;}\n.bs-wizard > .bs-wizard-step > .bs-wizard-dot:after {content: ' '; width: 14px; height: 14px; background: #fbbd19; border-radius: 50px; position: absolute; top: 8px; left: 8px; }\n.bs-wizard > .bs-wizard-step > .progress {position: relative; border-radius: 0px; height: 8px; box-shadow: none; margin: 20px 0;}\n.bs-wizard > .bs-wizard-step > .progress > .progress-bar {width:0px; box-shadow: none; background: #fbe8aa;}\n.bs-wizard > .bs-wizard-step.complete > .progress > .progress-bar {width:100%;}\n.bs-wizard > .bs-wizard-step.active > .progress > .progress-bar {width:50%;}\n.bs-wizard > .bs-wizard-step:first-child.active > .progress > .progress-bar {width:0%;}\n.bs-wizard > .bs-wizard-step:last-child.active > .progress > .progress-bar {width: 100%;}\n.bs-wizard > .bs-wizard-step.disabled > .bs-wizard-dot {background-color: #f5f5f5;}\n.bs-wizard > .bs-wizard-step.disabled > .bs-wizard-dot:after {opacity: 0;}\n.bs-wizard > .bs-wizard-step:first-child > .progress {left: 50%; width: 50%;}\n.bs-wizard > .bs-wizard-step:last-child > .progress {width: 50%;}\n.bs-wizard > .bs-wizard-step.disabled a.bs-wizard-dot{ pointer-events: none; }\n/*END Form Wizard*/\n\n.left-navblock {\n display: inline-block;\n float: left;\n text-align: left;\n color: white;\n padding: 0px;\n /* adjust based on your layout */\n\n}\n.skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n.navbar\n.dropdown-menu li a {\n color: #333;\n}\n\na.logo.no-hover a:hover {\n background-color: transparent;\n}\n\n\n.required {\n border-right: 6px solid orange;\n}\n\nbody {\n font-family: -apple-system, BlinkMacSystemFont,\n \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\",\n \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n font-size: 13px;\n}\n\n.sidebar-menu {\n font-size: 14px;\n white-space: normal;\n}\n\n.modal-warning .modal-help {\n color: #fff8af;\n}\n\n.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading {\n z-index: 0 !important;\n}\n\n@media print {\n\n @page {\n size: A4;\n margin: 0mm;\n }\n\n .tab-content > .tab-pane {\n display: block !important;\n opacity: 1 !important;\n visibility: visible !important;\n }\n\n .img-responsive {\n width: 200px;\n }\n\n html, body {\n width: 1024px;\n }\n\n body {\n margin: 0 auto;\n line-height: 1em;\n word-spacing:1px;\n letter-spacing:0.2px;\n font: 15px \"Times New Roman\", Times, serif;\n background:white;\n color:black;\n width: 100%;\n float: none;\n }\n\n /* avoid page-breaks inside a listingContainer*/\n .listingContainer {\n page-break-inside: avoid;\n }\n\n h1 {\n font: 28px \"Times New Roman\", Times, serif;\n }\n\n h2 {\n font: 24px \"Times New Roman\", Times, serif;\n }\n\n h3 {\n font: 20px \"Times New Roman\", Times, serif;\n }\n\n /* Improve colour contrast of links */\n a:link, a:visited {\n color: #781351\n }\n\n /* URL */\n a:link, a:visited {\n background: transparent;\n color:#333;\n text-decoration:none;\n }\n\n a[href]:after {\n content: \"\" !important;\n }\n\n a[href^=\"http://\"] {\n color:#000;\n }\n\n #header {\n height:75px;\n font-size: 24pt;\n color:black\n }\n\n div.row-new-striped {\n margin: 0px;\n padding: 0px;\n }\n\n .pagination-detail, .fixed-table-toolbar {\n visibility: hidden;\n }\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 .col-sm-pull-3 .col-sm-push-9 {\n float: left;\n }\n\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666666666666%;\n }\n .col-sm-10 {\n width: 83.33333333333334%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666666666666%;\n }\n .col-sm-7 {\n width: 58.333333333333336%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666666666667%;\n }\n .col-sm-4 {\n width: 33.33333333333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.666666666666664%;\n }\n .col-sm-1 {\n width: 8.333333333333332%;\n }\n\n}\n\n\n.select2-selection__choice__remove {\n color: white !important;\n}\n\n.select2-selection--multiple {\n border-color: #d2d6de !important;\n overflow-y: auto;\n}\n\n.select2-selection__choice {\n border-radius: 0px !important;\n}\n\n.select2-search select2-search--inline {\n height: 35px !important;\n float: left;\n margin: 0;\n}\n\n\n\n.select2-results__option {\n padding: 5px;\n user-select: none;\n -webkit-user-select: none;\n margin: 0px;\n}\n\nimg.navbar-brand-img, .navbar-brand>img {\n float: left;\n padding: 5px 5px 5px 0;\n max-height: 50px;\n}\n\n.input-daterange, .input-daterange input:first-child, .input-daterange input:last-child {\n border-radius: 0px !important;\n}\n\n.btn.bg-maroon, .btn.bg-purple{\n min-width:90px;\n}\n\n[hidden] {\n display: none !important;\n}\n\n#toolbar {\n margin-top: 10px;\n}\n\n#uploadPreview {\n border-color: grey;\n border-width: 1px;\n border-style: solid\n}\n\n.icon-med {\n font-size: 14px;\n color: #889195;\n}\n\n#login-logo {\n padding-top: 20px;\n padding-bottom: 10px;\n max-width: 200px\n}\n\n// accessibility skip link\na.skip-main {\n left:-999px;\n position:absolute;\n top:auto;\n width:1px;\n height:1px;\n overflow:hidden;\n z-index:-999;\n}\na.skip-main:focus, a.skip-main:active {\n color: #fff;\n background-color:#000;\n left: auto;\n top: auto;\n width: 30%;\n height: auto;\n overflow:auto;\n margin: 10px 35%;\n padding:5px;\n border-radius: 15px;\n border:4px solid yellow;\n text-align:center;\n font-size:1.2em;\n z-index:999;\n}\n\nh2 {\n font-size: 22px;\n}\n\nh2.task_menu {\n font-size: 14px;\n}\n\nh2 small {\n font-size: 85%;\n}\n\nh3 {\n font-size: 20px;\n}\n\nh4 {\n font-size: 16px;\n}\n\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: #f9f9f9;\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background: #FFFFFF;\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n\n\n.row-new-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n display: table;\n width: 100%;\n padding-right: 20px;\n}\n\n/**\n* NEW STRIPING\n* This section is for the new row striping for nicer \n* display for non-table data as of v6\n**/\n.row-new-striped > .row:nth-of-type(even) {\n background: #FFFFFF;\n border-top: 1px solid #dddddd;\n display: table-row;\n\n}\n\n.row-new-striped > .row:nth-of-type(odd) {\n background-color: #F8F8F8;\n border-top: 1px solid #dddddd;\n display: table-row;\n\n}\n\n.row-new-striped div {\n display: table-cell;\n border-top: 1px solid #dddddd;\n}\n\n.row-new-striped div {\n display: table-cell;\n border-top: 1px solid #dddddd;\n}\n\n\n.row-new-striped div[class^=\"col\"]:first-child {\n font-weight: bold;\n}\n\n\n\n/**\n* This just adds a little extra padding on mobile\n**/\n@media only screen and (max-width: 520px) {\n h1.pagetitle {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n\n .firstnav {\n padding-top: 120px !important;\n }\n\n .product {\n width: 400px;\n }\n\n .product img {\n min-width: 400px;\n }\n}\n\n.card-view-title {\n min-width: 40% !important;\n line-height: 3.0!important;\n padding-right: 20px;\n}\n\n.card-view {\n display: table-row;\n flex-direction: column;\n}\n\n// ---------------\n\n/**\n\n COLUMN SELECTOR ICONS\n -----------------------------\n This is kind of weird, but it is necessary to prevent the column-selector code from barfing, since\n any HTML used in the UserPresenter \"title\" attribute breaks the column selector HTML.\n\n Instead, we use CSS to add the icon into the table header, which leaves the column selector\n \"title\" text as-is.\n\n See https://github.com/grokability/snipe-it/issues/7989\n\n */\n\nth.css-barcode > .th-inner,\nth.css-license > .th-inner,\nth.css-consumable > .th-inner,\nth.css-envelope > .th-inner,\nth.css-users > .th-inner,\nth.css-location > .th-inner,\nth.css-component > .th-inner,\nth.css-accessory > .th-inner\n{\n font-size: 0px;\n line-height: .75!important;\n text-align: left;\n text-rendering: auto;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n\nth.css-padlock > .th-inner::before,\nth.css-barcode > .th-inner::before,\nth.css-license > .th-inner::before,\nth.css-consumable > .th-inner::before,\nth.css-envelope > .th-inner::before,\nth.css-users > .th-inner::before,\nth.css-location > .th-inner::before,\nth.css-component > .th-inner::before,\nth.css-accessory > .th-inner::before\n\n{\n display: inline-block;\n font-size: 20px;\n font-family: \"Font Awesome 5 Free\";\n font-weight: 900;\n}\n\nth.css-padlock > .th-inner::before\n{\n content: \"\\f023\";\n font-family: \"Font Awesome 5 Free\";\n font-weight: 900;\n padding-right: 4px;\n font-size: 12px;\n}\n\n/**\nBEGIN ICON TABLE HEADERS\nSet the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons).\n**/\nth.css-barcode > .th-inner::before\n{\n content: \"\\f02a\"; font-family: \"Font Awesome 5 Free\"; font-weight: 900;\n}\n\nth.css-license > .th-inner::before\n{\n content: \"\\f0c7\"; font-family: \"Font Awesome 5 Free\"; font-weight: 400;\n}\n\nth.css-consumable > .th-inner::before\n{\n content: \"\\f043\"; font-family: \"Font Awesome 5 Free\"; font-weight: 900;\n}\n\nth.css-envelope > .th-inner::before\n{\n content: \"\\f0e0\"; font-family: \"Font Awesome 5 Free\"; font-weight: 400;\n}\n\nth.css-accessory > .th-inner::before\n{\n content: \"\\f11c\"; font-family: \"Font Awesome 5 Free\"; font-weight: 400;\n}\n\nth.css-users > .th-inner::before {\n content: \"\\f0c0\"; font-family: \"Font Awesome 5 Free\"; font-size: 15px;\n}\n\nth.css-location > .th-inner::before {\n content: \"\\f3c5\"; font-family: \"Font Awesome 5 Free\"; font-size: 19px; margin-bottom: 0px;\n}\n\nth.css-component > .th-inner::before\n{\n content: \"\\f0a0\"; font-family: \"Font Awesome 5 Free\"; font-weight: 500;\n}\n\n\n.small-box .inner {\n padding-left: 15px;\n padding-right: 15px;\n padding-top: 15px;\n color: #fff;\n}\n\n\n.small-box > a:link, .small-box > a:visited, .small-box > a:hover {\n color: #fff;\n}\n\n.select2-container--default .select2-selection--single, .select2-selection .select2-selection--single {\n border: 1px solid #d2d6de;\n border-radius: 0;\n padding: 6px 12px;\n height: 34px;\n}\n\n.form-group.has-error label {\n color: #a94442;\n}\n\n.select2-container--default .select2-selection--multiple {\n border-radius: 0px;\n}\n\n@media screen and (max-width: 511px){\n .tab-content .tab-pane .alert-block {\n margin-top: 120px\n }\n .sidebar-menu{\n margin-top:160px;\n }\n}\n@media screen and (max-width: 912px) and (min-width: 512px){\n .sidebar-menu {\n margin-top:100px\n }\n .navbar-custom-menu > .navbar-nav > li.dropdown.user.user-menu {\n float:right;\n }\n .navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n margin-right:-39px;\n }\n}\n\n@media screen and (max-width: 1268px) and (min-width: 912px){\n .sidebar-menu {\n margin-top:50px\n }\n}\n@media screen and (max-width: 992px){\n .info-stack-container {\n display: flex;\n flex-direction: column;\n }\n .col-md-3.col-xs-12.col-sm-push-9.info-stack{\n left:auto;\n order:1;\n }\n .col-md-9.col-xs-12.col-sm-pull-3.info-stack{\n right:auto;\n order:2;\n }\n .info-stack-container > .col-md-9.col-xs-12.col-sm-pull-3.info-stack > .row-new-striped > .row > .col-sm-2{\n width:auto;\n float:none;\n }\n}\n@media screen and (max-width: 1318px) and (min-width: 1200px){\n .admin.box{\n height:170px;\n }\n}\n@media screen and (max-width: 1494px) and (min-width: 1200px){\n .dashboard.small-box{\n white-space: nowrap;\n text-overflow: ellipsis;\n max-width: 188px;\n display: block;\n overflow: hidden;\n }\n}\n\n/** Form-stuff overrides for checkboxes and stuff **/\n\nlabel.form-control {\n display: grid;\n grid-template-columns: 1.8em auto;\n gap: 0.5em;\n border: 0px;\n padding-left: 0px;\n background-color: inherit;\n color: inherit;\n font-size: inherit;\n font-weight: inherit;\n}\n\nlabel.form-control--disabled {\n color: #959495;\n pointer-events:none;\n cursor: not-allowed;\n}\n\n/** --------------------------------------- **/\n/** Start checkbox styles to replace iCheck **/\n/** --------------------------------------- **/\ninput[type=\"checkbox\"] {\n /* Add if not using autoprefixer */\n -webkit-appearance: none;\n appearance: none;\n /* For iOS < 15 to remove gradient background */\n background-color: #fff;\n /* Not removed via appearance */\n margin: 0;\n font: inherit;\n color: #959495;\n width: 1.8em;\n height: 1.8em;\n border: 0.05em solid;\n border-radius: 0em;\n transform: translateY(-0.075em);\n display: grid;\n place-content: center;\n /*Windows High Contrast Mode*/\n}\n\n/** This sets the display of a checkbox, and what the \"fill\" checkmark should look like */\n\ninput[type=\"checkbox\"]::before {\n\n /** If you want to use the non-checkbox, filled square, use this instead **/\n content: \"\";\n width: 1em;\n height: 1em;\n transform: scale(0);\n transition: 120ms transform ease-in-out;\n box-shadow: inset 1em 1em rgb(211, 211, 211);\n\n content: \"\";\n width: 1em;\n height: 1em;\n clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);\n transform: scale(0);\n transform-origin: bottom left;\n transition: 120ms transform ease-in-out;\n box-shadow: inset 1em 1em #428bca;\n /* Windows High Contrast Mode */\n background-color: CanvasText;\n}\n\n/** This sets the size of the scale up for the shape we defined above **/\ninput[type=\"checkbox\"]:checked::before {\n transform: scale(1);\n}\n\n/** This sets the scale and color of the DISABLED but CHECKED checkbox */\ninput[type=checkbox]:disabled::before, input[type=radio]:disabled::before {\n content: \"\";\n width: 1em;\n height: 1em;\n transform: scale(1);\n box-shadow: inset 1em 1em rgb(211, 211, 211);\n}\n\n/* This sets the scale and style of a DISABLED checkbox that is NOT checked */\ninput[type=checkbox]:disabled:not(:checked)::before, input[type=radio]:disabled:not(:checked)::before {\n content: \"\";\n transform: scale(0);\n cursor: not-allowed;\n pointer-events:none;\n}\n\n/** this is the color of the checkbox and content on a disabled, checked box **/\ninput[type=checkbox]:disabled, input[type=radio]:disabled {\n --form-control-color: rgb(211, 211, 211);\n color: #959495;\n cursor: not-allowed;\n pointer-events:none;\n}\n\n\n/** Radio styles to replace iCheck **/\n\ninput[type=\"radio\"] {\n appearance: none;\n background-color: #fff;\n margin: 0;\n font: inherit;\n color: #959495;\n width: 1.8em;\n height: 1.8em;\n border: 0.05em solid;\n border-radius: 50%;\n transform: translateY(-0.075em);\n display: grid;\n place-content: center;\n}\n\ninput[type=\"radio\"]::before {\n content: \"\";\n width: 1em;\n height: 1em;\n border-radius: 50%;\n transform: scale(0);\n transition: 120ms transform ease-in-out;\n box-shadow: inset 1em 1em #428bca;\n}\n\ninput[type=\"radio\"]:checked::before {\n transform: scale(1);\n}\n\n\n/**\n* This addresses the column selector in bootstrap-table. Without these two lines, the\n* checkbox and the with the label text that BS tables generates will\n* end up on two different lines and it looks assy.\n */\n.dropdown-item-marker input[type=checkbox] {\n font-size: 10px;\n}\n\n.bootstrap-table .fixed-table-toolbar li.dropdown-item-marker label {\n font-weight: normal;\n display: grid;\n grid-template-columns: .1em auto;\n gap: 1.5em;\n}\n\n.container.row-striped .col-md-6 {\n overflow-wrap:anywhere;\n}\n\n.nav-tabs-custom > .nav-tabs > li {\n z-index: 1;\n}\n\n.select2-container .select2-search--inline .select2-search__field{\n padding-left:15px;\n}\n\n.nav-tabs-custom > .nav-tabs > li.active {\n font-weight: bold;\n}\n\n/** --------------------------------------- **/\n/** End checkbox styles to replace iCheck **/\n/** --------------------------------------- **/\n\n/**\n/** Separator styles with text in the middle. Currently only used by the login page but\n/** could be used elsewhere.\n */\n\n.separator {\n display: flex;\n align-items: center;\n text-align: center;\n padding-top: 20px;\n color: #959495;\n}\n\n.separator::before,\n.separator::after {\n content: '';\n flex: 1;\n border-bottom: 1px solid #959495;\n}\n\n.separator:not(:empty)::before {\n margin-right: .25em;\n}\n\n.separator:not(:empty)::after {\n margin-left: .25em;\n}\n.datepicker.dropdown-menu {\n z-index: 1030 !important;\n}\n\n.sidebar-menu > li .badge {\n margin-top: 0px;\n filter: brightness(70%);\n font-size: 70%;\n}\n"],"names":[],"sourceRoot":""} diff --git a/public/css/dist/all.css b/public/css/dist/all.css index 48cf0fefe1..5d809f514e 100644 --- a/public/css/dist/all.css +++ b/public/css/dist/all.css @@ -6833,7 +6833,7 @@ button.close { } /*# sourceMappingURL=bootstrap.css.map */ /*! - * Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com + * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) * Copyright 2024 Fonticons, Inc. */ @@ -6841,15 +6841,13 @@ button.close { font-family: var(--fa-style-family, "Font Awesome 6 Free"); font-weight: var(--fa-style, 900); } -.fa, -.fa-classic, -.fa-sharp, .fas, -.fa-solid, .far, -.fa-regular, .fab, -.fa-brands { +.fa-solid, +.fa-regular, +.fa-brands, +.fa { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; display: var(--fa-display, inline-block); @@ -6858,15 +6856,24 @@ button.close { line-height: 1; text-rendering: auto; } -.fas, +.fas::before, +.far::before, +.fab::before, +.fa-solid::before, +.fa-regular::before, +.fa-brands::before, +.fa::before { + content: var(--fa); } + .fa-classic, +.fas, .fa-solid, .far, .fa-regular { font-family: 'Font Awesome 6 Free'; } -.fab, -.fa-brands { +.fa-brands, +.fab { font-family: 'Font Awesome 6 Brands'; } .fa-1x { @@ -6941,7 +6948,7 @@ button.close { position: relative; } .fa-li { - left: calc(var(--fa-li-width, 2em) * -1); + left: calc(-1 * var(--fa-li-width, 2em)); position: absolute; text-align: center; width: var(--fa-li-width, 2em); @@ -6963,118 +6970,71 @@ button.close { margin-left: var(--fa-pull-margin, 0.3em); } .fa-beat { - -webkit-animation-name: fa-beat; - animation-name: fa-beat; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); - animation-timing-function: var(--fa-animation-timing, ease-in-out); } + animation-name: fa-beat; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-bounce { - -webkit-animation-name: fa-bounce; - animation-name: fa-bounce; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); - animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } + animation-name: fa-bounce; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } .fa-fade { - -webkit-animation-name: fa-fade; - animation-name: fa-fade; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); - animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + animation-name: fa-fade; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } .fa-beat-fade { - -webkit-animation-name: fa-beat-fade; - animation-name: fa-beat-fade; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); - animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + animation-name: fa-beat-fade; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } .fa-flip { - -webkit-animation-name: fa-flip; - animation-name: fa-flip; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); - animation-timing-function: var(--fa-animation-timing, ease-in-out); } + animation-name: fa-flip; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-shake { - -webkit-animation-name: fa-shake; - animation-name: fa-shake; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, linear); - animation-timing-function: var(--fa-animation-timing, linear); } + animation-name: fa-shake; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin { - -webkit-animation-name: fa-spin; - animation-name: fa-spin; - -webkit-animation-delay: var(--fa-animation-delay, 0s); - animation-delay: var(--fa-animation-delay, 0s); - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 2s); - animation-duration: var(--fa-animation-duration, 2s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, linear); - animation-timing-function: var(--fa-animation-timing, linear); } + animation-name: fa-spin; + animation-delay: var(--fa-animation-delay, 0s); + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 2s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin-reverse { --fa-animation-direction: reverse; } .fa-pulse, .fa-spin-pulse { - -webkit-animation-name: fa-spin; - animation-name: fa-spin; - -webkit-animation-direction: var(--fa-animation-direction, normal); - animation-direction: var(--fa-animation-direction, normal); - -webkit-animation-duration: var(--fa-animation-duration, 1s); - animation-duration: var(--fa-animation-duration, 1s); - -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); - animation-iteration-count: var(--fa-animation-iteration-count, infinite); - -webkit-animation-timing-function: var(--fa-animation-timing, steps(8)); - animation-timing-function: var(--fa-animation-timing, steps(8)); } + animation-name: fa-spin; + animation-direction: var(--fa-animation-direction, normal); + animation-duration: var(--fa-animation-duration, 1s); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-timing-function: var(--fa-animation-timing, steps(8)); } @media (prefers-reduced-motion: reduce) { .fa-beat, @@ -7086,219 +7046,97 @@ button.close { .fa-shake, .fa-spin, .fa-spin-pulse { - -webkit-animation-delay: -1ms; - animation-delay: -1ms; - -webkit-animation-duration: 1ms; - animation-duration: 1ms; - -webkit-animation-iteration-count: 1; - animation-iteration-count: 1; - -webkit-transition-delay: 0s; - transition-delay: 0s; - -webkit-transition-duration: 0s; - transition-duration: 0s; } } - -@-webkit-keyframes fa-beat { - 0%, 90% { - -webkit-transform: scale(1); - transform: scale(1); } - 45% { - -webkit-transform: scale(var(--fa-beat-scale, 1.25)); - transform: scale(var(--fa-beat-scale, 1.25)); } } + animation-delay: -1ms; + animation-duration: 1ms; + animation-iteration-count: 1; + transition-delay: 0s; + transition-duration: 0s; } } @keyframes fa-beat { 0%, 90% { - -webkit-transform: scale(1); - transform: scale(1); } + transform: scale(1); } 45% { - -webkit-transform: scale(var(--fa-beat-scale, 1.25)); - transform: scale(var(--fa-beat-scale, 1.25)); } } - -@-webkit-keyframes fa-bounce { - 0% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } - 10% { - -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); - transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } - 30% { - -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); - transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } - 50% { - -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); - transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } - 57% { - -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); - transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } - 64% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } - 100% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } } + transform: scale(var(--fa-beat-scale, 1.25)); } } @keyframes fa-bounce { 0% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } + transform: scale(1, 1) translateY(0); } 10% { - -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); - transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } 30% { - -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); - transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } 50% { - -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); - transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } 57% { - -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); - transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } 64% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } + transform: scale(1, 1) translateY(0); } 100% { - -webkit-transform: scale(1, 1) translateY(0); - transform: scale(1, 1) translateY(0); } } - -@-webkit-keyframes fa-fade { - 50% { - opacity: var(--fa-fade-opacity, 0.4); } } + transform: scale(1, 1) translateY(0); } } @keyframes fa-fade { 50% { opacity: var(--fa-fade-opacity, 0.4); } } -@-webkit-keyframes fa-beat-fade { - 0%, 100% { - opacity: var(--fa-beat-fade-opacity, 0.4); - -webkit-transform: scale(1); - transform: scale(1); } - 50% { - opacity: 1; - -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); - transform: scale(var(--fa-beat-fade-scale, 1.125)); } } - @keyframes fa-beat-fade { 0%, 100% { opacity: var(--fa-beat-fade-opacity, 0.4); - -webkit-transform: scale(1); - transform: scale(1); } + transform: scale(1); } 50% { opacity: 1; - -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); - transform: scale(var(--fa-beat-fade-scale, 1.125)); } } - -@-webkit-keyframes fa-flip { - 50% { - -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); - transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } + transform: scale(var(--fa-beat-fade-scale, 1.125)); } } @keyframes fa-flip { 50% { - -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); - transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } - -@-webkit-keyframes fa-shake { - 0% { - -webkit-transform: rotate(-15deg); - transform: rotate(-15deg); } - 4% { - -webkit-transform: rotate(15deg); - transform: rotate(15deg); } - 8%, 24% { - -webkit-transform: rotate(-18deg); - transform: rotate(-18deg); } - 12%, 28% { - -webkit-transform: rotate(18deg); - transform: rotate(18deg); } - 16% { - -webkit-transform: rotate(-22deg); - transform: rotate(-22deg); } - 20% { - -webkit-transform: rotate(22deg); - transform: rotate(22deg); } - 32% { - -webkit-transform: rotate(-12deg); - transform: rotate(-12deg); } - 36% { - -webkit-transform: rotate(12deg); - transform: rotate(12deg); } - 40%, 100% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } } + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } @keyframes fa-shake { 0% { - -webkit-transform: rotate(-15deg); - transform: rotate(-15deg); } + transform: rotate(-15deg); } 4% { - -webkit-transform: rotate(15deg); - transform: rotate(15deg); } + transform: rotate(15deg); } 8%, 24% { - -webkit-transform: rotate(-18deg); - transform: rotate(-18deg); } + transform: rotate(-18deg); } 12%, 28% { - -webkit-transform: rotate(18deg); - transform: rotate(18deg); } + transform: rotate(18deg); } 16% { - -webkit-transform: rotate(-22deg); - transform: rotate(-22deg); } + transform: rotate(-22deg); } 20% { - -webkit-transform: rotate(22deg); - transform: rotate(22deg); } + transform: rotate(22deg); } 32% { - -webkit-transform: rotate(-12deg); - transform: rotate(-12deg); } + transform: rotate(-12deg); } 36% { - -webkit-transform: rotate(12deg); - transform: rotate(12deg); } + transform: rotate(12deg); } 40%, 100% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } } - -@-webkit-keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } + transform: rotate(0deg); } } @keyframes fa-spin { 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); } + transform: rotate(0deg); } 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); } } + transform: rotate(360deg); } } .fa-rotate-90 { - -webkit-transform: rotate(90deg); - transform: rotate(90deg); } + transform: rotate(90deg); } .fa-rotate-180 { - -webkit-transform: rotate(180deg); - transform: rotate(180deg); } + transform: rotate(180deg); } .fa-rotate-270 { - -webkit-transform: rotate(270deg); - transform: rotate(270deg); } + transform: rotate(270deg); } .fa-flip-horizontal { - -webkit-transform: scale(-1, 1); - transform: scale(-1, 1); } + transform: scale(-1, 1); } .fa-flip-vertical { - -webkit-transform: scale(1, -1); - transform: scale(1, -1); } + transform: scale(1, -1); } .fa-flip-both, .fa-flip-horizontal.fa-flip-vertical { - -webkit-transform: scale(-1, -1); - transform: scale(-1, -1); } + transform: scale(-1, -1); } .fa-rotate-by { - -webkit-transform: rotate(var(--fa-rotate-angle, 0)); - transform: rotate(var(--fa-rotate-angle, 0)); } + transform: rotate(var(--fa-rotate-angle, 0)); } .fa-stack { display: inline-block; @@ -7328,5861 +7166,5891 @@ button.close { /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ -.fa-0::before { - content: "\30"; } +.fa-0 { + --fa: "\30"; } -.fa-1::before { - content: "\31"; } +.fa-1 { + --fa: "\31"; } -.fa-2::before { - content: "\32"; } +.fa-2 { + --fa: "\32"; } -.fa-3::before { - content: "\33"; } +.fa-3 { + --fa: "\33"; } -.fa-4::before { - content: "\34"; } +.fa-4 { + --fa: "\34"; } -.fa-5::before { - content: "\35"; } +.fa-5 { + --fa: "\35"; } -.fa-6::before { - content: "\36"; } +.fa-6 { + --fa: "\36"; } -.fa-7::before { - content: "\37"; } +.fa-7 { + --fa: "\37"; } -.fa-8::before { - content: "\38"; } +.fa-8 { + --fa: "\38"; } -.fa-9::before { - content: "\39"; } +.fa-9 { + --fa: "\39"; } -.fa-fill-drip::before { - content: "\f576"; } +.fa-fill-drip { + --fa: "\f576"; } -.fa-arrows-to-circle::before { - content: "\e4bd"; } +.fa-arrows-to-circle { + --fa: "\e4bd"; } -.fa-circle-chevron-right::before { - content: "\f138"; } +.fa-circle-chevron-right { + --fa: "\f138"; } -.fa-chevron-circle-right::before { - content: "\f138"; } +.fa-chevron-circle-right { + --fa: "\f138"; } -.fa-at::before { - content: "\40"; } +.fa-at { + --fa: "\40"; } -.fa-trash-can::before { - content: "\f2ed"; } +.fa-trash-can { + --fa: "\f2ed"; } -.fa-trash-alt::before { - content: "\f2ed"; } +.fa-trash-alt { + --fa: "\f2ed"; } -.fa-text-height::before { - content: "\f034"; } +.fa-text-height { + --fa: "\f034"; } -.fa-user-xmark::before { - content: "\f235"; } +.fa-user-xmark { + --fa: "\f235"; } -.fa-user-times::before { - content: "\f235"; } +.fa-user-times { + --fa: "\f235"; } -.fa-stethoscope::before { - content: "\f0f1"; } +.fa-stethoscope { + --fa: "\f0f1"; } -.fa-message::before { - content: "\f27a"; } +.fa-message { + --fa: "\f27a"; } -.fa-comment-alt::before { - content: "\f27a"; } +.fa-comment-alt { + --fa: "\f27a"; } -.fa-info::before { - content: "\f129"; } +.fa-info { + --fa: "\f129"; } -.fa-down-left-and-up-right-to-center::before { - content: "\f422"; } +.fa-down-left-and-up-right-to-center { + --fa: "\f422"; } -.fa-compress-alt::before { - content: "\f422"; } +.fa-compress-alt { + --fa: "\f422"; } -.fa-explosion::before { - content: "\e4e9"; } +.fa-explosion { + --fa: "\e4e9"; } -.fa-file-lines::before { - content: "\f15c"; } +.fa-file-lines { + --fa: "\f15c"; } -.fa-file-alt::before { - content: "\f15c"; } +.fa-file-alt { + --fa: "\f15c"; } -.fa-file-text::before { - content: "\f15c"; } +.fa-file-text { + --fa: "\f15c"; } -.fa-wave-square::before { - content: "\f83e"; } +.fa-wave-square { + --fa: "\f83e"; } -.fa-ring::before { - content: "\f70b"; } +.fa-ring { + --fa: "\f70b"; } -.fa-building-un::before { - content: "\e4d9"; } +.fa-building-un { + --fa: "\e4d9"; } -.fa-dice-three::before { - content: "\f527"; } +.fa-dice-three { + --fa: "\f527"; } -.fa-calendar-days::before { - content: "\f073"; } +.fa-calendar-days { + --fa: "\f073"; } -.fa-calendar-alt::before { - content: "\f073"; } +.fa-calendar-alt { + --fa: "\f073"; } -.fa-anchor-circle-check::before { - content: "\e4aa"; } +.fa-anchor-circle-check { + --fa: "\e4aa"; } -.fa-building-circle-arrow-right::before { - content: "\e4d1"; } +.fa-building-circle-arrow-right { + --fa: "\e4d1"; } -.fa-volleyball::before { - content: "\f45f"; } +.fa-volleyball { + --fa: "\f45f"; } -.fa-volleyball-ball::before { - content: "\f45f"; } +.fa-volleyball-ball { + --fa: "\f45f"; } -.fa-arrows-up-to-line::before { - content: "\e4c2"; } +.fa-arrows-up-to-line { + --fa: "\e4c2"; } -.fa-sort-down::before { - content: "\f0dd"; } +.fa-sort-down { + --fa: "\f0dd"; } -.fa-sort-desc::before { - content: "\f0dd"; } +.fa-sort-desc { + --fa: "\f0dd"; } -.fa-circle-minus::before { - content: "\f056"; } +.fa-circle-minus { + --fa: "\f056"; } -.fa-minus-circle::before { - content: "\f056"; } +.fa-minus-circle { + --fa: "\f056"; } -.fa-door-open::before { - content: "\f52b"; } +.fa-door-open { + --fa: "\f52b"; } -.fa-right-from-bracket::before { - content: "\f2f5"; } +.fa-right-from-bracket { + --fa: "\f2f5"; } -.fa-sign-out-alt::before { - content: "\f2f5"; } +.fa-sign-out-alt { + --fa: "\f2f5"; } -.fa-atom::before { - content: "\f5d2"; } +.fa-atom { + --fa: "\f5d2"; } -.fa-soap::before { - content: "\e06e"; } +.fa-soap { + --fa: "\e06e"; } -.fa-icons::before { - content: "\f86d"; } +.fa-icons { + --fa: "\f86d"; } -.fa-heart-music-camera-bolt::before { - content: "\f86d"; } +.fa-heart-music-camera-bolt { + --fa: "\f86d"; } -.fa-microphone-lines-slash::before { - content: "\f539"; } +.fa-microphone-lines-slash { + --fa: "\f539"; } -.fa-microphone-alt-slash::before { - content: "\f539"; } +.fa-microphone-alt-slash { + --fa: "\f539"; } -.fa-bridge-circle-check::before { - content: "\e4c9"; } +.fa-bridge-circle-check { + --fa: "\e4c9"; } -.fa-pump-medical::before { - content: "\e06a"; } +.fa-pump-medical { + --fa: "\e06a"; } -.fa-fingerprint::before { - content: "\f577"; } +.fa-fingerprint { + --fa: "\f577"; } -.fa-hand-point-right::before { - content: "\f0a4"; } +.fa-hand-point-right { + --fa: "\f0a4"; } -.fa-magnifying-glass-location::before { - content: "\f689"; } +.fa-magnifying-glass-location { + --fa: "\f689"; } -.fa-search-location::before { - content: "\f689"; } +.fa-search-location { + --fa: "\f689"; } -.fa-forward-step::before { - content: "\f051"; } +.fa-forward-step { + --fa: "\f051"; } -.fa-step-forward::before { - content: "\f051"; } +.fa-step-forward { + --fa: "\f051"; } -.fa-face-smile-beam::before { - content: "\f5b8"; } +.fa-face-smile-beam { + --fa: "\f5b8"; } -.fa-smile-beam::before { - content: "\f5b8"; } +.fa-smile-beam { + --fa: "\f5b8"; } -.fa-flag-checkered::before { - content: "\f11e"; } +.fa-flag-checkered { + --fa: "\f11e"; } -.fa-football::before { - content: "\f44e"; } +.fa-football { + --fa: "\f44e"; } -.fa-football-ball::before { - content: "\f44e"; } +.fa-football-ball { + --fa: "\f44e"; } -.fa-school-circle-exclamation::before { - content: "\e56c"; } +.fa-school-circle-exclamation { + --fa: "\e56c"; } -.fa-crop::before { - content: "\f125"; } +.fa-crop { + --fa: "\f125"; } -.fa-angles-down::before { - content: "\f103"; } +.fa-angles-down { + --fa: "\f103"; } -.fa-angle-double-down::before { - content: "\f103"; } +.fa-angle-double-down { + --fa: "\f103"; } -.fa-users-rectangle::before { - content: "\e594"; } +.fa-users-rectangle { + --fa: "\e594"; } -.fa-people-roof::before { - content: "\e537"; } +.fa-people-roof { + --fa: "\e537"; } -.fa-people-line::before { - content: "\e534"; } +.fa-people-line { + --fa: "\e534"; } -.fa-beer-mug-empty::before { - content: "\f0fc"; } +.fa-beer-mug-empty { + --fa: "\f0fc"; } -.fa-beer::before { - content: "\f0fc"; } +.fa-beer { + --fa: "\f0fc"; } -.fa-diagram-predecessor::before { - content: "\e477"; } +.fa-diagram-predecessor { + --fa: "\e477"; } -.fa-arrow-up-long::before { - content: "\f176"; } +.fa-arrow-up-long { + --fa: "\f176"; } -.fa-long-arrow-up::before { - content: "\f176"; } +.fa-long-arrow-up { + --fa: "\f176"; } -.fa-fire-flame-simple::before { - content: "\f46a"; } +.fa-fire-flame-simple { + --fa: "\f46a"; } -.fa-burn::before { - content: "\f46a"; } +.fa-burn { + --fa: "\f46a"; } -.fa-person::before { - content: "\f183"; } +.fa-person { + --fa: "\f183"; } -.fa-male::before { - content: "\f183"; } +.fa-male { + --fa: "\f183"; } -.fa-laptop::before { - content: "\f109"; } +.fa-laptop { + --fa: "\f109"; } -.fa-file-csv::before { - content: "\f6dd"; } +.fa-file-csv { + --fa: "\f6dd"; } -.fa-menorah::before { - content: "\f676"; } +.fa-menorah { + --fa: "\f676"; } -.fa-truck-plane::before { - content: "\e58f"; } +.fa-truck-plane { + --fa: "\e58f"; } -.fa-record-vinyl::before { - content: "\f8d9"; } +.fa-record-vinyl { + --fa: "\f8d9"; } -.fa-face-grin-stars::before { - content: "\f587"; } +.fa-face-grin-stars { + --fa: "\f587"; } -.fa-grin-stars::before { - content: "\f587"; } +.fa-grin-stars { + --fa: "\f587"; } -.fa-bong::before { - content: "\f55c"; } +.fa-bong { + --fa: "\f55c"; } -.fa-spaghetti-monster-flying::before { - content: "\f67b"; } +.fa-spaghetti-monster-flying { + --fa: "\f67b"; } -.fa-pastafarianism::before { - content: "\f67b"; } +.fa-pastafarianism { + --fa: "\f67b"; } -.fa-arrow-down-up-across-line::before { - content: "\e4af"; } +.fa-arrow-down-up-across-line { + --fa: "\e4af"; } -.fa-spoon::before { - content: "\f2e5"; } +.fa-spoon { + --fa: "\f2e5"; } -.fa-utensil-spoon::before { - content: "\f2e5"; } +.fa-utensil-spoon { + --fa: "\f2e5"; } -.fa-jar-wheat::before { - content: "\e517"; } +.fa-jar-wheat { + --fa: "\e517"; } -.fa-envelopes-bulk::before { - content: "\f674"; } +.fa-envelopes-bulk { + --fa: "\f674"; } -.fa-mail-bulk::before { - content: "\f674"; } +.fa-mail-bulk { + --fa: "\f674"; } -.fa-file-circle-exclamation::before { - content: "\e4eb"; } +.fa-file-circle-exclamation { + --fa: "\e4eb"; } -.fa-circle-h::before { - content: "\f47e"; } +.fa-circle-h { + --fa: "\f47e"; } -.fa-hospital-symbol::before { - content: "\f47e"; } +.fa-hospital-symbol { + --fa: "\f47e"; } -.fa-pager::before { - content: "\f815"; } +.fa-pager { + --fa: "\f815"; } -.fa-address-book::before { - content: "\f2b9"; } +.fa-address-book { + --fa: "\f2b9"; } -.fa-contact-book::before { - content: "\f2b9"; } +.fa-contact-book { + --fa: "\f2b9"; } -.fa-strikethrough::before { - content: "\f0cc"; } +.fa-strikethrough { + --fa: "\f0cc"; } -.fa-k::before { - content: "\4b"; } +.fa-k { + --fa: "\4b"; } -.fa-landmark-flag::before { - content: "\e51c"; } +.fa-landmark-flag { + --fa: "\e51c"; } -.fa-pencil::before { - content: "\f303"; } +.fa-pencil { + --fa: "\f303"; } -.fa-pencil-alt::before { - content: "\f303"; } +.fa-pencil-alt { + --fa: "\f303"; } -.fa-backward::before { - content: "\f04a"; } +.fa-backward { + --fa: "\f04a"; } -.fa-caret-right::before { - content: "\f0da"; } +.fa-caret-right { + --fa: "\f0da"; } -.fa-comments::before { - content: "\f086"; } +.fa-comments { + --fa: "\f086"; } -.fa-paste::before { - content: "\f0ea"; } +.fa-paste { + --fa: "\f0ea"; } -.fa-file-clipboard::before { - content: "\f0ea"; } +.fa-file-clipboard { + --fa: "\f0ea"; } -.fa-code-pull-request::before { - content: "\e13c"; } +.fa-code-pull-request { + --fa: "\e13c"; } -.fa-clipboard-list::before { - content: "\f46d"; } +.fa-clipboard-list { + --fa: "\f46d"; } -.fa-truck-ramp-box::before { - content: "\f4de"; } +.fa-truck-ramp-box { + --fa: "\f4de"; } -.fa-truck-loading::before { - content: "\f4de"; } +.fa-truck-loading { + --fa: "\f4de"; } -.fa-user-check::before { - content: "\f4fc"; } +.fa-user-check { + --fa: "\f4fc"; } -.fa-vial-virus::before { - content: "\e597"; } +.fa-vial-virus { + --fa: "\e597"; } -.fa-sheet-plastic::before { - content: "\e571"; } +.fa-sheet-plastic { + --fa: "\e571"; } -.fa-blog::before { - content: "\f781"; } +.fa-blog { + --fa: "\f781"; } -.fa-user-ninja::before { - content: "\f504"; } +.fa-user-ninja { + --fa: "\f504"; } -.fa-person-arrow-up-from-line::before { - content: "\e539"; } +.fa-person-arrow-up-from-line { + --fa: "\e539"; } -.fa-scroll-torah::before { - content: "\f6a0"; } +.fa-scroll-torah { + --fa: "\f6a0"; } -.fa-torah::before { - content: "\f6a0"; } +.fa-torah { + --fa: "\f6a0"; } -.fa-broom-ball::before { - content: "\f458"; } +.fa-broom-ball { + --fa: "\f458"; } -.fa-quidditch::before { - content: "\f458"; } +.fa-quidditch { + --fa: "\f458"; } -.fa-quidditch-broom-ball::before { - content: "\f458"; } +.fa-quidditch-broom-ball { + --fa: "\f458"; } -.fa-toggle-off::before { - content: "\f204"; } +.fa-toggle-off { + --fa: "\f204"; } -.fa-box-archive::before { - content: "\f187"; } +.fa-box-archive { + --fa: "\f187"; } -.fa-archive::before { - content: "\f187"; } +.fa-archive { + --fa: "\f187"; } -.fa-person-drowning::before { - content: "\e545"; } +.fa-person-drowning { + --fa: "\e545"; } -.fa-arrow-down-9-1::before { - content: "\f886"; } +.fa-arrow-down-9-1 { + --fa: "\f886"; } -.fa-sort-numeric-desc::before { - content: "\f886"; } +.fa-sort-numeric-desc { + --fa: "\f886"; } -.fa-sort-numeric-down-alt::before { - content: "\f886"; } +.fa-sort-numeric-down-alt { + --fa: "\f886"; } -.fa-face-grin-tongue-squint::before { - content: "\f58a"; } +.fa-face-grin-tongue-squint { + --fa: "\f58a"; } -.fa-grin-tongue-squint::before { - content: "\f58a"; } +.fa-grin-tongue-squint { + --fa: "\f58a"; } -.fa-spray-can::before { - content: "\f5bd"; } +.fa-spray-can { + --fa: "\f5bd"; } -.fa-truck-monster::before { - content: "\f63b"; } +.fa-truck-monster { + --fa: "\f63b"; } -.fa-w::before { - content: "\57"; } +.fa-w { + --fa: "\57"; } -.fa-earth-africa::before { - content: "\f57c"; } +.fa-earth-africa { + --fa: "\f57c"; } -.fa-globe-africa::before { - content: "\f57c"; } +.fa-globe-africa { + --fa: "\f57c"; } -.fa-rainbow::before { - content: "\f75b"; } +.fa-rainbow { + --fa: "\f75b"; } -.fa-circle-notch::before { - content: "\f1ce"; } +.fa-circle-notch { + --fa: "\f1ce"; } -.fa-tablet-screen-button::before { - content: "\f3fa"; } +.fa-tablet-screen-button { + --fa: "\f3fa"; } -.fa-tablet-alt::before { - content: "\f3fa"; } +.fa-tablet-alt { + --fa: "\f3fa"; } -.fa-paw::before { - content: "\f1b0"; } +.fa-paw { + --fa: "\f1b0"; } -.fa-cloud::before { - content: "\f0c2"; } +.fa-cloud { + --fa: "\f0c2"; } -.fa-trowel-bricks::before { - content: "\e58a"; } +.fa-trowel-bricks { + --fa: "\e58a"; } -.fa-face-flushed::before { - content: "\f579"; } +.fa-face-flushed { + --fa: "\f579"; } -.fa-flushed::before { - content: "\f579"; } +.fa-flushed { + --fa: "\f579"; } -.fa-hospital-user::before { - content: "\f80d"; } +.fa-hospital-user { + --fa: "\f80d"; } -.fa-tent-arrow-left-right::before { - content: "\e57f"; } +.fa-tent-arrow-left-right { + --fa: "\e57f"; } -.fa-gavel::before { - content: "\f0e3"; } +.fa-gavel { + --fa: "\f0e3"; } -.fa-legal::before { - content: "\f0e3"; } +.fa-legal { + --fa: "\f0e3"; } -.fa-binoculars::before { - content: "\f1e5"; } +.fa-binoculars { + --fa: "\f1e5"; } -.fa-microphone-slash::before { - content: "\f131"; } +.fa-microphone-slash { + --fa: "\f131"; } -.fa-box-tissue::before { - content: "\e05b"; } +.fa-box-tissue { + --fa: "\e05b"; } -.fa-motorcycle::before { - content: "\f21c"; } +.fa-motorcycle { + --fa: "\f21c"; } -.fa-bell-concierge::before { - content: "\f562"; } +.fa-bell-concierge { + --fa: "\f562"; } -.fa-concierge-bell::before { - content: "\f562"; } +.fa-concierge-bell { + --fa: "\f562"; } -.fa-pen-ruler::before { - content: "\f5ae"; } +.fa-pen-ruler { + --fa: "\f5ae"; } -.fa-pencil-ruler::before { - content: "\f5ae"; } +.fa-pencil-ruler { + --fa: "\f5ae"; } -.fa-people-arrows::before { - content: "\e068"; } +.fa-people-arrows { + --fa: "\e068"; } -.fa-people-arrows-left-right::before { - content: "\e068"; } +.fa-people-arrows-left-right { + --fa: "\e068"; } -.fa-mars-and-venus-burst::before { - content: "\e523"; } +.fa-mars-and-venus-burst { + --fa: "\e523"; } -.fa-square-caret-right::before { - content: "\f152"; } +.fa-square-caret-right { + --fa: "\f152"; } -.fa-caret-square-right::before { - content: "\f152"; } +.fa-caret-square-right { + --fa: "\f152"; } -.fa-scissors::before { - content: "\f0c4"; } +.fa-scissors { + --fa: "\f0c4"; } -.fa-cut::before { - content: "\f0c4"; } +.fa-cut { + --fa: "\f0c4"; } -.fa-sun-plant-wilt::before { - content: "\e57a"; } +.fa-sun-plant-wilt { + --fa: "\e57a"; } -.fa-toilets-portable::before { - content: "\e584"; } +.fa-toilets-portable { + --fa: "\e584"; } -.fa-hockey-puck::before { - content: "\f453"; } +.fa-hockey-puck { + --fa: "\f453"; } -.fa-table::before { - content: "\f0ce"; } +.fa-table { + --fa: "\f0ce"; } -.fa-magnifying-glass-arrow-right::before { - content: "\e521"; } +.fa-magnifying-glass-arrow-right { + --fa: "\e521"; } -.fa-tachograph-digital::before { - content: "\f566"; } +.fa-tachograph-digital { + --fa: "\f566"; } -.fa-digital-tachograph::before { - content: "\f566"; } +.fa-digital-tachograph { + --fa: "\f566"; } -.fa-users-slash::before { - content: "\e073"; } +.fa-users-slash { + --fa: "\e073"; } -.fa-clover::before { - content: "\e139"; } +.fa-clover { + --fa: "\e139"; } -.fa-reply::before { - content: "\f3e5"; } +.fa-reply { + --fa: "\f3e5"; } -.fa-mail-reply::before { - content: "\f3e5"; } +.fa-mail-reply { + --fa: "\f3e5"; } -.fa-star-and-crescent::before { - content: "\f699"; } +.fa-star-and-crescent { + --fa: "\f699"; } -.fa-house-fire::before { - content: "\e50c"; } +.fa-house-fire { + --fa: "\e50c"; } -.fa-square-minus::before { - content: "\f146"; } +.fa-square-minus { + --fa: "\f146"; } -.fa-minus-square::before { - content: "\f146"; } +.fa-minus-square { + --fa: "\f146"; } -.fa-helicopter::before { - content: "\f533"; } +.fa-helicopter { + --fa: "\f533"; } -.fa-compass::before { - content: "\f14e"; } +.fa-compass { + --fa: "\f14e"; } -.fa-square-caret-down::before { - content: "\f150"; } +.fa-square-caret-down { + --fa: "\f150"; } -.fa-caret-square-down::before { - content: "\f150"; } +.fa-caret-square-down { + --fa: "\f150"; } -.fa-file-circle-question::before { - content: "\e4ef"; } +.fa-file-circle-question { + --fa: "\e4ef"; } -.fa-laptop-code::before { - content: "\f5fc"; } +.fa-laptop-code { + --fa: "\f5fc"; } -.fa-swatchbook::before { - content: "\f5c3"; } +.fa-swatchbook { + --fa: "\f5c3"; } -.fa-prescription-bottle::before { - content: "\f485"; } +.fa-prescription-bottle { + --fa: "\f485"; } -.fa-bars::before { - content: "\f0c9"; } +.fa-bars { + --fa: "\f0c9"; } -.fa-navicon::before { - content: "\f0c9"; } +.fa-navicon { + --fa: "\f0c9"; } -.fa-people-group::before { - content: "\e533"; } +.fa-people-group { + --fa: "\e533"; } -.fa-hourglass-end::before { - content: "\f253"; } +.fa-hourglass-end { + --fa: "\f253"; } -.fa-hourglass-3::before { - content: "\f253"; } +.fa-hourglass-3 { + --fa: "\f253"; } -.fa-heart-crack::before { - content: "\f7a9"; } +.fa-heart-crack { + --fa: "\f7a9"; } -.fa-heart-broken::before { - content: "\f7a9"; } +.fa-heart-broken { + --fa: "\f7a9"; } -.fa-square-up-right::before { - content: "\f360"; } +.fa-square-up-right { + --fa: "\f360"; } -.fa-external-link-square-alt::before { - content: "\f360"; } +.fa-external-link-square-alt { + --fa: "\f360"; } -.fa-face-kiss-beam::before { - content: "\f597"; } +.fa-face-kiss-beam { + --fa: "\f597"; } -.fa-kiss-beam::before { - content: "\f597"; } +.fa-kiss-beam { + --fa: "\f597"; } -.fa-film::before { - content: "\f008"; } +.fa-film { + --fa: "\f008"; } -.fa-ruler-horizontal::before { - content: "\f547"; } +.fa-ruler-horizontal { + --fa: "\f547"; } -.fa-people-robbery::before { - content: "\e536"; } +.fa-people-robbery { + --fa: "\e536"; } -.fa-lightbulb::before { - content: "\f0eb"; } +.fa-lightbulb { + --fa: "\f0eb"; } -.fa-caret-left::before { - content: "\f0d9"; } +.fa-caret-left { + --fa: "\f0d9"; } -.fa-circle-exclamation::before { - content: "\f06a"; } +.fa-circle-exclamation { + --fa: "\f06a"; } -.fa-exclamation-circle::before { - content: "\f06a"; } +.fa-exclamation-circle { + --fa: "\f06a"; } -.fa-school-circle-xmark::before { - content: "\e56d"; } +.fa-school-circle-xmark { + --fa: "\e56d"; } -.fa-arrow-right-from-bracket::before { - content: "\f08b"; } +.fa-arrow-right-from-bracket { + --fa: "\f08b"; } -.fa-sign-out::before { - content: "\f08b"; } +.fa-sign-out { + --fa: "\f08b"; } -.fa-circle-chevron-down::before { - content: "\f13a"; } +.fa-circle-chevron-down { + --fa: "\f13a"; } -.fa-chevron-circle-down::before { - content: "\f13a"; } +.fa-chevron-circle-down { + --fa: "\f13a"; } -.fa-unlock-keyhole::before { - content: "\f13e"; } +.fa-unlock-keyhole { + --fa: "\f13e"; } -.fa-unlock-alt::before { - content: "\f13e"; } +.fa-unlock-alt { + --fa: "\f13e"; } -.fa-cloud-showers-heavy::before { - content: "\f740"; } +.fa-cloud-showers-heavy { + --fa: "\f740"; } -.fa-headphones-simple::before { - content: "\f58f"; } +.fa-headphones-simple { + --fa: "\f58f"; } -.fa-headphones-alt::before { - content: "\f58f"; } +.fa-headphones-alt { + --fa: "\f58f"; } -.fa-sitemap::before { - content: "\f0e8"; } +.fa-sitemap { + --fa: "\f0e8"; } -.fa-circle-dollar-to-slot::before { - content: "\f4b9"; } +.fa-circle-dollar-to-slot { + --fa: "\f4b9"; } -.fa-donate::before { - content: "\f4b9"; } +.fa-donate { + --fa: "\f4b9"; } -.fa-memory::before { - content: "\f538"; } +.fa-memory { + --fa: "\f538"; } -.fa-road-spikes::before { - content: "\e568"; } +.fa-road-spikes { + --fa: "\e568"; } -.fa-fire-burner::before { - content: "\e4f1"; } +.fa-fire-burner { + --fa: "\e4f1"; } -.fa-flag::before { - content: "\f024"; } +.fa-flag { + --fa: "\f024"; } -.fa-hanukiah::before { - content: "\f6e6"; } +.fa-hanukiah { + --fa: "\f6e6"; } -.fa-feather::before { - content: "\f52d"; } +.fa-feather { + --fa: "\f52d"; } -.fa-volume-low::before { - content: "\f027"; } +.fa-volume-low { + --fa: "\f027"; } -.fa-volume-down::before { - content: "\f027"; } +.fa-volume-down { + --fa: "\f027"; } -.fa-comment-slash::before { - content: "\f4b3"; } +.fa-comment-slash { + --fa: "\f4b3"; } -.fa-cloud-sun-rain::before { - content: "\f743"; } +.fa-cloud-sun-rain { + --fa: "\f743"; } -.fa-compress::before { - content: "\f066"; } +.fa-compress { + --fa: "\f066"; } -.fa-wheat-awn::before { - content: "\e2cd"; } +.fa-wheat-awn { + --fa: "\e2cd"; } -.fa-wheat-alt::before { - content: "\e2cd"; } +.fa-wheat-alt { + --fa: "\e2cd"; } -.fa-ankh::before { - content: "\f644"; } +.fa-ankh { + --fa: "\f644"; } -.fa-hands-holding-child::before { - content: "\e4fa"; } +.fa-hands-holding-child { + --fa: "\e4fa"; } -.fa-asterisk::before { - content: "\2a"; } +.fa-asterisk { + --fa: "\2a"; } -.fa-square-check::before { - content: "\f14a"; } +.fa-square-check { + --fa: "\f14a"; } -.fa-check-square::before { - content: "\f14a"; } +.fa-check-square { + --fa: "\f14a"; } -.fa-peseta-sign::before { - content: "\e221"; } +.fa-peseta-sign { + --fa: "\e221"; } -.fa-heading::before { - content: "\f1dc"; } +.fa-heading { + --fa: "\f1dc"; } -.fa-header::before { - content: "\f1dc"; } +.fa-header { + --fa: "\f1dc"; } -.fa-ghost::before { - content: "\f6e2"; } +.fa-ghost { + --fa: "\f6e2"; } -.fa-list::before { - content: "\f03a"; } +.fa-list { + --fa: "\f03a"; } -.fa-list-squares::before { - content: "\f03a"; } +.fa-list-squares { + --fa: "\f03a"; } -.fa-square-phone-flip::before { - content: "\f87b"; } +.fa-square-phone-flip { + --fa: "\f87b"; } -.fa-phone-square-alt::before { - content: "\f87b"; } +.fa-phone-square-alt { + --fa: "\f87b"; } -.fa-cart-plus::before { - content: "\f217"; } +.fa-cart-plus { + --fa: "\f217"; } -.fa-gamepad::before { - content: "\f11b"; } +.fa-gamepad { + --fa: "\f11b"; } -.fa-circle-dot::before { - content: "\f192"; } +.fa-circle-dot { + --fa: "\f192"; } -.fa-dot-circle::before { - content: "\f192"; } +.fa-dot-circle { + --fa: "\f192"; } -.fa-face-dizzy::before { - content: "\f567"; } +.fa-face-dizzy { + --fa: "\f567"; } -.fa-dizzy::before { - content: "\f567"; } +.fa-dizzy { + --fa: "\f567"; } -.fa-egg::before { - content: "\f7fb"; } +.fa-egg { + --fa: "\f7fb"; } -.fa-house-medical-circle-xmark::before { - content: "\e513"; } +.fa-house-medical-circle-xmark { + --fa: "\e513"; } -.fa-campground::before { - content: "\f6bb"; } +.fa-campground { + --fa: "\f6bb"; } -.fa-folder-plus::before { - content: "\f65e"; } +.fa-folder-plus { + --fa: "\f65e"; } -.fa-futbol::before { - content: "\f1e3"; } +.fa-futbol { + --fa: "\f1e3"; } -.fa-futbol-ball::before { - content: "\f1e3"; } +.fa-futbol-ball { + --fa: "\f1e3"; } -.fa-soccer-ball::before { - content: "\f1e3"; } +.fa-soccer-ball { + --fa: "\f1e3"; } -.fa-paintbrush::before { - content: "\f1fc"; } +.fa-paintbrush { + --fa: "\f1fc"; } -.fa-paint-brush::before { - content: "\f1fc"; } +.fa-paint-brush { + --fa: "\f1fc"; } -.fa-lock::before { - content: "\f023"; } +.fa-lock { + --fa: "\f023"; } -.fa-gas-pump::before { - content: "\f52f"; } +.fa-gas-pump { + --fa: "\f52f"; } -.fa-hot-tub-person::before { - content: "\f593"; } +.fa-hot-tub-person { + --fa: "\f593"; } -.fa-hot-tub::before { - content: "\f593"; } +.fa-hot-tub { + --fa: "\f593"; } -.fa-map-location::before { - content: "\f59f"; } +.fa-map-location { + --fa: "\f59f"; } -.fa-map-marked::before { - content: "\f59f"; } +.fa-map-marked { + --fa: "\f59f"; } -.fa-house-flood-water::before { - content: "\e50e"; } +.fa-house-flood-water { + --fa: "\e50e"; } -.fa-tree::before { - content: "\f1bb"; } +.fa-tree { + --fa: "\f1bb"; } -.fa-bridge-lock::before { - content: "\e4cc"; } +.fa-bridge-lock { + --fa: "\e4cc"; } -.fa-sack-dollar::before { - content: "\f81d"; } +.fa-sack-dollar { + --fa: "\f81d"; } -.fa-pen-to-square::before { - content: "\f044"; } +.fa-pen-to-square { + --fa: "\f044"; } -.fa-edit::before { - content: "\f044"; } +.fa-edit { + --fa: "\f044"; } -.fa-car-side::before { - content: "\f5e4"; } +.fa-car-side { + --fa: "\f5e4"; } -.fa-share-nodes::before { - content: "\f1e0"; } +.fa-share-nodes { + --fa: "\f1e0"; } -.fa-share-alt::before { - content: "\f1e0"; } +.fa-share-alt { + --fa: "\f1e0"; } -.fa-heart-circle-minus::before { - content: "\e4ff"; } +.fa-heart-circle-minus { + --fa: "\e4ff"; } -.fa-hourglass-half::before { - content: "\f252"; } +.fa-hourglass-half { + --fa: "\f252"; } -.fa-hourglass-2::before { - content: "\f252"; } +.fa-hourglass-2 { + --fa: "\f252"; } -.fa-microscope::before { - content: "\f610"; } +.fa-microscope { + --fa: "\f610"; } -.fa-sink::before { - content: "\e06d"; } +.fa-sink { + --fa: "\e06d"; } -.fa-bag-shopping::before { - content: "\f290"; } +.fa-bag-shopping { + --fa: "\f290"; } -.fa-shopping-bag::before { - content: "\f290"; } +.fa-shopping-bag { + --fa: "\f290"; } -.fa-arrow-down-z-a::before { - content: "\f881"; } +.fa-arrow-down-z-a { + --fa: "\f881"; } -.fa-sort-alpha-desc::before { - content: "\f881"; } +.fa-sort-alpha-desc { + --fa: "\f881"; } -.fa-sort-alpha-down-alt::before { - content: "\f881"; } +.fa-sort-alpha-down-alt { + --fa: "\f881"; } -.fa-mitten::before { - content: "\f7b5"; } +.fa-mitten { + --fa: "\f7b5"; } -.fa-person-rays::before { - content: "\e54d"; } +.fa-person-rays { + --fa: "\e54d"; } -.fa-users::before { - content: "\f0c0"; } +.fa-users { + --fa: "\f0c0"; } -.fa-eye-slash::before { - content: "\f070"; } +.fa-eye-slash { + --fa: "\f070"; } -.fa-flask-vial::before { - content: "\e4f3"; } +.fa-flask-vial { + --fa: "\e4f3"; } -.fa-hand::before { - content: "\f256"; } +.fa-hand { + --fa: "\f256"; } -.fa-hand-paper::before { - content: "\f256"; } +.fa-hand-paper { + --fa: "\f256"; } -.fa-om::before { - content: "\f679"; } +.fa-om { + --fa: "\f679"; } -.fa-worm::before { - content: "\e599"; } +.fa-worm { + --fa: "\e599"; } -.fa-house-circle-xmark::before { - content: "\e50b"; } +.fa-house-circle-xmark { + --fa: "\e50b"; } -.fa-plug::before { - content: "\f1e6"; } +.fa-plug { + --fa: "\f1e6"; } -.fa-chevron-up::before { - content: "\f077"; } +.fa-chevron-up { + --fa: "\f077"; } -.fa-hand-spock::before { - content: "\f259"; } +.fa-hand-spock { + --fa: "\f259"; } -.fa-stopwatch::before { - content: "\f2f2"; } +.fa-stopwatch { + --fa: "\f2f2"; } -.fa-face-kiss::before { - content: "\f596"; } +.fa-face-kiss { + --fa: "\f596"; } -.fa-kiss::before { - content: "\f596"; } +.fa-kiss { + --fa: "\f596"; } -.fa-bridge-circle-xmark::before { - content: "\e4cb"; } +.fa-bridge-circle-xmark { + --fa: "\e4cb"; } -.fa-face-grin-tongue::before { - content: "\f589"; } +.fa-face-grin-tongue { + --fa: "\f589"; } -.fa-grin-tongue::before { - content: "\f589"; } +.fa-grin-tongue { + --fa: "\f589"; } -.fa-chess-bishop::before { - content: "\f43a"; } +.fa-chess-bishop { + --fa: "\f43a"; } -.fa-face-grin-wink::before { - content: "\f58c"; } +.fa-face-grin-wink { + --fa: "\f58c"; } -.fa-grin-wink::before { - content: "\f58c"; } +.fa-grin-wink { + --fa: "\f58c"; } -.fa-ear-deaf::before { - content: "\f2a4"; } +.fa-ear-deaf { + --fa: "\f2a4"; } -.fa-deaf::before { - content: "\f2a4"; } +.fa-deaf { + --fa: "\f2a4"; } -.fa-deafness::before { - content: "\f2a4"; } +.fa-deafness { + --fa: "\f2a4"; } -.fa-hard-of-hearing::before { - content: "\f2a4"; } +.fa-hard-of-hearing { + --fa: "\f2a4"; } -.fa-road-circle-check::before { - content: "\e564"; } +.fa-road-circle-check { + --fa: "\e564"; } -.fa-dice-five::before { - content: "\f523"; } +.fa-dice-five { + --fa: "\f523"; } -.fa-square-rss::before { - content: "\f143"; } +.fa-square-rss { + --fa: "\f143"; } -.fa-rss-square::before { - content: "\f143"; } +.fa-rss-square { + --fa: "\f143"; } -.fa-land-mine-on::before { - content: "\e51b"; } +.fa-land-mine-on { + --fa: "\e51b"; } -.fa-i-cursor::before { - content: "\f246"; } +.fa-i-cursor { + --fa: "\f246"; } -.fa-stamp::before { - content: "\f5bf"; } +.fa-stamp { + --fa: "\f5bf"; } -.fa-stairs::before { - content: "\e289"; } +.fa-stairs { + --fa: "\e289"; } -.fa-i::before { - content: "\49"; } +.fa-i { + --fa: "\49"; } -.fa-hryvnia-sign::before { - content: "\f6f2"; } +.fa-hryvnia-sign { + --fa: "\f6f2"; } -.fa-hryvnia::before { - content: "\f6f2"; } +.fa-hryvnia { + --fa: "\f6f2"; } -.fa-pills::before { - content: "\f484"; } +.fa-pills { + --fa: "\f484"; } -.fa-face-grin-wide::before { - content: "\f581"; } +.fa-face-grin-wide { + --fa: "\f581"; } -.fa-grin-alt::before { - content: "\f581"; } +.fa-grin-alt { + --fa: "\f581"; } -.fa-tooth::before { - content: "\f5c9"; } +.fa-tooth { + --fa: "\f5c9"; } -.fa-v::before { - content: "\56"; } +.fa-v { + --fa: "\56"; } -.fa-bangladeshi-taka-sign::before { - content: "\e2e6"; } +.fa-bangladeshi-taka-sign { + --fa: "\e2e6"; } -.fa-bicycle::before { - content: "\f206"; } +.fa-bicycle { + --fa: "\f206"; } -.fa-staff-snake::before { - content: "\e579"; } +.fa-staff-snake { + --fa: "\e579"; } -.fa-rod-asclepius::before { - content: "\e579"; } +.fa-rod-asclepius { + --fa: "\e579"; } -.fa-rod-snake::before { - content: "\e579"; } +.fa-rod-snake { + --fa: "\e579"; } -.fa-staff-aesculapius::before { - content: "\e579"; } +.fa-staff-aesculapius { + --fa: "\e579"; } -.fa-head-side-cough-slash::before { - content: "\e062"; } +.fa-head-side-cough-slash { + --fa: "\e062"; } -.fa-truck-medical::before { - content: "\f0f9"; } +.fa-truck-medical { + --fa: "\f0f9"; } -.fa-ambulance::before { - content: "\f0f9"; } +.fa-ambulance { + --fa: "\f0f9"; } -.fa-wheat-awn-circle-exclamation::before { - content: "\e598"; } +.fa-wheat-awn-circle-exclamation { + --fa: "\e598"; } -.fa-snowman::before { - content: "\f7d0"; } +.fa-snowman { + --fa: "\f7d0"; } -.fa-mortar-pestle::before { - content: "\f5a7"; } +.fa-mortar-pestle { + --fa: "\f5a7"; } -.fa-road-barrier::before { - content: "\e562"; } +.fa-road-barrier { + --fa: "\e562"; } -.fa-school::before { - content: "\f549"; } +.fa-school { + --fa: "\f549"; } -.fa-igloo::before { - content: "\f7ae"; } +.fa-igloo { + --fa: "\f7ae"; } -.fa-joint::before { - content: "\f595"; } +.fa-joint { + --fa: "\f595"; } -.fa-angle-right::before { - content: "\f105"; } +.fa-angle-right { + --fa: "\f105"; } -.fa-horse::before { - content: "\f6f0"; } +.fa-horse { + --fa: "\f6f0"; } -.fa-q::before { - content: "\51"; } +.fa-q { + --fa: "\51"; } -.fa-g::before { - content: "\47"; } +.fa-g { + --fa: "\47"; } -.fa-notes-medical::before { - content: "\f481"; } +.fa-notes-medical { + --fa: "\f481"; } -.fa-temperature-half::before { - content: "\f2c9"; } +.fa-temperature-half { + --fa: "\f2c9"; } -.fa-temperature-2::before { - content: "\f2c9"; } +.fa-temperature-2 { + --fa: "\f2c9"; } -.fa-thermometer-2::before { - content: "\f2c9"; } +.fa-thermometer-2 { + --fa: "\f2c9"; } -.fa-thermometer-half::before { - content: "\f2c9"; } +.fa-thermometer-half { + --fa: "\f2c9"; } -.fa-dong-sign::before { - content: "\e169"; } +.fa-dong-sign { + --fa: "\e169"; } -.fa-capsules::before { - content: "\f46b"; } +.fa-capsules { + --fa: "\f46b"; } -.fa-poo-storm::before { - content: "\f75a"; } +.fa-poo-storm { + --fa: "\f75a"; } -.fa-poo-bolt::before { - content: "\f75a"; } +.fa-poo-bolt { + --fa: "\f75a"; } -.fa-face-frown-open::before { - content: "\f57a"; } +.fa-face-frown-open { + --fa: "\f57a"; } -.fa-frown-open::before { - content: "\f57a"; } +.fa-frown-open { + --fa: "\f57a"; } -.fa-hand-point-up::before { - content: "\f0a6"; } +.fa-hand-point-up { + --fa: "\f0a6"; } -.fa-money-bill::before { - content: "\f0d6"; } +.fa-money-bill { + --fa: "\f0d6"; } -.fa-bookmark::before { - content: "\f02e"; } +.fa-bookmark { + --fa: "\f02e"; } -.fa-align-justify::before { - content: "\f039"; } +.fa-align-justify { + --fa: "\f039"; } -.fa-umbrella-beach::before { - content: "\f5ca"; } +.fa-umbrella-beach { + --fa: "\f5ca"; } -.fa-helmet-un::before { - content: "\e503"; } +.fa-helmet-un { + --fa: "\e503"; } -.fa-bullseye::before { - content: "\f140"; } +.fa-bullseye { + --fa: "\f140"; } -.fa-bacon::before { - content: "\f7e5"; } +.fa-bacon { + --fa: "\f7e5"; } -.fa-hand-point-down::before { - content: "\f0a7"; } +.fa-hand-point-down { + --fa: "\f0a7"; } -.fa-arrow-up-from-bracket::before { - content: "\e09a"; } +.fa-arrow-up-from-bracket { + --fa: "\e09a"; } -.fa-folder::before { - content: "\f07b"; } +.fa-folder { + --fa: "\f07b"; } -.fa-folder-blank::before { - content: "\f07b"; } +.fa-folder-blank { + --fa: "\f07b"; } -.fa-file-waveform::before { - content: "\f478"; } +.fa-file-waveform { + --fa: "\f478"; } -.fa-file-medical-alt::before { - content: "\f478"; } +.fa-file-medical-alt { + --fa: "\f478"; } -.fa-radiation::before { - content: "\f7b9"; } +.fa-radiation { + --fa: "\f7b9"; } -.fa-chart-simple::before { - content: "\e473"; } +.fa-chart-simple { + --fa: "\e473"; } -.fa-mars-stroke::before { - content: "\f229"; } +.fa-mars-stroke { + --fa: "\f229"; } -.fa-vial::before { - content: "\f492"; } +.fa-vial { + --fa: "\f492"; } -.fa-gauge::before { - content: "\f624"; } +.fa-gauge { + --fa: "\f624"; } -.fa-dashboard::before { - content: "\f624"; } +.fa-dashboard { + --fa: "\f624"; } -.fa-gauge-med::before { - content: "\f624"; } +.fa-gauge-med { + --fa: "\f624"; } -.fa-tachometer-alt-average::before { - content: "\f624"; } +.fa-tachometer-alt-average { + --fa: "\f624"; } -.fa-wand-magic-sparkles::before { - content: "\e2ca"; } +.fa-wand-magic-sparkles { + --fa: "\e2ca"; } -.fa-magic-wand-sparkles::before { - content: "\e2ca"; } +.fa-magic-wand-sparkles { + --fa: "\e2ca"; } -.fa-e::before { - content: "\45"; } +.fa-e { + --fa: "\45"; } -.fa-pen-clip::before { - content: "\f305"; } +.fa-pen-clip { + --fa: "\f305"; } -.fa-pen-alt::before { - content: "\f305"; } +.fa-pen-alt { + --fa: "\f305"; } -.fa-bridge-circle-exclamation::before { - content: "\e4ca"; } +.fa-bridge-circle-exclamation { + --fa: "\e4ca"; } -.fa-user::before { - content: "\f007"; } +.fa-user { + --fa: "\f007"; } -.fa-school-circle-check::before { - content: "\e56b"; } +.fa-school-circle-check { + --fa: "\e56b"; } -.fa-dumpster::before { - content: "\f793"; } +.fa-dumpster { + --fa: "\f793"; } -.fa-van-shuttle::before { - content: "\f5b6"; } +.fa-van-shuttle { + --fa: "\f5b6"; } -.fa-shuttle-van::before { - content: "\f5b6"; } +.fa-shuttle-van { + --fa: "\f5b6"; } -.fa-building-user::before { - content: "\e4da"; } +.fa-building-user { + --fa: "\e4da"; } -.fa-square-caret-left::before { - content: "\f191"; } +.fa-square-caret-left { + --fa: "\f191"; } -.fa-caret-square-left::before { - content: "\f191"; } +.fa-caret-square-left { + --fa: "\f191"; } -.fa-highlighter::before { - content: "\f591"; } +.fa-highlighter { + --fa: "\f591"; } -.fa-key::before { - content: "\f084"; } +.fa-key { + --fa: "\f084"; } -.fa-bullhorn::before { - content: "\f0a1"; } +.fa-bullhorn { + --fa: "\f0a1"; } -.fa-globe::before { - content: "\f0ac"; } +.fa-globe { + --fa: "\f0ac"; } -.fa-synagogue::before { - content: "\f69b"; } +.fa-synagogue { + --fa: "\f69b"; } -.fa-person-half-dress::before { - content: "\e548"; } +.fa-person-half-dress { + --fa: "\e548"; } -.fa-road-bridge::before { - content: "\e563"; } +.fa-road-bridge { + --fa: "\e563"; } -.fa-location-arrow::before { - content: "\f124"; } +.fa-location-arrow { + --fa: "\f124"; } -.fa-c::before { - content: "\43"; } +.fa-c { + --fa: "\43"; } -.fa-tablet-button::before { - content: "\f10a"; } +.fa-tablet-button { + --fa: "\f10a"; } -.fa-building-lock::before { - content: "\e4d6"; } +.fa-building-lock { + --fa: "\e4d6"; } -.fa-pizza-slice::before { - content: "\f818"; } +.fa-pizza-slice { + --fa: "\f818"; } -.fa-money-bill-wave::before { - content: "\f53a"; } +.fa-money-bill-wave { + --fa: "\f53a"; } -.fa-chart-area::before { - content: "\f1fe"; } +.fa-chart-area { + --fa: "\f1fe"; } -.fa-area-chart::before { - content: "\f1fe"; } +.fa-area-chart { + --fa: "\f1fe"; } -.fa-house-flag::before { - content: "\e50d"; } +.fa-house-flag { + --fa: "\e50d"; } -.fa-person-circle-minus::before { - content: "\e540"; } +.fa-person-circle-minus { + --fa: "\e540"; } -.fa-ban::before { - content: "\f05e"; } +.fa-ban { + --fa: "\f05e"; } -.fa-cancel::before { - content: "\f05e"; } +.fa-cancel { + --fa: "\f05e"; } -.fa-camera-rotate::before { - content: "\e0d8"; } +.fa-camera-rotate { + --fa: "\e0d8"; } -.fa-spray-can-sparkles::before { - content: "\f5d0"; } +.fa-spray-can-sparkles { + --fa: "\f5d0"; } -.fa-air-freshener::before { - content: "\f5d0"; } +.fa-air-freshener { + --fa: "\f5d0"; } -.fa-star::before { - content: "\f005"; } +.fa-star { + --fa: "\f005"; } -.fa-repeat::before { - content: "\f363"; } +.fa-repeat { + --fa: "\f363"; } -.fa-cross::before { - content: "\f654"; } +.fa-cross { + --fa: "\f654"; } -.fa-box::before { - content: "\f466"; } +.fa-box { + --fa: "\f466"; } -.fa-venus-mars::before { - content: "\f228"; } +.fa-venus-mars { + --fa: "\f228"; } -.fa-arrow-pointer::before { - content: "\f245"; } +.fa-arrow-pointer { + --fa: "\f245"; } -.fa-mouse-pointer::before { - content: "\f245"; } +.fa-mouse-pointer { + --fa: "\f245"; } -.fa-maximize::before { - content: "\f31e"; } +.fa-maximize { + --fa: "\f31e"; } -.fa-expand-arrows-alt::before { - content: "\f31e"; } +.fa-expand-arrows-alt { + --fa: "\f31e"; } -.fa-charging-station::before { - content: "\f5e7"; } +.fa-charging-station { + --fa: "\f5e7"; } -.fa-shapes::before { - content: "\f61f"; } +.fa-shapes { + --fa: "\f61f"; } -.fa-triangle-circle-square::before { - content: "\f61f"; } +.fa-triangle-circle-square { + --fa: "\f61f"; } -.fa-shuffle::before { - content: "\f074"; } +.fa-shuffle { + --fa: "\f074"; } -.fa-random::before { - content: "\f074"; } +.fa-random { + --fa: "\f074"; } -.fa-person-running::before { - content: "\f70c"; } +.fa-person-running { + --fa: "\f70c"; } -.fa-running::before { - content: "\f70c"; } +.fa-running { + --fa: "\f70c"; } -.fa-mobile-retro::before { - content: "\e527"; } +.fa-mobile-retro { + --fa: "\e527"; } -.fa-grip-lines-vertical::before { - content: "\f7a5"; } +.fa-grip-lines-vertical { + --fa: "\f7a5"; } -.fa-spider::before { - content: "\f717"; } +.fa-spider { + --fa: "\f717"; } -.fa-hands-bound::before { - content: "\e4f9"; } +.fa-hands-bound { + --fa: "\e4f9"; } -.fa-file-invoice-dollar::before { - content: "\f571"; } +.fa-file-invoice-dollar { + --fa: "\f571"; } -.fa-plane-circle-exclamation::before { - content: "\e556"; } +.fa-plane-circle-exclamation { + --fa: "\e556"; } -.fa-x-ray::before { - content: "\f497"; } +.fa-x-ray { + --fa: "\f497"; } -.fa-spell-check::before { - content: "\f891"; } +.fa-spell-check { + --fa: "\f891"; } -.fa-slash::before { - content: "\f715"; } +.fa-slash { + --fa: "\f715"; } -.fa-computer-mouse::before { - content: "\f8cc"; } +.fa-computer-mouse { + --fa: "\f8cc"; } -.fa-mouse::before { - content: "\f8cc"; } +.fa-mouse { + --fa: "\f8cc"; } -.fa-arrow-right-to-bracket::before { - content: "\f090"; } +.fa-arrow-right-to-bracket { + --fa: "\f090"; } -.fa-sign-in::before { - content: "\f090"; } +.fa-sign-in { + --fa: "\f090"; } -.fa-shop-slash::before { - content: "\e070"; } +.fa-shop-slash { + --fa: "\e070"; } -.fa-store-alt-slash::before { - content: "\e070"; } +.fa-store-alt-slash { + --fa: "\e070"; } -.fa-server::before { - content: "\f233"; } +.fa-server { + --fa: "\f233"; } -.fa-virus-covid-slash::before { - content: "\e4a9"; } +.fa-virus-covid-slash { + --fa: "\e4a9"; } -.fa-shop-lock::before { - content: "\e4a5"; } +.fa-shop-lock { + --fa: "\e4a5"; } -.fa-hourglass-start::before { - content: "\f251"; } +.fa-hourglass-start { + --fa: "\f251"; } -.fa-hourglass-1::before { - content: "\f251"; } +.fa-hourglass-1 { + --fa: "\f251"; } -.fa-blender-phone::before { - content: "\f6b6"; } +.fa-blender-phone { + --fa: "\f6b6"; } -.fa-building-wheat::before { - content: "\e4db"; } +.fa-building-wheat { + --fa: "\e4db"; } -.fa-person-breastfeeding::before { - content: "\e53a"; } +.fa-person-breastfeeding { + --fa: "\e53a"; } -.fa-right-to-bracket::before { - content: "\f2f6"; } +.fa-right-to-bracket { + --fa: "\f2f6"; } -.fa-sign-in-alt::before { - content: "\f2f6"; } +.fa-sign-in-alt { + --fa: "\f2f6"; } -.fa-venus::before { - content: "\f221"; } +.fa-venus { + --fa: "\f221"; } -.fa-passport::before { - content: "\f5ab"; } +.fa-passport { + --fa: "\f5ab"; } -.fa-heart-pulse::before { - content: "\f21e"; } +.fa-thumbtack-slash { + --fa: "\e68f"; } -.fa-heartbeat::before { - content: "\f21e"; } +.fa-thumb-tack-slash { + --fa: "\e68f"; } -.fa-people-carry-box::before { - content: "\f4ce"; } +.fa-heart-pulse { + --fa: "\f21e"; } -.fa-people-carry::before { - content: "\f4ce"; } +.fa-heartbeat { + --fa: "\f21e"; } -.fa-temperature-high::before { - content: "\f769"; } +.fa-people-carry-box { + --fa: "\f4ce"; } -.fa-microchip::before { - content: "\f2db"; } +.fa-people-carry { + --fa: "\f4ce"; } -.fa-crown::before { - content: "\f521"; } +.fa-temperature-high { + --fa: "\f769"; } -.fa-weight-hanging::before { - content: "\f5cd"; } +.fa-microchip { + --fa: "\f2db"; } -.fa-xmarks-lines::before { - content: "\e59a"; } +.fa-crown { + --fa: "\f521"; } -.fa-file-prescription::before { - content: "\f572"; } +.fa-weight-hanging { + --fa: "\f5cd"; } -.fa-weight-scale::before { - content: "\f496"; } +.fa-xmarks-lines { + --fa: "\e59a"; } -.fa-weight::before { - content: "\f496"; } +.fa-file-prescription { + --fa: "\f572"; } -.fa-user-group::before { - content: "\f500"; } +.fa-weight-scale { + --fa: "\f496"; } -.fa-user-friends::before { - content: "\f500"; } +.fa-weight { + --fa: "\f496"; } -.fa-arrow-up-a-z::before { - content: "\f15e"; } +.fa-user-group { + --fa: "\f500"; } -.fa-sort-alpha-up::before { - content: "\f15e"; } +.fa-user-friends { + --fa: "\f500"; } -.fa-chess-knight::before { - content: "\f441"; } +.fa-arrow-up-a-z { + --fa: "\f15e"; } -.fa-face-laugh-squint::before { - content: "\f59b"; } +.fa-sort-alpha-up { + --fa: "\f15e"; } -.fa-laugh-squint::before { - content: "\f59b"; } +.fa-chess-knight { + --fa: "\f441"; } -.fa-wheelchair::before { - content: "\f193"; } +.fa-face-laugh-squint { + --fa: "\f59b"; } -.fa-circle-arrow-up::before { - content: "\f0aa"; } +.fa-laugh-squint { + --fa: "\f59b"; } -.fa-arrow-circle-up::before { - content: "\f0aa"; } +.fa-wheelchair { + --fa: "\f193"; } -.fa-toggle-on::before { - content: "\f205"; } +.fa-circle-arrow-up { + --fa: "\f0aa"; } -.fa-person-walking::before { - content: "\f554"; } +.fa-arrow-circle-up { + --fa: "\f0aa"; } -.fa-walking::before { - content: "\f554"; } +.fa-toggle-on { + --fa: "\f205"; } -.fa-l::before { - content: "\4c"; } +.fa-person-walking { + --fa: "\f554"; } -.fa-fire::before { - content: "\f06d"; } +.fa-walking { + --fa: "\f554"; } -.fa-bed-pulse::before { - content: "\f487"; } +.fa-l { + --fa: "\4c"; } -.fa-procedures::before { - content: "\f487"; } +.fa-fire { + --fa: "\f06d"; } -.fa-shuttle-space::before { - content: "\f197"; } +.fa-bed-pulse { + --fa: "\f487"; } -.fa-space-shuttle::before { - content: "\f197"; } +.fa-procedures { + --fa: "\f487"; } -.fa-face-laugh::before { - content: "\f599"; } +.fa-shuttle-space { + --fa: "\f197"; } -.fa-laugh::before { - content: "\f599"; } +.fa-space-shuttle { + --fa: "\f197"; } -.fa-folder-open::before { - content: "\f07c"; } +.fa-face-laugh { + --fa: "\f599"; } -.fa-heart-circle-plus::before { - content: "\e500"; } +.fa-laugh { + --fa: "\f599"; } -.fa-code-fork::before { - content: "\e13b"; } +.fa-folder-open { + --fa: "\f07c"; } -.fa-city::before { - content: "\f64f"; } +.fa-heart-circle-plus { + --fa: "\e500"; } -.fa-microphone-lines::before { - content: "\f3c9"; } +.fa-code-fork { + --fa: "\e13b"; } -.fa-microphone-alt::before { - content: "\f3c9"; } +.fa-city { + --fa: "\f64f"; } -.fa-pepper-hot::before { - content: "\f816"; } +.fa-microphone-lines { + --fa: "\f3c9"; } -.fa-unlock::before { - content: "\f09c"; } +.fa-microphone-alt { + --fa: "\f3c9"; } -.fa-colon-sign::before { - content: "\e140"; } +.fa-pepper-hot { + --fa: "\f816"; } -.fa-headset::before { - content: "\f590"; } +.fa-unlock { + --fa: "\f09c"; } -.fa-store-slash::before { - content: "\e071"; } +.fa-colon-sign { + --fa: "\e140"; } -.fa-road-circle-xmark::before { - content: "\e566"; } +.fa-headset { + --fa: "\f590"; } -.fa-user-minus::before { - content: "\f503"; } +.fa-store-slash { + --fa: "\e071"; } -.fa-mars-stroke-up::before { - content: "\f22a"; } +.fa-road-circle-xmark { + --fa: "\e566"; } -.fa-mars-stroke-v::before { - content: "\f22a"; } +.fa-user-minus { + --fa: "\f503"; } -.fa-champagne-glasses::before { - content: "\f79f"; } +.fa-mars-stroke-up { + --fa: "\f22a"; } -.fa-glass-cheers::before { - content: "\f79f"; } +.fa-mars-stroke-v { + --fa: "\f22a"; } -.fa-clipboard::before { - content: "\f328"; } +.fa-champagne-glasses { + --fa: "\f79f"; } -.fa-house-circle-exclamation::before { - content: "\e50a"; } +.fa-glass-cheers { + --fa: "\f79f"; } -.fa-file-arrow-up::before { - content: "\f574"; } +.fa-clipboard { + --fa: "\f328"; } -.fa-file-upload::before { - content: "\f574"; } +.fa-house-circle-exclamation { + --fa: "\e50a"; } -.fa-wifi::before { - content: "\f1eb"; } +.fa-file-arrow-up { + --fa: "\f574"; } -.fa-wifi-3::before { - content: "\f1eb"; } +.fa-file-upload { + --fa: "\f574"; } -.fa-wifi-strong::before { - content: "\f1eb"; } +.fa-wifi { + --fa: "\f1eb"; } -.fa-bath::before { - content: "\f2cd"; } +.fa-wifi-3 { + --fa: "\f1eb"; } -.fa-bathtub::before { - content: "\f2cd"; } +.fa-wifi-strong { + --fa: "\f1eb"; } -.fa-underline::before { - content: "\f0cd"; } +.fa-bath { + --fa: "\f2cd"; } -.fa-user-pen::before { - content: "\f4ff"; } +.fa-bathtub { + --fa: "\f2cd"; } -.fa-user-edit::before { - content: "\f4ff"; } +.fa-underline { + --fa: "\f0cd"; } -.fa-signature::before { - content: "\f5b7"; } +.fa-user-pen { + --fa: "\f4ff"; } -.fa-stroopwafel::before { - content: "\f551"; } +.fa-user-edit { + --fa: "\f4ff"; } -.fa-bold::before { - content: "\f032"; } +.fa-signature { + --fa: "\f5b7"; } -.fa-anchor-lock::before { - content: "\e4ad"; } +.fa-stroopwafel { + --fa: "\f551"; } -.fa-building-ngo::before { - content: "\e4d7"; } +.fa-bold { + --fa: "\f032"; } -.fa-manat-sign::before { - content: "\e1d5"; } +.fa-anchor-lock { + --fa: "\e4ad"; } -.fa-not-equal::before { - content: "\f53e"; } +.fa-building-ngo { + --fa: "\e4d7"; } -.fa-border-top-left::before { - content: "\f853"; } +.fa-manat-sign { + --fa: "\e1d5"; } -.fa-border-style::before { - content: "\f853"; } +.fa-not-equal { + --fa: "\f53e"; } -.fa-map-location-dot::before { - content: "\f5a0"; } +.fa-border-top-left { + --fa: "\f853"; } -.fa-map-marked-alt::before { - content: "\f5a0"; } +.fa-border-style { + --fa: "\f853"; } -.fa-jedi::before { - content: "\f669"; } +.fa-map-location-dot { + --fa: "\f5a0"; } -.fa-square-poll-vertical::before { - content: "\f681"; } +.fa-map-marked-alt { + --fa: "\f5a0"; } -.fa-poll::before { - content: "\f681"; } +.fa-jedi { + --fa: "\f669"; } -.fa-mug-hot::before { - content: "\f7b6"; } +.fa-square-poll-vertical { + --fa: "\f681"; } -.fa-car-battery::before { - content: "\f5df"; } +.fa-poll { + --fa: "\f681"; } -.fa-battery-car::before { - content: "\f5df"; } +.fa-mug-hot { + --fa: "\f7b6"; } -.fa-gift::before { - content: "\f06b"; } +.fa-car-battery { + --fa: "\f5df"; } -.fa-dice-two::before { - content: "\f528"; } +.fa-battery-car { + --fa: "\f5df"; } -.fa-chess-queen::before { - content: "\f445"; } +.fa-gift { + --fa: "\f06b"; } -.fa-glasses::before { - content: "\f530"; } +.fa-dice-two { + --fa: "\f528"; } -.fa-chess-board::before { - content: "\f43c"; } +.fa-chess-queen { + --fa: "\f445"; } -.fa-building-circle-check::before { - content: "\e4d2"; } +.fa-glasses { + --fa: "\f530"; } -.fa-person-chalkboard::before { - content: "\e53d"; } +.fa-chess-board { + --fa: "\f43c"; } -.fa-mars-stroke-right::before { - content: "\f22b"; } +.fa-building-circle-check { + --fa: "\e4d2"; } -.fa-mars-stroke-h::before { - content: "\f22b"; } +.fa-person-chalkboard { + --fa: "\e53d"; } -.fa-hand-back-fist::before { - content: "\f255"; } +.fa-mars-stroke-right { + --fa: "\f22b"; } -.fa-hand-rock::before { - content: "\f255"; } +.fa-mars-stroke-h { + --fa: "\f22b"; } -.fa-square-caret-up::before { - content: "\f151"; } +.fa-hand-back-fist { + --fa: "\f255"; } -.fa-caret-square-up::before { - content: "\f151"; } +.fa-hand-rock { + --fa: "\f255"; } -.fa-cloud-showers-water::before { - content: "\e4e4"; } +.fa-square-caret-up { + --fa: "\f151"; } -.fa-chart-bar::before { - content: "\f080"; } +.fa-caret-square-up { + --fa: "\f151"; } -.fa-bar-chart::before { - content: "\f080"; } +.fa-cloud-showers-water { + --fa: "\e4e4"; } -.fa-hands-bubbles::before { - content: "\e05e"; } +.fa-chart-bar { + --fa: "\f080"; } -.fa-hands-wash::before { - content: "\e05e"; } +.fa-bar-chart { + --fa: "\f080"; } -.fa-less-than-equal::before { - content: "\f537"; } +.fa-hands-bubbles { + --fa: "\e05e"; } -.fa-train::before { - content: "\f238"; } +.fa-hands-wash { + --fa: "\e05e"; } -.fa-eye-low-vision::before { - content: "\f2a8"; } +.fa-less-than-equal { + --fa: "\f537"; } -.fa-low-vision::before { - content: "\f2a8"; } +.fa-train { + --fa: "\f238"; } -.fa-crow::before { - content: "\f520"; } +.fa-eye-low-vision { + --fa: "\f2a8"; } -.fa-sailboat::before { - content: "\e445"; } +.fa-low-vision { + --fa: "\f2a8"; } -.fa-window-restore::before { - content: "\f2d2"; } +.fa-crow { + --fa: "\f520"; } -.fa-square-plus::before { - content: "\f0fe"; } +.fa-sailboat { + --fa: "\e445"; } -.fa-plus-square::before { - content: "\f0fe"; } +.fa-window-restore { + --fa: "\f2d2"; } -.fa-torii-gate::before { - content: "\f6a1"; } +.fa-square-plus { + --fa: "\f0fe"; } -.fa-frog::before { - content: "\f52e"; } +.fa-plus-square { + --fa: "\f0fe"; } -.fa-bucket::before { - content: "\e4cf"; } +.fa-torii-gate { + --fa: "\f6a1"; } -.fa-image::before { - content: "\f03e"; } +.fa-frog { + --fa: "\f52e"; } -.fa-microphone::before { - content: "\f130"; } +.fa-bucket { + --fa: "\e4cf"; } -.fa-cow::before { - content: "\f6c8"; } +.fa-image { + --fa: "\f03e"; } -.fa-caret-up::before { - content: "\f0d8"; } +.fa-microphone { + --fa: "\f130"; } -.fa-screwdriver::before { - content: "\f54a"; } +.fa-cow { + --fa: "\f6c8"; } -.fa-folder-closed::before { - content: "\e185"; } +.fa-caret-up { + --fa: "\f0d8"; } -.fa-house-tsunami::before { - content: "\e515"; } +.fa-screwdriver { + --fa: "\f54a"; } -.fa-square-nfi::before { - content: "\e576"; } +.fa-folder-closed { + --fa: "\e185"; } -.fa-arrow-up-from-ground-water::before { - content: "\e4b5"; } +.fa-house-tsunami { + --fa: "\e515"; } -.fa-martini-glass::before { - content: "\f57b"; } +.fa-square-nfi { + --fa: "\e576"; } -.fa-glass-martini-alt::before { - content: "\f57b"; } +.fa-arrow-up-from-ground-water { + --fa: "\e4b5"; } -.fa-rotate-left::before { - content: "\f2ea"; } +.fa-martini-glass { + --fa: "\f57b"; } -.fa-rotate-back::before { - content: "\f2ea"; } +.fa-glass-martini-alt { + --fa: "\f57b"; } -.fa-rotate-backward::before { - content: "\f2ea"; } +.fa-square-binary { + --fa: "\e69b"; } -.fa-undo-alt::before { - content: "\f2ea"; } +.fa-rotate-left { + --fa: "\f2ea"; } -.fa-table-columns::before { - content: "\f0db"; } +.fa-rotate-back { + --fa: "\f2ea"; } -.fa-columns::before { - content: "\f0db"; } +.fa-rotate-backward { + --fa: "\f2ea"; } -.fa-lemon::before { - content: "\f094"; } +.fa-undo-alt { + --fa: "\f2ea"; } -.fa-head-side-mask::before { - content: "\e063"; } +.fa-table-columns { + --fa: "\f0db"; } -.fa-handshake::before { - content: "\f2b5"; } +.fa-columns { + --fa: "\f0db"; } -.fa-gem::before { - content: "\f3a5"; } +.fa-lemon { + --fa: "\f094"; } -.fa-dolly::before { - content: "\f472"; } +.fa-head-side-mask { + --fa: "\e063"; } -.fa-dolly-box::before { - content: "\f472"; } +.fa-handshake { + --fa: "\f2b5"; } -.fa-smoking::before { - content: "\f48d"; } +.fa-gem { + --fa: "\f3a5"; } -.fa-minimize::before { - content: "\f78c"; } +.fa-dolly { + --fa: "\f472"; } -.fa-compress-arrows-alt::before { - content: "\f78c"; } +.fa-dolly-box { + --fa: "\f472"; } -.fa-monument::before { - content: "\f5a6"; } +.fa-smoking { + --fa: "\f48d"; } -.fa-snowplow::before { - content: "\f7d2"; } +.fa-minimize { + --fa: "\f78c"; } -.fa-angles-right::before { - content: "\f101"; } +.fa-compress-arrows-alt { + --fa: "\f78c"; } -.fa-angle-double-right::before { - content: "\f101"; } +.fa-monument { + --fa: "\f5a6"; } -.fa-cannabis::before { - content: "\f55f"; } +.fa-snowplow { + --fa: "\f7d2"; } -.fa-circle-play::before { - content: "\f144"; } +.fa-angles-right { + --fa: "\f101"; } -.fa-play-circle::before { - content: "\f144"; } +.fa-angle-double-right { + --fa: "\f101"; } -.fa-tablets::before { - content: "\f490"; } +.fa-cannabis { + --fa: "\f55f"; } -.fa-ethernet::before { - content: "\f796"; } +.fa-circle-play { + --fa: "\f144"; } -.fa-euro-sign::before { - content: "\f153"; } +.fa-play-circle { + --fa: "\f144"; } -.fa-eur::before { - content: "\f153"; } +.fa-tablets { + --fa: "\f490"; } -.fa-euro::before { - content: "\f153"; } +.fa-ethernet { + --fa: "\f796"; } -.fa-chair::before { - content: "\f6c0"; } +.fa-euro-sign { + --fa: "\f153"; } -.fa-circle-check::before { - content: "\f058"; } +.fa-eur { + --fa: "\f153"; } -.fa-check-circle::before { - content: "\f058"; } +.fa-euro { + --fa: "\f153"; } -.fa-circle-stop::before { - content: "\f28d"; } +.fa-chair { + --fa: "\f6c0"; } -.fa-stop-circle::before { - content: "\f28d"; } +.fa-circle-check { + --fa: "\f058"; } -.fa-compass-drafting::before { - content: "\f568"; } +.fa-check-circle { + --fa: "\f058"; } -.fa-drafting-compass::before { - content: "\f568"; } +.fa-circle-stop { + --fa: "\f28d"; } -.fa-plate-wheat::before { - content: "\e55a"; } +.fa-stop-circle { + --fa: "\f28d"; } -.fa-icicles::before { - content: "\f7ad"; } +.fa-compass-drafting { + --fa: "\f568"; } -.fa-person-shelter::before { - content: "\e54f"; } +.fa-drafting-compass { + --fa: "\f568"; } -.fa-neuter::before { - content: "\f22c"; } +.fa-plate-wheat { + --fa: "\e55a"; } -.fa-id-badge::before { - content: "\f2c1"; } +.fa-icicles { + --fa: "\f7ad"; } -.fa-marker::before { - content: "\f5a1"; } +.fa-person-shelter { + --fa: "\e54f"; } -.fa-face-laugh-beam::before { - content: "\f59a"; } +.fa-neuter { + --fa: "\f22c"; } -.fa-laugh-beam::before { - content: "\f59a"; } +.fa-id-badge { + --fa: "\f2c1"; } -.fa-helicopter-symbol::before { - content: "\e502"; } +.fa-marker { + --fa: "\f5a1"; } -.fa-universal-access::before { - content: "\f29a"; } +.fa-face-laugh-beam { + --fa: "\f59a"; } -.fa-circle-chevron-up::before { - content: "\f139"; } +.fa-laugh-beam { + --fa: "\f59a"; } -.fa-chevron-circle-up::before { - content: "\f139"; } +.fa-helicopter-symbol { + --fa: "\e502"; } -.fa-lari-sign::before { - content: "\e1c8"; } +.fa-universal-access { + --fa: "\f29a"; } -.fa-volcano::before { - content: "\f770"; } +.fa-circle-chevron-up { + --fa: "\f139"; } -.fa-person-walking-dashed-line-arrow-right::before { - content: "\e553"; } +.fa-chevron-circle-up { + --fa: "\f139"; } -.fa-sterling-sign::before { - content: "\f154"; } +.fa-lari-sign { + --fa: "\e1c8"; } -.fa-gbp::before { - content: "\f154"; } +.fa-volcano { + --fa: "\f770"; } -.fa-pound-sign::before { - content: "\f154"; } +.fa-person-walking-dashed-line-arrow-right { + --fa: "\e553"; } -.fa-viruses::before { - content: "\e076"; } +.fa-sterling-sign { + --fa: "\f154"; } -.fa-square-person-confined::before { - content: "\e577"; } +.fa-gbp { + --fa: "\f154"; } -.fa-user-tie::before { - content: "\f508"; } +.fa-pound-sign { + --fa: "\f154"; } -.fa-arrow-down-long::before { - content: "\f175"; } +.fa-viruses { + --fa: "\e076"; } -.fa-long-arrow-down::before { - content: "\f175"; } +.fa-square-person-confined { + --fa: "\e577"; } -.fa-tent-arrow-down-to-line::before { - content: "\e57e"; } +.fa-user-tie { + --fa: "\f508"; } -.fa-certificate::before { - content: "\f0a3"; } +.fa-arrow-down-long { + --fa: "\f175"; } -.fa-reply-all::before { - content: "\f122"; } +.fa-long-arrow-down { + --fa: "\f175"; } -.fa-mail-reply-all::before { - content: "\f122"; } +.fa-tent-arrow-down-to-line { + --fa: "\e57e"; } -.fa-suitcase::before { - content: "\f0f2"; } +.fa-certificate { + --fa: "\f0a3"; } -.fa-person-skating::before { - content: "\f7c5"; } +.fa-reply-all { + --fa: "\f122"; } -.fa-skating::before { - content: "\f7c5"; } +.fa-mail-reply-all { + --fa: "\f122"; } -.fa-filter-circle-dollar::before { - content: "\f662"; } +.fa-suitcase { + --fa: "\f0f2"; } -.fa-funnel-dollar::before { - content: "\f662"; } +.fa-person-skating { + --fa: "\f7c5"; } -.fa-camera-retro::before { - content: "\f083"; } +.fa-skating { + --fa: "\f7c5"; } -.fa-circle-arrow-down::before { - content: "\f0ab"; } +.fa-filter-circle-dollar { + --fa: "\f662"; } -.fa-arrow-circle-down::before { - content: "\f0ab"; } +.fa-funnel-dollar { + --fa: "\f662"; } -.fa-file-import::before { - content: "\f56f"; } +.fa-camera-retro { + --fa: "\f083"; } -.fa-arrow-right-to-file::before { - content: "\f56f"; } +.fa-circle-arrow-down { + --fa: "\f0ab"; } -.fa-square-arrow-up-right::before { - content: "\f14c"; } +.fa-arrow-circle-down { + --fa: "\f0ab"; } -.fa-external-link-square::before { - content: "\f14c"; } +.fa-file-import { + --fa: "\f56f"; } -.fa-box-open::before { - content: "\f49e"; } +.fa-arrow-right-to-file { + --fa: "\f56f"; } -.fa-scroll::before { - content: "\f70e"; } +.fa-square-arrow-up-right { + --fa: "\f14c"; } -.fa-spa::before { - content: "\f5bb"; } +.fa-external-link-square { + --fa: "\f14c"; } -.fa-location-pin-lock::before { - content: "\e51f"; } +.fa-box-open { + --fa: "\f49e"; } -.fa-pause::before { - content: "\f04c"; } +.fa-scroll { + --fa: "\f70e"; } -.fa-hill-avalanche::before { - content: "\e507"; } +.fa-spa { + --fa: "\f5bb"; } -.fa-temperature-empty::before { - content: "\f2cb"; } +.fa-location-pin-lock { + --fa: "\e51f"; } -.fa-temperature-0::before { - content: "\f2cb"; } +.fa-pause { + --fa: "\f04c"; } -.fa-thermometer-0::before { - content: "\f2cb"; } +.fa-hill-avalanche { + --fa: "\e507"; } -.fa-thermometer-empty::before { - content: "\f2cb"; } +.fa-temperature-empty { + --fa: "\f2cb"; } -.fa-bomb::before { - content: "\f1e2"; } +.fa-temperature-0 { + --fa: "\f2cb"; } -.fa-registered::before { - content: "\f25d"; } +.fa-thermometer-0 { + --fa: "\f2cb"; } -.fa-address-card::before { - content: "\f2bb"; } +.fa-thermometer-empty { + --fa: "\f2cb"; } -.fa-contact-card::before { - content: "\f2bb"; } +.fa-bomb { + --fa: "\f1e2"; } -.fa-vcard::before { - content: "\f2bb"; } +.fa-registered { + --fa: "\f25d"; } -.fa-scale-unbalanced-flip::before { - content: "\f516"; } +.fa-address-card { + --fa: "\f2bb"; } -.fa-balance-scale-right::before { - content: "\f516"; } +.fa-contact-card { + --fa: "\f2bb"; } -.fa-subscript::before { - content: "\f12c"; } +.fa-vcard { + --fa: "\f2bb"; } -.fa-diamond-turn-right::before { - content: "\f5eb"; } +.fa-scale-unbalanced-flip { + --fa: "\f516"; } -.fa-directions::before { - content: "\f5eb"; } +.fa-balance-scale-right { + --fa: "\f516"; } -.fa-burst::before { - content: "\e4dc"; } +.fa-subscript { + --fa: "\f12c"; } -.fa-house-laptop::before { - content: "\e066"; } +.fa-diamond-turn-right { + --fa: "\f5eb"; } -.fa-laptop-house::before { - content: "\e066"; } +.fa-directions { + --fa: "\f5eb"; } -.fa-face-tired::before { - content: "\f5c8"; } +.fa-burst { + --fa: "\e4dc"; } -.fa-tired::before { - content: "\f5c8"; } +.fa-house-laptop { + --fa: "\e066"; } -.fa-money-bills::before { - content: "\e1f3"; } +.fa-laptop-house { + --fa: "\e066"; } -.fa-smog::before { - content: "\f75f"; } +.fa-face-tired { + --fa: "\f5c8"; } -.fa-crutch::before { - content: "\f7f7"; } +.fa-tired { + --fa: "\f5c8"; } -.fa-cloud-arrow-up::before { - content: "\f0ee"; } +.fa-money-bills { + --fa: "\e1f3"; } -.fa-cloud-upload::before { - content: "\f0ee"; } +.fa-smog { + --fa: "\f75f"; } -.fa-cloud-upload-alt::before { - content: "\f0ee"; } +.fa-crutch { + --fa: "\f7f7"; } -.fa-palette::before { - content: "\f53f"; } +.fa-cloud-arrow-up { + --fa: "\f0ee"; } -.fa-arrows-turn-right::before { - content: "\e4c0"; } +.fa-cloud-upload { + --fa: "\f0ee"; } -.fa-vest::before { - content: "\e085"; } +.fa-cloud-upload-alt { + --fa: "\f0ee"; } -.fa-ferry::before { - content: "\e4ea"; } +.fa-palette { + --fa: "\f53f"; } -.fa-arrows-down-to-people::before { - content: "\e4b9"; } +.fa-arrows-turn-right { + --fa: "\e4c0"; } -.fa-seedling::before { - content: "\f4d8"; } +.fa-vest { + --fa: "\e085"; } -.fa-sprout::before { - content: "\f4d8"; } +.fa-ferry { + --fa: "\e4ea"; } -.fa-left-right::before { - content: "\f337"; } +.fa-arrows-down-to-people { + --fa: "\e4b9"; } -.fa-arrows-alt-h::before { - content: "\f337"; } +.fa-seedling { + --fa: "\f4d8"; } -.fa-boxes-packing::before { - content: "\e4c7"; } +.fa-sprout { + --fa: "\f4d8"; } -.fa-circle-arrow-left::before { - content: "\f0a8"; } +.fa-left-right { + --fa: "\f337"; } -.fa-arrow-circle-left::before { - content: "\f0a8"; } +.fa-arrows-alt-h { + --fa: "\f337"; } -.fa-group-arrows-rotate::before { - content: "\e4f6"; } +.fa-boxes-packing { + --fa: "\e4c7"; } -.fa-bowl-food::before { - content: "\e4c6"; } +.fa-circle-arrow-left { + --fa: "\f0a8"; } -.fa-candy-cane::before { - content: "\f786"; } +.fa-arrow-circle-left { + --fa: "\f0a8"; } -.fa-arrow-down-wide-short::before { - content: "\f160"; } +.fa-group-arrows-rotate { + --fa: "\e4f6"; } -.fa-sort-amount-asc::before { - content: "\f160"; } +.fa-bowl-food { + --fa: "\e4c6"; } -.fa-sort-amount-down::before { - content: "\f160"; } +.fa-candy-cane { + --fa: "\f786"; } -.fa-cloud-bolt::before { - content: "\f76c"; } +.fa-arrow-down-wide-short { + --fa: "\f160"; } -.fa-thunderstorm::before { - content: "\f76c"; } +.fa-sort-amount-asc { + --fa: "\f160"; } -.fa-text-slash::before { - content: "\f87d"; } +.fa-sort-amount-down { + --fa: "\f160"; } -.fa-remove-format::before { - content: "\f87d"; } +.fa-cloud-bolt { + --fa: "\f76c"; } -.fa-face-smile-wink::before { - content: "\f4da"; } +.fa-thunderstorm { + --fa: "\f76c"; } -.fa-smile-wink::before { - content: "\f4da"; } +.fa-text-slash { + --fa: "\f87d"; } -.fa-file-word::before { - content: "\f1c2"; } +.fa-remove-format { + --fa: "\f87d"; } -.fa-file-powerpoint::before { - content: "\f1c4"; } +.fa-face-smile-wink { + --fa: "\f4da"; } -.fa-arrows-left-right::before { - content: "\f07e"; } +.fa-smile-wink { + --fa: "\f4da"; } -.fa-arrows-h::before { - content: "\f07e"; } +.fa-file-word { + --fa: "\f1c2"; } -.fa-house-lock::before { - content: "\e510"; } +.fa-file-powerpoint { + --fa: "\f1c4"; } -.fa-cloud-arrow-down::before { - content: "\f0ed"; } +.fa-arrows-left-right { + --fa: "\f07e"; } -.fa-cloud-download::before { - content: "\f0ed"; } +.fa-arrows-h { + --fa: "\f07e"; } -.fa-cloud-download-alt::before { - content: "\f0ed"; } +.fa-house-lock { + --fa: "\e510"; } -.fa-children::before { - content: "\e4e1"; } +.fa-cloud-arrow-down { + --fa: "\f0ed"; } -.fa-chalkboard::before { - content: "\f51b"; } +.fa-cloud-download { + --fa: "\f0ed"; } -.fa-blackboard::before { - content: "\f51b"; } +.fa-cloud-download-alt { + --fa: "\f0ed"; } -.fa-user-large-slash::before { - content: "\f4fa"; } +.fa-children { + --fa: "\e4e1"; } -.fa-user-alt-slash::before { - content: "\f4fa"; } +.fa-chalkboard { + --fa: "\f51b"; } -.fa-envelope-open::before { - content: "\f2b6"; } +.fa-blackboard { + --fa: "\f51b"; } -.fa-handshake-simple-slash::before { - content: "\e05f"; } +.fa-user-large-slash { + --fa: "\f4fa"; } -.fa-handshake-alt-slash::before { - content: "\e05f"; } +.fa-user-alt-slash { + --fa: "\f4fa"; } -.fa-mattress-pillow::before { - content: "\e525"; } +.fa-envelope-open { + --fa: "\f2b6"; } -.fa-guarani-sign::before { - content: "\e19a"; } +.fa-handshake-simple-slash { + --fa: "\e05f"; } -.fa-arrows-rotate::before { - content: "\f021"; } +.fa-handshake-alt-slash { + --fa: "\e05f"; } -.fa-refresh::before { - content: "\f021"; } +.fa-mattress-pillow { + --fa: "\e525"; } -.fa-sync::before { - content: "\f021"; } +.fa-guarani-sign { + --fa: "\e19a"; } -.fa-fire-extinguisher::before { - content: "\f134"; } +.fa-arrows-rotate { + --fa: "\f021"; } -.fa-cruzeiro-sign::before { - content: "\e152"; } +.fa-refresh { + --fa: "\f021"; } -.fa-greater-than-equal::before { - content: "\f532"; } +.fa-sync { + --fa: "\f021"; } -.fa-shield-halved::before { - content: "\f3ed"; } +.fa-fire-extinguisher { + --fa: "\f134"; } -.fa-shield-alt::before { - content: "\f3ed"; } +.fa-cruzeiro-sign { + --fa: "\e152"; } -.fa-book-atlas::before { - content: "\f558"; } +.fa-greater-than-equal { + --fa: "\f532"; } -.fa-atlas::before { - content: "\f558"; } +.fa-shield-halved { + --fa: "\f3ed"; } -.fa-virus::before { - content: "\e074"; } +.fa-shield-alt { + --fa: "\f3ed"; } -.fa-envelope-circle-check::before { - content: "\e4e8"; } +.fa-book-atlas { + --fa: "\f558"; } -.fa-layer-group::before { - content: "\f5fd"; } +.fa-atlas { + --fa: "\f558"; } -.fa-arrows-to-dot::before { - content: "\e4be"; } +.fa-virus { + --fa: "\e074"; } -.fa-archway::before { - content: "\f557"; } +.fa-envelope-circle-check { + --fa: "\e4e8"; } -.fa-heart-circle-check::before { - content: "\e4fd"; } +.fa-layer-group { + --fa: "\f5fd"; } -.fa-house-chimney-crack::before { - content: "\f6f1"; } +.fa-arrows-to-dot { + --fa: "\e4be"; } -.fa-house-damage::before { - content: "\f6f1"; } +.fa-archway { + --fa: "\f557"; } -.fa-file-zipper::before { - content: "\f1c6"; } +.fa-heart-circle-check { + --fa: "\e4fd"; } -.fa-file-archive::before { - content: "\f1c6"; } +.fa-house-chimney-crack { + --fa: "\f6f1"; } -.fa-square::before { - content: "\f0c8"; } +.fa-house-damage { + --fa: "\f6f1"; } -.fa-martini-glass-empty::before { - content: "\f000"; } +.fa-file-zipper { + --fa: "\f1c6"; } -.fa-glass-martini::before { - content: "\f000"; } +.fa-file-archive { + --fa: "\f1c6"; } -.fa-couch::before { - content: "\f4b8"; } +.fa-square { + --fa: "\f0c8"; } -.fa-cedi-sign::before { - content: "\e0df"; } +.fa-martini-glass-empty { + --fa: "\f000"; } -.fa-italic::before { - content: "\f033"; } +.fa-glass-martini { + --fa: "\f000"; } -.fa-table-cells-column-lock::before { - content: "\e678"; } +.fa-couch { + --fa: "\f4b8"; } -.fa-church::before { - content: "\f51d"; } +.fa-cedi-sign { + --fa: "\e0df"; } -.fa-comments-dollar::before { - content: "\f653"; } +.fa-italic { + --fa: "\f033"; } -.fa-democrat::before { - content: "\f747"; } +.fa-table-cells-column-lock { + --fa: "\e678"; } -.fa-z::before { - content: "\5a"; } +.fa-church { + --fa: "\f51d"; } -.fa-person-skiing::before { - content: "\f7c9"; } +.fa-comments-dollar { + --fa: "\f653"; } -.fa-skiing::before { - content: "\f7c9"; } +.fa-democrat { + --fa: "\f747"; } -.fa-road-lock::before { - content: "\e567"; } +.fa-z { + --fa: "\5a"; } -.fa-a::before { - content: "\41"; } +.fa-person-skiing { + --fa: "\f7c9"; } -.fa-temperature-arrow-down::before { - content: "\e03f"; } +.fa-skiing { + --fa: "\f7c9"; } -.fa-temperature-down::before { - content: "\e03f"; } +.fa-road-lock { + --fa: "\e567"; } -.fa-feather-pointed::before { - content: "\f56b"; } +.fa-a { + --fa: "\41"; } -.fa-feather-alt::before { - content: "\f56b"; } +.fa-temperature-arrow-down { + --fa: "\e03f"; } -.fa-p::before { - content: "\50"; } +.fa-temperature-down { + --fa: "\e03f"; } -.fa-snowflake::before { - content: "\f2dc"; } +.fa-feather-pointed { + --fa: "\f56b"; } -.fa-newspaper::before { - content: "\f1ea"; } +.fa-feather-alt { + --fa: "\f56b"; } -.fa-rectangle-ad::before { - content: "\f641"; } +.fa-p { + --fa: "\50"; } -.fa-ad::before { - content: "\f641"; } +.fa-snowflake { + --fa: "\f2dc"; } -.fa-circle-arrow-right::before { - content: "\f0a9"; } +.fa-newspaper { + --fa: "\f1ea"; } -.fa-arrow-circle-right::before { - content: "\f0a9"; } +.fa-rectangle-ad { + --fa: "\f641"; } -.fa-filter-circle-xmark::before { - content: "\e17b"; } +.fa-ad { + --fa: "\f641"; } -.fa-locust::before { - content: "\e520"; } +.fa-circle-arrow-right { + --fa: "\f0a9"; } -.fa-sort::before { - content: "\f0dc"; } +.fa-arrow-circle-right { + --fa: "\f0a9"; } -.fa-unsorted::before { - content: "\f0dc"; } +.fa-filter-circle-xmark { + --fa: "\e17b"; } -.fa-list-ol::before { - content: "\f0cb"; } +.fa-locust { + --fa: "\e520"; } -.fa-list-1-2::before { - content: "\f0cb"; } +.fa-sort { + --fa: "\f0dc"; } -.fa-list-numeric::before { - content: "\f0cb"; } +.fa-unsorted { + --fa: "\f0dc"; } -.fa-person-dress-burst::before { - content: "\e544"; } +.fa-list-ol { + --fa: "\f0cb"; } -.fa-money-check-dollar::before { - content: "\f53d"; } +.fa-list-1-2 { + --fa: "\f0cb"; } -.fa-money-check-alt::before { - content: "\f53d"; } +.fa-list-numeric { + --fa: "\f0cb"; } -.fa-vector-square::before { - content: "\f5cb"; } +.fa-person-dress-burst { + --fa: "\e544"; } -.fa-bread-slice::before { - content: "\f7ec"; } +.fa-money-check-dollar { + --fa: "\f53d"; } -.fa-language::before { - content: "\f1ab"; } +.fa-money-check-alt { + --fa: "\f53d"; } -.fa-face-kiss-wink-heart::before { - content: "\f598"; } +.fa-vector-square { + --fa: "\f5cb"; } -.fa-kiss-wink-heart::before { - content: "\f598"; } +.fa-bread-slice { + --fa: "\f7ec"; } -.fa-filter::before { - content: "\f0b0"; } +.fa-language { + --fa: "\f1ab"; } -.fa-question::before { - content: "\3f"; } +.fa-face-kiss-wink-heart { + --fa: "\f598"; } -.fa-file-signature::before { - content: "\f573"; } +.fa-kiss-wink-heart { + --fa: "\f598"; } -.fa-up-down-left-right::before { - content: "\f0b2"; } +.fa-filter { + --fa: "\f0b0"; } -.fa-arrows-alt::before { - content: "\f0b2"; } +.fa-question { + --fa: "\3f"; } -.fa-house-chimney-user::before { - content: "\e065"; } +.fa-file-signature { + --fa: "\f573"; } -.fa-hand-holding-heart::before { - content: "\f4be"; } +.fa-up-down-left-right { + --fa: "\f0b2"; } -.fa-puzzle-piece::before { - content: "\f12e"; } +.fa-arrows-alt { + --fa: "\f0b2"; } -.fa-money-check::before { - content: "\f53c"; } +.fa-house-chimney-user { + --fa: "\e065"; } -.fa-star-half-stroke::before { - content: "\f5c0"; } +.fa-hand-holding-heart { + --fa: "\f4be"; } -.fa-star-half-alt::before { - content: "\f5c0"; } +.fa-puzzle-piece { + --fa: "\f12e"; } -.fa-code::before { - content: "\f121"; } +.fa-money-check { + --fa: "\f53c"; } -.fa-whiskey-glass::before { - content: "\f7a0"; } +.fa-star-half-stroke { + --fa: "\f5c0"; } -.fa-glass-whiskey::before { - content: "\f7a0"; } +.fa-star-half-alt { + --fa: "\f5c0"; } -.fa-building-circle-exclamation::before { - content: "\e4d3"; } +.fa-code { + --fa: "\f121"; } -.fa-magnifying-glass-chart::before { - content: "\e522"; } +.fa-whiskey-glass { + --fa: "\f7a0"; } -.fa-arrow-up-right-from-square::before { - content: "\f08e"; } +.fa-glass-whiskey { + --fa: "\f7a0"; } -.fa-external-link::before { - content: "\f08e"; } +.fa-building-circle-exclamation { + --fa: "\e4d3"; } -.fa-cubes-stacked::before { - content: "\e4e6"; } +.fa-magnifying-glass-chart { + --fa: "\e522"; } -.fa-won-sign::before { - content: "\f159"; } +.fa-arrow-up-right-from-square { + --fa: "\f08e"; } -.fa-krw::before { - content: "\f159"; } +.fa-external-link { + --fa: "\f08e"; } -.fa-won::before { - content: "\f159"; } +.fa-cubes-stacked { + --fa: "\e4e6"; } -.fa-virus-covid::before { - content: "\e4a8"; } +.fa-won-sign { + --fa: "\f159"; } -.fa-austral-sign::before { - content: "\e0a9"; } +.fa-krw { + --fa: "\f159"; } -.fa-f::before { - content: "\46"; } +.fa-won { + --fa: "\f159"; } -.fa-leaf::before { - content: "\f06c"; } +.fa-virus-covid { + --fa: "\e4a8"; } -.fa-road::before { - content: "\f018"; } +.fa-austral-sign { + --fa: "\e0a9"; } -.fa-taxi::before { - content: "\f1ba"; } +.fa-f { + --fa: "\46"; } -.fa-cab::before { - content: "\f1ba"; } +.fa-leaf { + --fa: "\f06c"; } -.fa-person-circle-plus::before { - content: "\e541"; } +.fa-road { + --fa: "\f018"; } -.fa-chart-pie::before { - content: "\f200"; } +.fa-taxi { + --fa: "\f1ba"; } -.fa-pie-chart::before { - content: "\f200"; } +.fa-cab { + --fa: "\f1ba"; } -.fa-bolt-lightning::before { - content: "\e0b7"; } +.fa-person-circle-plus { + --fa: "\e541"; } -.fa-sack-xmark::before { - content: "\e56a"; } +.fa-chart-pie { + --fa: "\f200"; } -.fa-file-excel::before { - content: "\f1c3"; } +.fa-pie-chart { + --fa: "\f200"; } -.fa-file-contract::before { - content: "\f56c"; } +.fa-bolt-lightning { + --fa: "\e0b7"; } -.fa-fish-fins::before { - content: "\e4f2"; } +.fa-sack-xmark { + --fa: "\e56a"; } -.fa-building-flag::before { - content: "\e4d5"; } +.fa-file-excel { + --fa: "\f1c3"; } -.fa-face-grin-beam::before { - content: "\f582"; } +.fa-file-contract { + --fa: "\f56c"; } -.fa-grin-beam::before { - content: "\f582"; } +.fa-fish-fins { + --fa: "\e4f2"; } -.fa-object-ungroup::before { - content: "\f248"; } +.fa-building-flag { + --fa: "\e4d5"; } -.fa-poop::before { - content: "\f619"; } +.fa-face-grin-beam { + --fa: "\f582"; } -.fa-location-pin::before { - content: "\f041"; } +.fa-grin-beam { + --fa: "\f582"; } -.fa-map-marker::before { - content: "\f041"; } +.fa-object-ungroup { + --fa: "\f248"; } -.fa-kaaba::before { - content: "\f66b"; } +.fa-poop { + --fa: "\f619"; } -.fa-toilet-paper::before { - content: "\f71e"; } +.fa-location-pin { + --fa: "\f041"; } -.fa-helmet-safety::before { - content: "\f807"; } +.fa-map-marker { + --fa: "\f041"; } -.fa-hard-hat::before { - content: "\f807"; } +.fa-kaaba { + --fa: "\f66b"; } -.fa-hat-hard::before { - content: "\f807"; } +.fa-toilet-paper { + --fa: "\f71e"; } -.fa-eject::before { - content: "\f052"; } +.fa-helmet-safety { + --fa: "\f807"; } -.fa-circle-right::before { - content: "\f35a"; } +.fa-hard-hat { + --fa: "\f807"; } -.fa-arrow-alt-circle-right::before { - content: "\f35a"; } +.fa-hat-hard { + --fa: "\f807"; } -.fa-plane-circle-check::before { - content: "\e555"; } +.fa-eject { + --fa: "\f052"; } -.fa-face-rolling-eyes::before { - content: "\f5a5"; } +.fa-circle-right { + --fa: "\f35a"; } -.fa-meh-rolling-eyes::before { - content: "\f5a5"; } +.fa-arrow-alt-circle-right { + --fa: "\f35a"; } -.fa-object-group::before { - content: "\f247"; } +.fa-plane-circle-check { + --fa: "\e555"; } -.fa-chart-line::before { - content: "\f201"; } +.fa-face-rolling-eyes { + --fa: "\f5a5"; } -.fa-line-chart::before { - content: "\f201"; } +.fa-meh-rolling-eyes { + --fa: "\f5a5"; } -.fa-mask-ventilator::before { - content: "\e524"; } +.fa-object-group { + --fa: "\f247"; } -.fa-arrow-right::before { - content: "\f061"; } +.fa-chart-line { + --fa: "\f201"; } -.fa-signs-post::before { - content: "\f277"; } +.fa-line-chart { + --fa: "\f201"; } -.fa-map-signs::before { - content: "\f277"; } +.fa-mask-ventilator { + --fa: "\e524"; } -.fa-cash-register::before { - content: "\f788"; } +.fa-arrow-right { + --fa: "\f061"; } -.fa-person-circle-question::before { - content: "\e542"; } +.fa-signs-post { + --fa: "\f277"; } -.fa-h::before { - content: "\48"; } +.fa-map-signs { + --fa: "\f277"; } -.fa-tarp::before { - content: "\e57b"; } +.fa-cash-register { + --fa: "\f788"; } -.fa-screwdriver-wrench::before { - content: "\f7d9"; } +.fa-person-circle-question { + --fa: "\e542"; } -.fa-tools::before { - content: "\f7d9"; } +.fa-h { + --fa: "\48"; } -.fa-arrows-to-eye::before { - content: "\e4bf"; } +.fa-tarp { + --fa: "\e57b"; } -.fa-plug-circle-bolt::before { - content: "\e55b"; } +.fa-screwdriver-wrench { + --fa: "\f7d9"; } -.fa-heart::before { - content: "\f004"; } +.fa-tools { + --fa: "\f7d9"; } -.fa-mars-and-venus::before { - content: "\f224"; } +.fa-arrows-to-eye { + --fa: "\e4bf"; } -.fa-house-user::before { - content: "\e1b0"; } +.fa-plug-circle-bolt { + --fa: "\e55b"; } -.fa-home-user::before { - content: "\e1b0"; } +.fa-heart { + --fa: "\f004"; } -.fa-dumpster-fire::before { - content: "\f794"; } +.fa-mars-and-venus { + --fa: "\f224"; } -.fa-house-crack::before { - content: "\e3b1"; } +.fa-house-user { + --fa: "\e1b0"; } -.fa-martini-glass-citrus::before { - content: "\f561"; } +.fa-home-user { + --fa: "\e1b0"; } -.fa-cocktail::before { - content: "\f561"; } +.fa-dumpster-fire { + --fa: "\f794"; } -.fa-face-surprise::before { - content: "\f5c2"; } +.fa-house-crack { + --fa: "\e3b1"; } -.fa-surprise::before { - content: "\f5c2"; } +.fa-martini-glass-citrus { + --fa: "\f561"; } -.fa-bottle-water::before { - content: "\e4c5"; } +.fa-cocktail { + --fa: "\f561"; } -.fa-circle-pause::before { - content: "\f28b"; } +.fa-face-surprise { + --fa: "\f5c2"; } -.fa-pause-circle::before { - content: "\f28b"; } +.fa-surprise { + --fa: "\f5c2"; } -.fa-toilet-paper-slash::before { - content: "\e072"; } +.fa-bottle-water { + --fa: "\e4c5"; } -.fa-apple-whole::before { - content: "\f5d1"; } +.fa-circle-pause { + --fa: "\f28b"; } -.fa-apple-alt::before { - content: "\f5d1"; } +.fa-pause-circle { + --fa: "\f28b"; } -.fa-kitchen-set::before { - content: "\e51a"; } +.fa-toilet-paper-slash { + --fa: "\e072"; } -.fa-r::before { - content: "\52"; } +.fa-apple-whole { + --fa: "\f5d1"; } -.fa-temperature-quarter::before { - content: "\f2ca"; } +.fa-apple-alt { + --fa: "\f5d1"; } -.fa-temperature-1::before { - content: "\f2ca"; } +.fa-kitchen-set { + --fa: "\e51a"; } -.fa-thermometer-1::before { - content: "\f2ca"; } +.fa-r { + --fa: "\52"; } -.fa-thermometer-quarter::before { - content: "\f2ca"; } +.fa-temperature-quarter { + --fa: "\f2ca"; } -.fa-cube::before { - content: "\f1b2"; } +.fa-temperature-1 { + --fa: "\f2ca"; } -.fa-bitcoin-sign::before { - content: "\e0b4"; } +.fa-thermometer-1 { + --fa: "\f2ca"; } -.fa-shield-dog::before { - content: "\e573"; } +.fa-thermometer-quarter { + --fa: "\f2ca"; } -.fa-solar-panel::before { - content: "\f5ba"; } +.fa-cube { + --fa: "\f1b2"; } -.fa-lock-open::before { - content: "\f3c1"; } +.fa-bitcoin-sign { + --fa: "\e0b4"; } -.fa-elevator::before { - content: "\e16d"; } +.fa-shield-dog { + --fa: "\e573"; } -.fa-money-bill-transfer::before { - content: "\e528"; } +.fa-solar-panel { + --fa: "\f5ba"; } -.fa-money-bill-trend-up::before { - content: "\e529"; } +.fa-lock-open { + --fa: "\f3c1"; } -.fa-house-flood-water-circle-arrow-right::before { - content: "\e50f"; } +.fa-elevator { + --fa: "\e16d"; } -.fa-square-poll-horizontal::before { - content: "\f682"; } +.fa-money-bill-transfer { + --fa: "\e528"; } -.fa-poll-h::before { - content: "\f682"; } +.fa-money-bill-trend-up { + --fa: "\e529"; } -.fa-circle::before { - content: "\f111"; } +.fa-house-flood-water-circle-arrow-right { + --fa: "\e50f"; } -.fa-backward-fast::before { - content: "\f049"; } +.fa-square-poll-horizontal { + --fa: "\f682"; } -.fa-fast-backward::before { - content: "\f049"; } +.fa-poll-h { + --fa: "\f682"; } -.fa-recycle::before { - content: "\f1b8"; } +.fa-circle { + --fa: "\f111"; } -.fa-user-astronaut::before { - content: "\f4fb"; } +.fa-backward-fast { + --fa: "\f049"; } -.fa-plane-slash::before { - content: "\e069"; } +.fa-fast-backward { + --fa: "\f049"; } -.fa-trademark::before { - content: "\f25c"; } +.fa-recycle { + --fa: "\f1b8"; } -.fa-basketball::before { - content: "\f434"; } +.fa-user-astronaut { + --fa: "\f4fb"; } -.fa-basketball-ball::before { - content: "\f434"; } +.fa-plane-slash { + --fa: "\e069"; } -.fa-satellite-dish::before { - content: "\f7c0"; } +.fa-trademark { + --fa: "\f25c"; } -.fa-circle-up::before { - content: "\f35b"; } +.fa-basketball { + --fa: "\f434"; } -.fa-arrow-alt-circle-up::before { - content: "\f35b"; } +.fa-basketball-ball { + --fa: "\f434"; } -.fa-mobile-screen-button::before { - content: "\f3cd"; } +.fa-satellite-dish { + --fa: "\f7c0"; } -.fa-mobile-alt::before { - content: "\f3cd"; } +.fa-circle-up { + --fa: "\f35b"; } -.fa-volume-high::before { - content: "\f028"; } +.fa-arrow-alt-circle-up { + --fa: "\f35b"; } -.fa-volume-up::before { - content: "\f028"; } +.fa-mobile-screen-button { + --fa: "\f3cd"; } -.fa-users-rays::before { - content: "\e593"; } +.fa-mobile-alt { + --fa: "\f3cd"; } -.fa-wallet::before { - content: "\f555"; } +.fa-volume-high { + --fa: "\f028"; } -.fa-clipboard-check::before { - content: "\f46c"; } +.fa-volume-up { + --fa: "\f028"; } -.fa-file-audio::before { - content: "\f1c7"; } +.fa-users-rays { + --fa: "\e593"; } -.fa-burger::before { - content: "\f805"; } +.fa-wallet { + --fa: "\f555"; } -.fa-hamburger::before { - content: "\f805"; } +.fa-clipboard-check { + --fa: "\f46c"; } -.fa-wrench::before { - content: "\f0ad"; } +.fa-file-audio { + --fa: "\f1c7"; } -.fa-bugs::before { - content: "\e4d0"; } +.fa-burger { + --fa: "\f805"; } -.fa-rupee-sign::before { - content: "\f156"; } +.fa-hamburger { + --fa: "\f805"; } -.fa-rupee::before { - content: "\f156"; } +.fa-wrench { + --fa: "\f0ad"; } -.fa-file-image::before { - content: "\f1c5"; } +.fa-bugs { + --fa: "\e4d0"; } -.fa-circle-question::before { - content: "\f059"; } +.fa-rupee-sign { + --fa: "\f156"; } -.fa-question-circle::before { - content: "\f059"; } +.fa-rupee { + --fa: "\f156"; } -.fa-plane-departure::before { - content: "\f5b0"; } +.fa-file-image { + --fa: "\f1c5"; } -.fa-handshake-slash::before { - content: "\e060"; } +.fa-circle-question { + --fa: "\f059"; } -.fa-book-bookmark::before { - content: "\e0bb"; } +.fa-question-circle { + --fa: "\f059"; } -.fa-code-branch::before { - content: "\f126"; } +.fa-plane-departure { + --fa: "\f5b0"; } -.fa-hat-cowboy::before { - content: "\f8c0"; } +.fa-handshake-slash { + --fa: "\e060"; } -.fa-bridge::before { - content: "\e4c8"; } +.fa-book-bookmark { + --fa: "\e0bb"; } -.fa-phone-flip::before { - content: "\f879"; } +.fa-code-branch { + --fa: "\f126"; } -.fa-phone-alt::before { - content: "\f879"; } +.fa-hat-cowboy { + --fa: "\f8c0"; } -.fa-truck-front::before { - content: "\e2b7"; } +.fa-bridge { + --fa: "\e4c8"; } -.fa-cat::before { - content: "\f6be"; } +.fa-phone-flip { + --fa: "\f879"; } -.fa-anchor-circle-exclamation::before { - content: "\e4ab"; } +.fa-phone-alt { + --fa: "\f879"; } -.fa-truck-field::before { - content: "\e58d"; } +.fa-truck-front { + --fa: "\e2b7"; } -.fa-route::before { - content: "\f4d7"; } +.fa-cat { + --fa: "\f6be"; } -.fa-clipboard-question::before { - content: "\e4e3"; } +.fa-anchor-circle-exclamation { + --fa: "\e4ab"; } -.fa-panorama::before { - content: "\e209"; } +.fa-truck-field { + --fa: "\e58d"; } -.fa-comment-medical::before { - content: "\f7f5"; } +.fa-route { + --fa: "\f4d7"; } -.fa-teeth-open::before { - content: "\f62f"; } +.fa-clipboard-question { + --fa: "\e4e3"; } -.fa-file-circle-minus::before { - content: "\e4ed"; } +.fa-panorama { + --fa: "\e209"; } -.fa-tags::before { - content: "\f02c"; } +.fa-comment-medical { + --fa: "\f7f5"; } -.fa-wine-glass::before { - content: "\f4e3"; } +.fa-teeth-open { + --fa: "\f62f"; } -.fa-forward-fast::before { - content: "\f050"; } +.fa-file-circle-minus { + --fa: "\e4ed"; } -.fa-fast-forward::before { - content: "\f050"; } +.fa-tags { + --fa: "\f02c"; } -.fa-face-meh-blank::before { - content: "\f5a4"; } +.fa-wine-glass { + --fa: "\f4e3"; } -.fa-meh-blank::before { - content: "\f5a4"; } +.fa-forward-fast { + --fa: "\f050"; } -.fa-square-parking::before { - content: "\f540"; } +.fa-fast-forward { + --fa: "\f050"; } -.fa-parking::before { - content: "\f540"; } +.fa-face-meh-blank { + --fa: "\f5a4"; } -.fa-house-signal::before { - content: "\e012"; } +.fa-meh-blank { + --fa: "\f5a4"; } -.fa-bars-progress::before { - content: "\f828"; } +.fa-square-parking { + --fa: "\f540"; } -.fa-tasks-alt::before { - content: "\f828"; } +.fa-parking { + --fa: "\f540"; } -.fa-faucet-drip::before { - content: "\e006"; } +.fa-house-signal { + --fa: "\e012"; } -.fa-cart-flatbed::before { - content: "\f474"; } +.fa-bars-progress { + --fa: "\f828"; } -.fa-dolly-flatbed::before { - content: "\f474"; } +.fa-tasks-alt { + --fa: "\f828"; } -.fa-ban-smoking::before { - content: "\f54d"; } +.fa-faucet-drip { + --fa: "\e006"; } -.fa-smoking-ban::before { - content: "\f54d"; } +.fa-cart-flatbed { + --fa: "\f474"; } -.fa-terminal::before { - content: "\f120"; } +.fa-dolly-flatbed { + --fa: "\f474"; } -.fa-mobile-button::before { - content: "\f10b"; } +.fa-ban-smoking { + --fa: "\f54d"; } -.fa-house-medical-flag::before { - content: "\e514"; } +.fa-smoking-ban { + --fa: "\f54d"; } -.fa-basket-shopping::before { - content: "\f291"; } +.fa-terminal { + --fa: "\f120"; } -.fa-shopping-basket::before { - content: "\f291"; } +.fa-mobile-button { + --fa: "\f10b"; } -.fa-tape::before { - content: "\f4db"; } +.fa-house-medical-flag { + --fa: "\e514"; } -.fa-bus-simple::before { - content: "\f55e"; } +.fa-basket-shopping { + --fa: "\f291"; } -.fa-bus-alt::before { - content: "\f55e"; } +.fa-shopping-basket { + --fa: "\f291"; } -.fa-eye::before { - content: "\f06e"; } +.fa-tape { + --fa: "\f4db"; } -.fa-face-sad-cry::before { - content: "\f5b3"; } +.fa-bus-simple { + --fa: "\f55e"; } -.fa-sad-cry::before { - content: "\f5b3"; } +.fa-bus-alt { + --fa: "\f55e"; } -.fa-audio-description::before { - content: "\f29e"; } +.fa-eye { + --fa: "\f06e"; } -.fa-person-military-to-person::before { - content: "\e54c"; } +.fa-face-sad-cry { + --fa: "\f5b3"; } -.fa-file-shield::before { - content: "\e4f0"; } +.fa-sad-cry { + --fa: "\f5b3"; } -.fa-user-slash::before { - content: "\f506"; } +.fa-audio-description { + --fa: "\f29e"; } -.fa-pen::before { - content: "\f304"; } +.fa-person-military-to-person { + --fa: "\e54c"; } -.fa-tower-observation::before { - content: "\e586"; } +.fa-file-shield { + --fa: "\e4f0"; } -.fa-file-code::before { - content: "\f1c9"; } +.fa-user-slash { + --fa: "\f506"; } -.fa-signal::before { - content: "\f012"; } +.fa-pen { + --fa: "\f304"; } -.fa-signal-5::before { - content: "\f012"; } +.fa-tower-observation { + --fa: "\e586"; } -.fa-signal-perfect::before { - content: "\f012"; } +.fa-file-code { + --fa: "\f1c9"; } -.fa-bus::before { - content: "\f207"; } +.fa-signal { + --fa: "\f012"; } -.fa-heart-circle-xmark::before { - content: "\e501"; } +.fa-signal-5 { + --fa: "\f012"; } -.fa-house-chimney::before { - content: "\e3af"; } +.fa-signal-perfect { + --fa: "\f012"; } -.fa-home-lg::before { - content: "\e3af"; } +.fa-bus { + --fa: "\f207"; } -.fa-window-maximize::before { - content: "\f2d0"; } +.fa-heart-circle-xmark { + --fa: "\e501"; } -.fa-face-frown::before { - content: "\f119"; } +.fa-house-chimney { + --fa: "\e3af"; } -.fa-frown::before { - content: "\f119"; } +.fa-home-lg { + --fa: "\e3af"; } -.fa-prescription::before { - content: "\f5b1"; } +.fa-window-maximize { + --fa: "\f2d0"; } -.fa-shop::before { - content: "\f54f"; } +.fa-face-frown { + --fa: "\f119"; } -.fa-store-alt::before { - content: "\f54f"; } +.fa-frown { + --fa: "\f119"; } -.fa-floppy-disk::before { - content: "\f0c7"; } +.fa-prescription { + --fa: "\f5b1"; } -.fa-save::before { - content: "\f0c7"; } +.fa-shop { + --fa: "\f54f"; } -.fa-vihara::before { - content: "\f6a7"; } +.fa-store-alt { + --fa: "\f54f"; } -.fa-scale-unbalanced::before { - content: "\f515"; } +.fa-floppy-disk { + --fa: "\f0c7"; } -.fa-balance-scale-left::before { - content: "\f515"; } +.fa-save { + --fa: "\f0c7"; } -.fa-sort-up::before { - content: "\f0de"; } +.fa-vihara { + --fa: "\f6a7"; } -.fa-sort-asc::before { - content: "\f0de"; } +.fa-scale-unbalanced { + --fa: "\f515"; } -.fa-comment-dots::before { - content: "\f4ad"; } +.fa-balance-scale-left { + --fa: "\f515"; } -.fa-commenting::before { - content: "\f4ad"; } +.fa-sort-up { + --fa: "\f0de"; } -.fa-plant-wilt::before { - content: "\e5aa"; } +.fa-sort-asc { + --fa: "\f0de"; } -.fa-diamond::before { - content: "\f219"; } +.fa-comment-dots { + --fa: "\f4ad"; } -.fa-face-grin-squint::before { - content: "\f585"; } +.fa-commenting { + --fa: "\f4ad"; } -.fa-grin-squint::before { - content: "\f585"; } +.fa-plant-wilt { + --fa: "\e5aa"; } -.fa-hand-holding-dollar::before { - content: "\f4c0"; } +.fa-diamond { + --fa: "\f219"; } -.fa-hand-holding-usd::before { - content: "\f4c0"; } +.fa-face-grin-squint { + --fa: "\f585"; } -.fa-bacterium::before { - content: "\e05a"; } +.fa-grin-squint { + --fa: "\f585"; } -.fa-hand-pointer::before { - content: "\f25a"; } +.fa-hand-holding-dollar { + --fa: "\f4c0"; } -.fa-drum-steelpan::before { - content: "\f56a"; } +.fa-hand-holding-usd { + --fa: "\f4c0"; } -.fa-hand-scissors::before { - content: "\f257"; } +.fa-chart-diagram { + --fa: "\e695"; } -.fa-hands-praying::before { - content: "\f684"; } +.fa-bacterium { + --fa: "\e05a"; } -.fa-praying-hands::before { - content: "\f684"; } +.fa-hand-pointer { + --fa: "\f25a"; } -.fa-arrow-rotate-right::before { - content: "\f01e"; } +.fa-drum-steelpan { + --fa: "\f56a"; } -.fa-arrow-right-rotate::before { - content: "\f01e"; } +.fa-hand-scissors { + --fa: "\f257"; } -.fa-arrow-rotate-forward::before { - content: "\f01e"; } +.fa-hands-praying { + --fa: "\f684"; } -.fa-redo::before { - content: "\f01e"; } +.fa-praying-hands { + --fa: "\f684"; } -.fa-biohazard::before { - content: "\f780"; } +.fa-arrow-rotate-right { + --fa: "\f01e"; } -.fa-location-crosshairs::before { - content: "\f601"; } +.fa-arrow-right-rotate { + --fa: "\f01e"; } -.fa-location::before { - content: "\f601"; } +.fa-arrow-rotate-forward { + --fa: "\f01e"; } -.fa-mars-double::before { - content: "\f227"; } +.fa-redo { + --fa: "\f01e"; } -.fa-child-dress::before { - content: "\e59c"; } +.fa-biohazard { + --fa: "\f780"; } -.fa-users-between-lines::before { - content: "\e591"; } +.fa-location-crosshairs { + --fa: "\f601"; } -.fa-lungs-virus::before { - content: "\e067"; } +.fa-location { + --fa: "\f601"; } -.fa-face-grin-tears::before { - content: "\f588"; } +.fa-mars-double { + --fa: "\f227"; } -.fa-grin-tears::before { - content: "\f588"; } +.fa-child-dress { + --fa: "\e59c"; } -.fa-phone::before { - content: "\f095"; } +.fa-users-between-lines { + --fa: "\e591"; } -.fa-calendar-xmark::before { - content: "\f273"; } +.fa-lungs-virus { + --fa: "\e067"; } -.fa-calendar-times::before { - content: "\f273"; } +.fa-face-grin-tears { + --fa: "\f588"; } -.fa-child-reaching::before { - content: "\e59d"; } +.fa-grin-tears { + --fa: "\f588"; } -.fa-head-side-virus::before { - content: "\e064"; } +.fa-phone { + --fa: "\f095"; } -.fa-user-gear::before { - content: "\f4fe"; } +.fa-calendar-xmark { + --fa: "\f273"; } -.fa-user-cog::before { - content: "\f4fe"; } +.fa-calendar-times { + --fa: "\f273"; } -.fa-arrow-up-1-9::before { - content: "\f163"; } +.fa-child-reaching { + --fa: "\e59d"; } -.fa-sort-numeric-up::before { - content: "\f163"; } +.fa-head-side-virus { + --fa: "\e064"; } -.fa-door-closed::before { - content: "\f52a"; } +.fa-user-gear { + --fa: "\f4fe"; } -.fa-shield-virus::before { - content: "\e06c"; } +.fa-user-cog { + --fa: "\f4fe"; } -.fa-dice-six::before { - content: "\f526"; } +.fa-arrow-up-1-9 { + --fa: "\f163"; } -.fa-mosquito-net::before { - content: "\e52c"; } +.fa-sort-numeric-up { + --fa: "\f163"; } -.fa-bridge-water::before { - content: "\e4ce"; } +.fa-door-closed { + --fa: "\f52a"; } -.fa-person-booth::before { - content: "\f756"; } +.fa-shield-virus { + --fa: "\e06c"; } -.fa-text-width::before { - content: "\f035"; } +.fa-dice-six { + --fa: "\f526"; } -.fa-hat-wizard::before { - content: "\f6e8"; } +.fa-mosquito-net { + --fa: "\e52c"; } -.fa-pen-fancy::before { - content: "\f5ac"; } +.fa-file-fragment { + --fa: "\e697"; } -.fa-person-digging::before { - content: "\f85e"; } +.fa-bridge-water { + --fa: "\e4ce"; } -.fa-digging::before { - content: "\f85e"; } +.fa-person-booth { + --fa: "\f756"; } -.fa-trash::before { - content: "\f1f8"; } +.fa-text-width { + --fa: "\f035"; } -.fa-gauge-simple::before { - content: "\f629"; } +.fa-hat-wizard { + --fa: "\f6e8"; } -.fa-gauge-simple-med::before { - content: "\f629"; } +.fa-pen-fancy { + --fa: "\f5ac"; } -.fa-tachometer-average::before { - content: "\f629"; } +.fa-person-digging { + --fa: "\f85e"; } -.fa-book-medical::before { - content: "\f7e6"; } +.fa-digging { + --fa: "\f85e"; } -.fa-poo::before { - content: "\f2fe"; } +.fa-trash { + --fa: "\f1f8"; } -.fa-quote-right::before { - content: "\f10e"; } +.fa-gauge-simple { + --fa: "\f629"; } -.fa-quote-right-alt::before { - content: "\f10e"; } +.fa-gauge-simple-med { + --fa: "\f629"; } -.fa-shirt::before { - content: "\f553"; } +.fa-tachometer-average { + --fa: "\f629"; } -.fa-t-shirt::before { - content: "\f553"; } +.fa-book-medical { + --fa: "\f7e6"; } -.fa-tshirt::before { - content: "\f553"; } +.fa-poo { + --fa: "\f2fe"; } -.fa-cubes::before { - content: "\f1b3"; } +.fa-quote-right { + --fa: "\f10e"; } -.fa-divide::before { - content: "\f529"; } +.fa-quote-right-alt { + --fa: "\f10e"; } -.fa-tenge-sign::before { - content: "\f7d7"; } +.fa-shirt { + --fa: "\f553"; } -.fa-tenge::before { - content: "\f7d7"; } +.fa-t-shirt { + --fa: "\f553"; } -.fa-headphones::before { - content: "\f025"; } +.fa-tshirt { + --fa: "\f553"; } -.fa-hands-holding::before { - content: "\f4c2"; } +.fa-cubes { + --fa: "\f1b3"; } -.fa-hands-clapping::before { - content: "\e1a8"; } +.fa-divide { + --fa: "\f529"; } -.fa-republican::before { - content: "\f75e"; } +.fa-tenge-sign { + --fa: "\f7d7"; } -.fa-arrow-left::before { - content: "\f060"; } +.fa-tenge { + --fa: "\f7d7"; } -.fa-person-circle-xmark::before { - content: "\e543"; } +.fa-headphones { + --fa: "\f025"; } -.fa-ruler::before { - content: "\f545"; } +.fa-hands-holding { + --fa: "\f4c2"; } -.fa-align-left::before { - content: "\f036"; } +.fa-hands-clapping { + --fa: "\e1a8"; } -.fa-dice-d6::before { - content: "\f6d1"; } +.fa-republican { + --fa: "\f75e"; } -.fa-restroom::before { - content: "\f7bd"; } +.fa-arrow-left { + --fa: "\f060"; } -.fa-j::before { - content: "\4a"; } +.fa-person-circle-xmark { + --fa: "\e543"; } -.fa-users-viewfinder::before { - content: "\e595"; } +.fa-ruler { + --fa: "\f545"; } -.fa-file-video::before { - content: "\f1c8"; } +.fa-align-left { + --fa: "\f036"; } -.fa-up-right-from-square::before { - content: "\f35d"; } +.fa-dice-d6 { + --fa: "\f6d1"; } -.fa-external-link-alt::before { - content: "\f35d"; } +.fa-restroom { + --fa: "\f7bd"; } -.fa-table-cells::before { - content: "\f00a"; } +.fa-j { + --fa: "\4a"; } -.fa-th::before { - content: "\f00a"; } +.fa-users-viewfinder { + --fa: "\e595"; } -.fa-file-pdf::before { - content: "\f1c1"; } +.fa-file-video { + --fa: "\f1c8"; } -.fa-book-bible::before { - content: "\f647"; } +.fa-up-right-from-square { + --fa: "\f35d"; } -.fa-bible::before { - content: "\f647"; } +.fa-external-link-alt { + --fa: "\f35d"; } -.fa-o::before { - content: "\4f"; } +.fa-table-cells { + --fa: "\f00a"; } -.fa-suitcase-medical::before { - content: "\f0fa"; } +.fa-th { + --fa: "\f00a"; } -.fa-medkit::before { - content: "\f0fa"; } +.fa-file-pdf { + --fa: "\f1c1"; } -.fa-user-secret::before { - content: "\f21b"; } +.fa-book-bible { + --fa: "\f647"; } -.fa-otter::before { - content: "\f700"; } +.fa-bible { + --fa: "\f647"; } -.fa-person-dress::before { - content: "\f182"; } +.fa-o { + --fa: "\4f"; } -.fa-female::before { - content: "\f182"; } +.fa-suitcase-medical { + --fa: "\f0fa"; } -.fa-comment-dollar::before { - content: "\f651"; } +.fa-medkit { + --fa: "\f0fa"; } -.fa-business-time::before { - content: "\f64a"; } +.fa-user-secret { + --fa: "\f21b"; } -.fa-briefcase-clock::before { - content: "\f64a"; } +.fa-otter { + --fa: "\f700"; } -.fa-table-cells-large::before { - content: "\f009"; } +.fa-person-dress { + --fa: "\f182"; } -.fa-th-large::before { - content: "\f009"; } +.fa-female { + --fa: "\f182"; } -.fa-book-tanakh::before { - content: "\f827"; } +.fa-comment-dollar { + --fa: "\f651"; } -.fa-tanakh::before { - content: "\f827"; } +.fa-business-time { + --fa: "\f64a"; } -.fa-phone-volume::before { - content: "\f2a0"; } +.fa-briefcase-clock { + --fa: "\f64a"; } -.fa-volume-control-phone::before { - content: "\f2a0"; } +.fa-table-cells-large { + --fa: "\f009"; } -.fa-hat-cowboy-side::before { - content: "\f8c1"; } +.fa-th-large { + --fa: "\f009"; } -.fa-clipboard-user::before { - content: "\f7f3"; } +.fa-book-tanakh { + --fa: "\f827"; } -.fa-child::before { - content: "\f1ae"; } +.fa-tanakh { + --fa: "\f827"; } -.fa-lira-sign::before { - content: "\f195"; } +.fa-phone-volume { + --fa: "\f2a0"; } -.fa-satellite::before { - content: "\f7bf"; } +.fa-volume-control-phone { + --fa: "\f2a0"; } -.fa-plane-lock::before { - content: "\e558"; } +.fa-hat-cowboy-side { + --fa: "\f8c1"; } -.fa-tag::before { - content: "\f02b"; } +.fa-clipboard-user { + --fa: "\f7f3"; } -.fa-comment::before { - content: "\f075"; } +.fa-child { + --fa: "\f1ae"; } -.fa-cake-candles::before { - content: "\f1fd"; } +.fa-lira-sign { + --fa: "\f195"; } -.fa-birthday-cake::before { - content: "\f1fd"; } +.fa-satellite { + --fa: "\f7bf"; } -.fa-cake::before { - content: "\f1fd"; } +.fa-plane-lock { + --fa: "\e558"; } -.fa-envelope::before { - content: "\f0e0"; } +.fa-tag { + --fa: "\f02b"; } -.fa-angles-up::before { - content: "\f102"; } +.fa-comment { + --fa: "\f075"; } -.fa-angle-double-up::before { - content: "\f102"; } +.fa-cake-candles { + --fa: "\f1fd"; } -.fa-paperclip::before { - content: "\f0c6"; } +.fa-birthday-cake { + --fa: "\f1fd"; } -.fa-arrow-right-to-city::before { - content: "\e4b3"; } +.fa-cake { + --fa: "\f1fd"; } -.fa-ribbon::before { - content: "\f4d6"; } +.fa-envelope { + --fa: "\f0e0"; } -.fa-lungs::before { - content: "\f604"; } +.fa-angles-up { + --fa: "\f102"; } -.fa-arrow-up-9-1::before { - content: "\f887"; } +.fa-angle-double-up { + --fa: "\f102"; } -.fa-sort-numeric-up-alt::before { - content: "\f887"; } +.fa-paperclip { + --fa: "\f0c6"; } -.fa-litecoin-sign::before { - content: "\e1d3"; } +.fa-arrow-right-to-city { + --fa: "\e4b3"; } -.fa-border-none::before { - content: "\f850"; } +.fa-ribbon { + --fa: "\f4d6"; } -.fa-circle-nodes::before { - content: "\e4e2"; } +.fa-lungs { + --fa: "\f604"; } -.fa-parachute-box::before { - content: "\f4cd"; } +.fa-arrow-up-9-1 { + --fa: "\f887"; } -.fa-indent::before { - content: "\f03c"; } +.fa-sort-numeric-up-alt { + --fa: "\f887"; } -.fa-truck-field-un::before { - content: "\e58e"; } +.fa-litecoin-sign { + --fa: "\e1d3"; } -.fa-hourglass::before { - content: "\f254"; } +.fa-border-none { + --fa: "\f850"; } -.fa-hourglass-empty::before { - content: "\f254"; } +.fa-circle-nodes { + --fa: "\e4e2"; } -.fa-mountain::before { - content: "\f6fc"; } +.fa-parachute-box { + --fa: "\f4cd"; } -.fa-user-doctor::before { - content: "\f0f0"; } +.fa-indent { + --fa: "\f03c"; } -.fa-user-md::before { - content: "\f0f0"; } +.fa-truck-field-un { + --fa: "\e58e"; } -.fa-circle-info::before { - content: "\f05a"; } +.fa-hourglass { + --fa: "\f254"; } -.fa-info-circle::before { - content: "\f05a"; } +.fa-hourglass-empty { + --fa: "\f254"; } -.fa-cloud-meatball::before { - content: "\f73b"; } +.fa-mountain { + --fa: "\f6fc"; } -.fa-camera::before { - content: "\f030"; } +.fa-user-doctor { + --fa: "\f0f0"; } -.fa-camera-alt::before { - content: "\f030"; } +.fa-user-md { + --fa: "\f0f0"; } -.fa-square-virus::before { - content: "\e578"; } +.fa-circle-info { + --fa: "\f05a"; } -.fa-meteor::before { - content: "\f753"; } +.fa-info-circle { + --fa: "\f05a"; } -.fa-car-on::before { - content: "\e4dd"; } +.fa-cloud-meatball { + --fa: "\f73b"; } -.fa-sleigh::before { - content: "\f7cc"; } +.fa-camera { + --fa: "\f030"; } -.fa-arrow-down-1-9::before { - content: "\f162"; } +.fa-camera-alt { + --fa: "\f030"; } -.fa-sort-numeric-asc::before { - content: "\f162"; } +.fa-square-virus { + --fa: "\e578"; } -.fa-sort-numeric-down::before { - content: "\f162"; } +.fa-meteor { + --fa: "\f753"; } -.fa-hand-holding-droplet::before { - content: "\f4c1"; } +.fa-car-on { + --fa: "\e4dd"; } -.fa-hand-holding-water::before { - content: "\f4c1"; } +.fa-sleigh { + --fa: "\f7cc"; } -.fa-water::before { - content: "\f773"; } +.fa-arrow-down-1-9 { + --fa: "\f162"; } -.fa-calendar-check::before { - content: "\f274"; } +.fa-sort-numeric-asc { + --fa: "\f162"; } -.fa-braille::before { - content: "\f2a1"; } +.fa-sort-numeric-down { + --fa: "\f162"; } -.fa-prescription-bottle-medical::before { - content: "\f486"; } +.fa-hand-holding-droplet { + --fa: "\f4c1"; } -.fa-prescription-bottle-alt::before { - content: "\f486"; } +.fa-hand-holding-water { + --fa: "\f4c1"; } -.fa-landmark::before { - content: "\f66f"; } +.fa-water { + --fa: "\f773"; } -.fa-truck::before { - content: "\f0d1"; } +.fa-calendar-check { + --fa: "\f274"; } -.fa-crosshairs::before { - content: "\f05b"; } +.fa-braille { + --fa: "\f2a1"; } -.fa-person-cane::before { - content: "\e53c"; } +.fa-prescription-bottle-medical { + --fa: "\f486"; } -.fa-tent::before { - content: "\e57d"; } +.fa-prescription-bottle-alt { + --fa: "\f486"; } -.fa-vest-patches::before { - content: "\e086"; } +.fa-landmark { + --fa: "\f66f"; } -.fa-check-double::before { - content: "\f560"; } +.fa-truck { + --fa: "\f0d1"; } -.fa-arrow-down-a-z::before { - content: "\f15d"; } +.fa-crosshairs { + --fa: "\f05b"; } -.fa-sort-alpha-asc::before { - content: "\f15d"; } +.fa-person-cane { + --fa: "\e53c"; } -.fa-sort-alpha-down::before { - content: "\f15d"; } +.fa-tent { + --fa: "\e57d"; } -.fa-money-bill-wheat::before { - content: "\e52a"; } +.fa-vest-patches { + --fa: "\e086"; } -.fa-cookie::before { - content: "\f563"; } +.fa-check-double { + --fa: "\f560"; } -.fa-arrow-rotate-left::before { - content: "\f0e2"; } +.fa-arrow-down-a-z { + --fa: "\f15d"; } -.fa-arrow-left-rotate::before { - content: "\f0e2"; } +.fa-sort-alpha-asc { + --fa: "\f15d"; } -.fa-arrow-rotate-back::before { - content: "\f0e2"; } +.fa-sort-alpha-down { + --fa: "\f15d"; } -.fa-arrow-rotate-backward::before { - content: "\f0e2"; } +.fa-money-bill-wheat { + --fa: "\e52a"; } -.fa-undo::before { - content: "\f0e2"; } +.fa-cookie { + --fa: "\f563"; } -.fa-hard-drive::before { - content: "\f0a0"; } +.fa-arrow-rotate-left { + --fa: "\f0e2"; } -.fa-hdd::before { - content: "\f0a0"; } +.fa-arrow-left-rotate { + --fa: "\f0e2"; } -.fa-face-grin-squint-tears::before { - content: "\f586"; } +.fa-arrow-rotate-back { + --fa: "\f0e2"; } -.fa-grin-squint-tears::before { - content: "\f586"; } +.fa-arrow-rotate-backward { + --fa: "\f0e2"; } -.fa-dumbbell::before { - content: "\f44b"; } +.fa-undo { + --fa: "\f0e2"; } -.fa-rectangle-list::before { - content: "\f022"; } +.fa-hard-drive { + --fa: "\f0a0"; } -.fa-list-alt::before { - content: "\f022"; } +.fa-hdd { + --fa: "\f0a0"; } -.fa-tarp-droplet::before { - content: "\e57c"; } +.fa-face-grin-squint-tears { + --fa: "\f586"; } -.fa-house-medical-circle-check::before { - content: "\e511"; } +.fa-grin-squint-tears { + --fa: "\f586"; } -.fa-person-skiing-nordic::before { - content: "\f7ca"; } +.fa-dumbbell { + --fa: "\f44b"; } -.fa-skiing-nordic::before { - content: "\f7ca"; } +.fa-rectangle-list { + --fa: "\f022"; } -.fa-calendar-plus::before { - content: "\f271"; } +.fa-list-alt { + --fa: "\f022"; } -.fa-plane-arrival::before { - content: "\f5af"; } +.fa-tarp-droplet { + --fa: "\e57c"; } -.fa-circle-left::before { - content: "\f359"; } +.fa-house-medical-circle-check { + --fa: "\e511"; } -.fa-arrow-alt-circle-left::before { - content: "\f359"; } +.fa-person-skiing-nordic { + --fa: "\f7ca"; } -.fa-train-subway::before { - content: "\f239"; } +.fa-skiing-nordic { + --fa: "\f7ca"; } -.fa-subway::before { - content: "\f239"; } +.fa-calendar-plus { + --fa: "\f271"; } -.fa-chart-gantt::before { - content: "\e0e4"; } +.fa-plane-arrival { + --fa: "\f5af"; } -.fa-indian-rupee-sign::before { - content: "\e1bc"; } +.fa-circle-left { + --fa: "\f359"; } -.fa-indian-rupee::before { - content: "\e1bc"; } +.fa-arrow-alt-circle-left { + --fa: "\f359"; } -.fa-inr::before { - content: "\e1bc"; } +.fa-train-subway { + --fa: "\f239"; } -.fa-crop-simple::before { - content: "\f565"; } +.fa-subway { + --fa: "\f239"; } -.fa-crop-alt::before { - content: "\f565"; } +.fa-chart-gantt { + --fa: "\e0e4"; } -.fa-money-bill-1::before { - content: "\f3d1"; } +.fa-indian-rupee-sign { + --fa: "\e1bc"; } -.fa-money-bill-alt::before { - content: "\f3d1"; } +.fa-indian-rupee { + --fa: "\e1bc"; } -.fa-left-long::before { - content: "\f30a"; } +.fa-inr { + --fa: "\e1bc"; } -.fa-long-arrow-alt-left::before { - content: "\f30a"; } +.fa-crop-simple { + --fa: "\f565"; } -.fa-dna::before { - content: "\f471"; } +.fa-crop-alt { + --fa: "\f565"; } -.fa-virus-slash::before { - content: "\e075"; } +.fa-money-bill-1 { + --fa: "\f3d1"; } -.fa-minus::before { - content: "\f068"; } +.fa-money-bill-alt { + --fa: "\f3d1"; } -.fa-subtract::before { - content: "\f068"; } +.fa-left-long { + --fa: "\f30a"; } -.fa-chess::before { - content: "\f439"; } +.fa-long-arrow-alt-left { + --fa: "\f30a"; } -.fa-arrow-left-long::before { - content: "\f177"; } +.fa-dna { + --fa: "\f471"; } -.fa-long-arrow-left::before { - content: "\f177"; } +.fa-virus-slash { + --fa: "\e075"; } -.fa-plug-circle-check::before { - content: "\e55c"; } +.fa-minus { + --fa: "\f068"; } -.fa-street-view::before { - content: "\f21d"; } +.fa-subtract { + --fa: "\f068"; } -.fa-franc-sign::before { - content: "\e18f"; } +.fa-chess { + --fa: "\f439"; } -.fa-volume-off::before { - content: "\f026"; } +.fa-arrow-left-long { + --fa: "\f177"; } -.fa-hands-asl-interpreting::before { - content: "\f2a3"; } +.fa-long-arrow-left { + --fa: "\f177"; } -.fa-american-sign-language-interpreting::before { - content: "\f2a3"; } +.fa-plug-circle-check { + --fa: "\e55c"; } -.fa-asl-interpreting::before { - content: "\f2a3"; } +.fa-street-view { + --fa: "\f21d"; } -.fa-hands-american-sign-language-interpreting::before { - content: "\f2a3"; } +.fa-franc-sign { + --fa: "\e18f"; } -.fa-gear::before { - content: "\f013"; } +.fa-volume-off { + --fa: "\f026"; } -.fa-cog::before { - content: "\f013"; } +.fa-hands-asl-interpreting { + --fa: "\f2a3"; } -.fa-droplet-slash::before { - content: "\f5c7"; } +.fa-american-sign-language-interpreting { + --fa: "\f2a3"; } -.fa-tint-slash::before { - content: "\f5c7"; } +.fa-asl-interpreting { + --fa: "\f2a3"; } -.fa-mosque::before { - content: "\f678"; } +.fa-hands-american-sign-language-interpreting { + --fa: "\f2a3"; } -.fa-mosquito::before { - content: "\e52b"; } +.fa-gear { + --fa: "\f013"; } -.fa-star-of-david::before { - content: "\f69a"; } +.fa-cog { + --fa: "\f013"; } -.fa-person-military-rifle::before { - content: "\e54b"; } +.fa-droplet-slash { + --fa: "\f5c7"; } -.fa-cart-shopping::before { - content: "\f07a"; } +.fa-tint-slash { + --fa: "\f5c7"; } -.fa-shopping-cart::before { - content: "\f07a"; } +.fa-mosque { + --fa: "\f678"; } -.fa-vials::before { - content: "\f493"; } +.fa-mosquito { + --fa: "\e52b"; } -.fa-plug-circle-plus::before { - content: "\e55f"; } +.fa-star-of-david { + --fa: "\f69a"; } -.fa-place-of-worship::before { - content: "\f67f"; } +.fa-person-military-rifle { + --fa: "\e54b"; } -.fa-grip-vertical::before { - content: "\f58e"; } +.fa-cart-shopping { + --fa: "\f07a"; } -.fa-arrow-turn-up::before { - content: "\f148"; } +.fa-shopping-cart { + --fa: "\f07a"; } -.fa-level-up::before { - content: "\f148"; } +.fa-vials { + --fa: "\f493"; } -.fa-u::before { - content: "\55"; } +.fa-plug-circle-plus { + --fa: "\e55f"; } -.fa-square-root-variable::before { - content: "\f698"; } +.fa-place-of-worship { + --fa: "\f67f"; } -.fa-square-root-alt::before { - content: "\f698"; } +.fa-grip-vertical { + --fa: "\f58e"; } -.fa-clock::before { - content: "\f017"; } +.fa-hexagon-nodes { + --fa: "\e699"; } -.fa-clock-four::before { - content: "\f017"; } +.fa-arrow-turn-up { + --fa: "\f148"; } -.fa-backward-step::before { - content: "\f048"; } +.fa-level-up { + --fa: "\f148"; } -.fa-step-backward::before { - content: "\f048"; } +.fa-u { + --fa: "\55"; } -.fa-pallet::before { - content: "\f482"; } +.fa-square-root-variable { + --fa: "\f698"; } -.fa-faucet::before { - content: "\e005"; } +.fa-square-root-alt { + --fa: "\f698"; } -.fa-baseball-bat-ball::before { - content: "\f432"; } +.fa-clock { + --fa: "\f017"; } -.fa-s::before { - content: "\53"; } +.fa-clock-four { + --fa: "\f017"; } -.fa-timeline::before { - content: "\e29c"; } +.fa-backward-step { + --fa: "\f048"; } -.fa-keyboard::before { - content: "\f11c"; } +.fa-step-backward { + --fa: "\f048"; } -.fa-caret-down::before { - content: "\f0d7"; } +.fa-pallet { + --fa: "\f482"; } -.fa-house-chimney-medical::before { - content: "\f7f2"; } +.fa-faucet { + --fa: "\e005"; } -.fa-clinic-medical::before { - content: "\f7f2"; } +.fa-baseball-bat-ball { + --fa: "\f432"; } -.fa-temperature-three-quarters::before { - content: "\f2c8"; } +.fa-s { + --fa: "\53"; } -.fa-temperature-3::before { - content: "\f2c8"; } +.fa-timeline { + --fa: "\e29c"; } -.fa-thermometer-3::before { - content: "\f2c8"; } +.fa-keyboard { + --fa: "\f11c"; } -.fa-thermometer-three-quarters::before { - content: "\f2c8"; } +.fa-caret-down { + --fa: "\f0d7"; } -.fa-mobile-screen::before { - content: "\f3cf"; } +.fa-house-chimney-medical { + --fa: "\f7f2"; } -.fa-mobile-android-alt::before { - content: "\f3cf"; } +.fa-clinic-medical { + --fa: "\f7f2"; } -.fa-plane-up::before { - content: "\e22d"; } +.fa-temperature-three-quarters { + --fa: "\f2c8"; } -.fa-piggy-bank::before { - content: "\f4d3"; } +.fa-temperature-3 { + --fa: "\f2c8"; } -.fa-battery-half::before { - content: "\f242"; } +.fa-thermometer-3 { + --fa: "\f2c8"; } -.fa-battery-3::before { - content: "\f242"; } +.fa-thermometer-three-quarters { + --fa: "\f2c8"; } -.fa-mountain-city::before { - content: "\e52e"; } +.fa-mobile-screen { + --fa: "\f3cf"; } -.fa-coins::before { - content: "\f51e"; } +.fa-mobile-android-alt { + --fa: "\f3cf"; } -.fa-khanda::before { - content: "\f66d"; } +.fa-plane-up { + --fa: "\e22d"; } -.fa-sliders::before { - content: "\f1de"; } +.fa-piggy-bank { + --fa: "\f4d3"; } -.fa-sliders-h::before { - content: "\f1de"; } +.fa-battery-half { + --fa: "\f242"; } -.fa-folder-tree::before { - content: "\f802"; } +.fa-battery-3 { + --fa: "\f242"; } -.fa-network-wired::before { - content: "\f6ff"; } +.fa-mountain-city { + --fa: "\e52e"; } -.fa-map-pin::before { - content: "\f276"; } +.fa-coins { + --fa: "\f51e"; } -.fa-hamsa::before { - content: "\f665"; } +.fa-khanda { + --fa: "\f66d"; } -.fa-cent-sign::before { - content: "\e3f5"; } +.fa-sliders { + --fa: "\f1de"; } -.fa-flask::before { - content: "\f0c3"; } +.fa-sliders-h { + --fa: "\f1de"; } -.fa-person-pregnant::before { - content: "\e31e"; } +.fa-folder-tree { + --fa: "\f802"; } -.fa-wand-sparkles::before { - content: "\f72b"; } +.fa-network-wired { + --fa: "\f6ff"; } -.fa-ellipsis-vertical::before { - content: "\f142"; } +.fa-map-pin { + --fa: "\f276"; } -.fa-ellipsis-v::before { - content: "\f142"; } +.fa-hamsa { + --fa: "\f665"; } -.fa-ticket::before { - content: "\f145"; } +.fa-cent-sign { + --fa: "\e3f5"; } -.fa-power-off::before { - content: "\f011"; } +.fa-flask { + --fa: "\f0c3"; } -.fa-right-long::before { - content: "\f30b"; } +.fa-person-pregnant { + --fa: "\e31e"; } -.fa-long-arrow-alt-right::before { - content: "\f30b"; } +.fa-wand-sparkles { + --fa: "\f72b"; } -.fa-flag-usa::before { - content: "\f74d"; } +.fa-ellipsis-vertical { + --fa: "\f142"; } -.fa-laptop-file::before { - content: "\e51d"; } +.fa-ellipsis-v { + --fa: "\f142"; } -.fa-tty::before { - content: "\f1e4"; } +.fa-ticket { + --fa: "\f145"; } -.fa-teletype::before { - content: "\f1e4"; } +.fa-power-off { + --fa: "\f011"; } -.fa-diagram-next::before { - content: "\e476"; } +.fa-right-long { + --fa: "\f30b"; } -.fa-person-rifle::before { - content: "\e54e"; } +.fa-long-arrow-alt-right { + --fa: "\f30b"; } -.fa-house-medical-circle-exclamation::before { - content: "\e512"; } +.fa-flag-usa { + --fa: "\f74d"; } -.fa-closed-captioning::before { - content: "\f20a"; } +.fa-laptop-file { + --fa: "\e51d"; } -.fa-person-hiking::before { - content: "\f6ec"; } +.fa-tty { + --fa: "\f1e4"; } -.fa-hiking::before { - content: "\f6ec"; } +.fa-teletype { + --fa: "\f1e4"; } -.fa-venus-double::before { - content: "\f226"; } +.fa-diagram-next { + --fa: "\e476"; } -.fa-images::before { - content: "\f302"; } +.fa-person-rifle { + --fa: "\e54e"; } -.fa-calculator::before { - content: "\f1ec"; } +.fa-house-medical-circle-exclamation { + --fa: "\e512"; } -.fa-people-pulling::before { - content: "\e535"; } +.fa-closed-captioning { + --fa: "\f20a"; } -.fa-n::before { - content: "\4e"; } +.fa-person-hiking { + --fa: "\f6ec"; } -.fa-cable-car::before { - content: "\f7da"; } +.fa-hiking { + --fa: "\f6ec"; } -.fa-tram::before { - content: "\f7da"; } +.fa-venus-double { + --fa: "\f226"; } -.fa-cloud-rain::before { - content: "\f73d"; } +.fa-images { + --fa: "\f302"; } -.fa-building-circle-xmark::before { - content: "\e4d4"; } +.fa-calculator { + --fa: "\f1ec"; } -.fa-ship::before { - content: "\f21a"; } +.fa-people-pulling { + --fa: "\e535"; } -.fa-arrows-down-to-line::before { - content: "\e4b8"; } +.fa-n { + --fa: "\4e"; } -.fa-download::before { - content: "\f019"; } +.fa-cable-car { + --fa: "\f7da"; } -.fa-face-grin::before { - content: "\f580"; } +.fa-tram { + --fa: "\f7da"; } -.fa-grin::before { - content: "\f580"; } +.fa-cloud-rain { + --fa: "\f73d"; } -.fa-delete-left::before { - content: "\f55a"; } +.fa-building-circle-xmark { + --fa: "\e4d4"; } -.fa-backspace::before { - content: "\f55a"; } +.fa-ship { + --fa: "\f21a"; } -.fa-eye-dropper::before { - content: "\f1fb"; } +.fa-arrows-down-to-line { + --fa: "\e4b8"; } -.fa-eye-dropper-empty::before { - content: "\f1fb"; } +.fa-download { + --fa: "\f019"; } -.fa-eyedropper::before { - content: "\f1fb"; } +.fa-face-grin { + --fa: "\f580"; } -.fa-file-circle-check::before { - content: "\e5a0"; } +.fa-grin { + --fa: "\f580"; } -.fa-forward::before { - content: "\f04e"; } +.fa-delete-left { + --fa: "\f55a"; } -.fa-mobile::before { - content: "\f3ce"; } +.fa-backspace { + --fa: "\f55a"; } -.fa-mobile-android::before { - content: "\f3ce"; } +.fa-eye-dropper { + --fa: "\f1fb"; } -.fa-mobile-phone::before { - content: "\f3ce"; } +.fa-eye-dropper-empty { + --fa: "\f1fb"; } -.fa-face-meh::before { - content: "\f11a"; } +.fa-eyedropper { + --fa: "\f1fb"; } -.fa-meh::before { - content: "\f11a"; } +.fa-file-circle-check { + --fa: "\e5a0"; } -.fa-align-center::before { - content: "\f037"; } +.fa-forward { + --fa: "\f04e"; } -.fa-book-skull::before { - content: "\f6b7"; } +.fa-mobile { + --fa: "\f3ce"; } -.fa-book-dead::before { - content: "\f6b7"; } +.fa-mobile-android { + --fa: "\f3ce"; } -.fa-id-card::before { - content: "\f2c2"; } +.fa-mobile-phone { + --fa: "\f3ce"; } -.fa-drivers-license::before { - content: "\f2c2"; } +.fa-face-meh { + --fa: "\f11a"; } -.fa-outdent::before { - content: "\f03b"; } +.fa-meh { + --fa: "\f11a"; } -.fa-dedent::before { - content: "\f03b"; } +.fa-align-center { + --fa: "\f037"; } -.fa-heart-circle-exclamation::before { - content: "\e4fe"; } +.fa-book-skull { + --fa: "\f6b7"; } -.fa-house::before { - content: "\f015"; } +.fa-book-dead { + --fa: "\f6b7"; } -.fa-home::before { - content: "\f015"; } +.fa-id-card { + --fa: "\f2c2"; } -.fa-home-alt::before { - content: "\f015"; } +.fa-drivers-license { + --fa: "\f2c2"; } -.fa-home-lg-alt::before { - content: "\f015"; } +.fa-outdent { + --fa: "\f03b"; } -.fa-calendar-week::before { - content: "\f784"; } +.fa-dedent { + --fa: "\f03b"; } -.fa-laptop-medical::before { - content: "\f812"; } +.fa-heart-circle-exclamation { + --fa: "\e4fe"; } -.fa-b::before { - content: "\42"; } +.fa-house { + --fa: "\f015"; } -.fa-file-medical::before { - content: "\f477"; } +.fa-home { + --fa: "\f015"; } -.fa-dice-one::before { - content: "\f525"; } +.fa-home-alt { + --fa: "\f015"; } -.fa-kiwi-bird::before { - content: "\f535"; } +.fa-home-lg-alt { + --fa: "\f015"; } -.fa-arrow-right-arrow-left::before { - content: "\f0ec"; } +.fa-calendar-week { + --fa: "\f784"; } -.fa-exchange::before { - content: "\f0ec"; } +.fa-laptop-medical { + --fa: "\f812"; } -.fa-rotate-right::before { - content: "\f2f9"; } +.fa-b { + --fa: "\42"; } -.fa-redo-alt::before { - content: "\f2f9"; } +.fa-file-medical { + --fa: "\f477"; } -.fa-rotate-forward::before { - content: "\f2f9"; } +.fa-dice-one { + --fa: "\f525"; } -.fa-utensils::before { - content: "\f2e7"; } +.fa-kiwi-bird { + --fa: "\f535"; } -.fa-cutlery::before { - content: "\f2e7"; } +.fa-arrow-right-arrow-left { + --fa: "\f0ec"; } -.fa-arrow-up-wide-short::before { - content: "\f161"; } +.fa-exchange { + --fa: "\f0ec"; } -.fa-sort-amount-up::before { - content: "\f161"; } +.fa-rotate-right { + --fa: "\f2f9"; } -.fa-mill-sign::before { - content: "\e1ed"; } +.fa-redo-alt { + --fa: "\f2f9"; } -.fa-bowl-rice::before { - content: "\e2eb"; } +.fa-rotate-forward { + --fa: "\f2f9"; } -.fa-skull::before { - content: "\f54c"; } +.fa-utensils { + --fa: "\f2e7"; } -.fa-tower-broadcast::before { - content: "\f519"; } +.fa-cutlery { + --fa: "\f2e7"; } -.fa-broadcast-tower::before { - content: "\f519"; } +.fa-arrow-up-wide-short { + --fa: "\f161"; } -.fa-truck-pickup::before { - content: "\f63c"; } +.fa-sort-amount-up { + --fa: "\f161"; } -.fa-up-long::before { - content: "\f30c"; } +.fa-mill-sign { + --fa: "\e1ed"; } -.fa-long-arrow-alt-up::before { - content: "\f30c"; } +.fa-bowl-rice { + --fa: "\e2eb"; } -.fa-stop::before { - content: "\f04d"; } +.fa-skull { + --fa: "\f54c"; } -.fa-code-merge::before { - content: "\f387"; } +.fa-tower-broadcast { + --fa: "\f519"; } -.fa-upload::before { - content: "\f093"; } +.fa-broadcast-tower { + --fa: "\f519"; } -.fa-hurricane::before { - content: "\f751"; } +.fa-truck-pickup { + --fa: "\f63c"; } -.fa-mound::before { - content: "\e52d"; } +.fa-up-long { + --fa: "\f30c"; } -.fa-toilet-portable::before { - content: "\e583"; } +.fa-long-arrow-alt-up { + --fa: "\f30c"; } -.fa-compact-disc::before { - content: "\f51f"; } +.fa-stop { + --fa: "\f04d"; } -.fa-file-arrow-down::before { - content: "\f56d"; } +.fa-code-merge { + --fa: "\f387"; } -.fa-file-download::before { - content: "\f56d"; } +.fa-upload { + --fa: "\f093"; } -.fa-caravan::before { - content: "\f8ff"; } +.fa-hurricane { + --fa: "\f751"; } -.fa-shield-cat::before { - content: "\e572"; } +.fa-mound { + --fa: "\e52d"; } -.fa-bolt::before { - content: "\f0e7"; } +.fa-toilet-portable { + --fa: "\e583"; } -.fa-zap::before { - content: "\f0e7"; } +.fa-compact-disc { + --fa: "\f51f"; } -.fa-glass-water::before { - content: "\e4f4"; } +.fa-file-arrow-down { + --fa: "\f56d"; } -.fa-oil-well::before { - content: "\e532"; } +.fa-file-download { + --fa: "\f56d"; } -.fa-vault::before { - content: "\e2c5"; } +.fa-caravan { + --fa: "\f8ff"; } -.fa-mars::before { - content: "\f222"; } +.fa-shield-cat { + --fa: "\e572"; } -.fa-toilet::before { - content: "\f7d8"; } +.fa-bolt { + --fa: "\f0e7"; } -.fa-plane-circle-xmark::before { - content: "\e557"; } +.fa-zap { + --fa: "\f0e7"; } -.fa-yen-sign::before { - content: "\f157"; } +.fa-glass-water { + --fa: "\e4f4"; } -.fa-cny::before { - content: "\f157"; } +.fa-oil-well { + --fa: "\e532"; } -.fa-jpy::before { - content: "\f157"; } +.fa-vault { + --fa: "\e2c5"; } -.fa-rmb::before { - content: "\f157"; } +.fa-mars { + --fa: "\f222"; } -.fa-yen::before { - content: "\f157"; } +.fa-toilet { + --fa: "\f7d8"; } -.fa-ruble-sign::before { - content: "\f158"; } +.fa-plane-circle-xmark { + --fa: "\e557"; } -.fa-rouble::before { - content: "\f158"; } +.fa-yen-sign { + --fa: "\f157"; } -.fa-rub::before { - content: "\f158"; } +.fa-cny { + --fa: "\f157"; } -.fa-ruble::before { - content: "\f158"; } +.fa-jpy { + --fa: "\f157"; } -.fa-sun::before { - content: "\f185"; } +.fa-rmb { + --fa: "\f157"; } -.fa-guitar::before { - content: "\f7a6"; } +.fa-yen { + --fa: "\f157"; } -.fa-face-laugh-wink::before { - content: "\f59c"; } +.fa-ruble-sign { + --fa: "\f158"; } -.fa-laugh-wink::before { - content: "\f59c"; } +.fa-rouble { + --fa: "\f158"; } -.fa-horse-head::before { - content: "\f7ab"; } +.fa-rub { + --fa: "\f158"; } -.fa-bore-hole::before { - content: "\e4c3"; } +.fa-ruble { + --fa: "\f158"; } -.fa-industry::before { - content: "\f275"; } +.fa-sun { + --fa: "\f185"; } -.fa-circle-down::before { - content: "\f358"; } +.fa-guitar { + --fa: "\f7a6"; } -.fa-arrow-alt-circle-down::before { - content: "\f358"; } +.fa-face-laugh-wink { + --fa: "\f59c"; } -.fa-arrows-turn-to-dots::before { - content: "\e4c1"; } +.fa-laugh-wink { + --fa: "\f59c"; } -.fa-florin-sign::before { - content: "\e184"; } +.fa-horse-head { + --fa: "\f7ab"; } -.fa-arrow-down-short-wide::before { - content: "\f884"; } +.fa-bore-hole { + --fa: "\e4c3"; } -.fa-sort-amount-desc::before { - content: "\f884"; } +.fa-industry { + --fa: "\f275"; } -.fa-sort-amount-down-alt::before { - content: "\f884"; } +.fa-circle-down { + --fa: "\f358"; } -.fa-less-than::before { - content: "\3c"; } +.fa-arrow-alt-circle-down { + --fa: "\f358"; } -.fa-angle-down::before { - content: "\f107"; } +.fa-arrows-turn-to-dots { + --fa: "\e4c1"; } -.fa-car-tunnel::before { - content: "\e4de"; } +.fa-florin-sign { + --fa: "\e184"; } -.fa-head-side-cough::before { - content: "\e061"; } +.fa-arrow-down-short-wide { + --fa: "\f884"; } -.fa-grip-lines::before { - content: "\f7a4"; } +.fa-sort-amount-desc { + --fa: "\f884"; } -.fa-thumbs-down::before { - content: "\f165"; } +.fa-sort-amount-down-alt { + --fa: "\f884"; } -.fa-user-lock::before { - content: "\f502"; } +.fa-less-than { + --fa: "\3c"; } -.fa-arrow-right-long::before { - content: "\f178"; } +.fa-angle-down { + --fa: "\f107"; } -.fa-long-arrow-right::before { - content: "\f178"; } +.fa-car-tunnel { + --fa: "\e4de"; } -.fa-anchor-circle-xmark::before { - content: "\e4ac"; } +.fa-head-side-cough { + --fa: "\e061"; } -.fa-ellipsis::before { - content: "\f141"; } +.fa-grip-lines { + --fa: "\f7a4"; } -.fa-ellipsis-h::before { - content: "\f141"; } +.fa-thumbs-down { + --fa: "\f165"; } -.fa-chess-pawn::before { - content: "\f443"; } +.fa-user-lock { + --fa: "\f502"; } -.fa-kit-medical::before { - content: "\f479"; } +.fa-arrow-right-long { + --fa: "\f178"; } -.fa-first-aid::before { - content: "\f479"; } +.fa-long-arrow-right { + --fa: "\f178"; } -.fa-person-through-window::before { - content: "\e5a9"; } +.fa-anchor-circle-xmark { + --fa: "\e4ac"; } -.fa-toolbox::before { - content: "\f552"; } +.fa-ellipsis { + --fa: "\f141"; } -.fa-hands-holding-circle::before { - content: "\e4fb"; } +.fa-ellipsis-h { + --fa: "\f141"; } -.fa-bug::before { - content: "\f188"; } +.fa-chess-pawn { + --fa: "\f443"; } -.fa-credit-card::before { - content: "\f09d"; } +.fa-kit-medical { + --fa: "\f479"; } -.fa-credit-card-alt::before { - content: "\f09d"; } +.fa-first-aid { + --fa: "\f479"; } -.fa-car::before { - content: "\f1b9"; } +.fa-person-through-window { + --fa: "\e5a9"; } -.fa-automobile::before { - content: "\f1b9"; } +.fa-toolbox { + --fa: "\f552"; } -.fa-hand-holding-hand::before { - content: "\e4f7"; } +.fa-hands-holding-circle { + --fa: "\e4fb"; } -.fa-book-open-reader::before { - content: "\f5da"; } +.fa-bug { + --fa: "\f188"; } -.fa-book-reader::before { - content: "\f5da"; } +.fa-credit-card { + --fa: "\f09d"; } -.fa-mountain-sun::before { - content: "\e52f"; } +.fa-credit-card-alt { + --fa: "\f09d"; } -.fa-arrows-left-right-to-line::before { - content: "\e4ba"; } +.fa-car { + --fa: "\f1b9"; } -.fa-dice-d20::before { - content: "\f6cf"; } +.fa-automobile { + --fa: "\f1b9"; } -.fa-truck-droplet::before { - content: "\e58c"; } +.fa-hand-holding-hand { + --fa: "\e4f7"; } -.fa-file-circle-xmark::before { - content: "\e5a1"; } +.fa-book-open-reader { + --fa: "\f5da"; } -.fa-temperature-arrow-up::before { - content: "\e040"; } +.fa-book-reader { + --fa: "\f5da"; } -.fa-temperature-up::before { - content: "\e040"; } +.fa-mountain-sun { + --fa: "\e52f"; } -.fa-medal::before { - content: "\f5a2"; } +.fa-arrows-left-right-to-line { + --fa: "\e4ba"; } -.fa-bed::before { - content: "\f236"; } +.fa-dice-d20 { + --fa: "\f6cf"; } -.fa-square-h::before { - content: "\f0fd"; } +.fa-truck-droplet { + --fa: "\e58c"; } -.fa-h-square::before { - content: "\f0fd"; } +.fa-file-circle-xmark { + --fa: "\e5a1"; } -.fa-podcast::before { - content: "\f2ce"; } +.fa-temperature-arrow-up { + --fa: "\e040"; } -.fa-temperature-full::before { - content: "\f2c7"; } +.fa-temperature-up { + --fa: "\e040"; } -.fa-temperature-4::before { - content: "\f2c7"; } +.fa-medal { + --fa: "\f5a2"; } -.fa-thermometer-4::before { - content: "\f2c7"; } +.fa-bed { + --fa: "\f236"; } -.fa-thermometer-full::before { - content: "\f2c7"; } +.fa-square-h { + --fa: "\f0fd"; } -.fa-bell::before { - content: "\f0f3"; } +.fa-h-square { + --fa: "\f0fd"; } -.fa-superscript::before { - content: "\f12b"; } +.fa-podcast { + --fa: "\f2ce"; } -.fa-plug-circle-xmark::before { - content: "\e560"; } +.fa-temperature-full { + --fa: "\f2c7"; } -.fa-star-of-life::before { - content: "\f621"; } +.fa-temperature-4 { + --fa: "\f2c7"; } -.fa-phone-slash::before { - content: "\f3dd"; } +.fa-thermometer-4 { + --fa: "\f2c7"; } -.fa-paint-roller::before { - content: "\f5aa"; } +.fa-thermometer-full { + --fa: "\f2c7"; } -.fa-handshake-angle::before { - content: "\f4c4"; } +.fa-bell { + --fa: "\f0f3"; } -.fa-hands-helping::before { - content: "\f4c4"; } +.fa-superscript { + --fa: "\f12b"; } -.fa-location-dot::before { - content: "\f3c5"; } +.fa-plug-circle-xmark { + --fa: "\e560"; } -.fa-map-marker-alt::before { - content: "\f3c5"; } +.fa-star-of-life { + --fa: "\f621"; } -.fa-file::before { - content: "\f15b"; } +.fa-phone-slash { + --fa: "\f3dd"; } -.fa-greater-than::before { - content: "\3e"; } +.fa-paint-roller { + --fa: "\f5aa"; } -.fa-person-swimming::before { - content: "\f5c4"; } +.fa-handshake-angle { + --fa: "\f4c4"; } -.fa-swimmer::before { - content: "\f5c4"; } +.fa-hands-helping { + --fa: "\f4c4"; } -.fa-arrow-down::before { - content: "\f063"; } +.fa-location-dot { + --fa: "\f3c5"; } -.fa-droplet::before { - content: "\f043"; } +.fa-map-marker-alt { + --fa: "\f3c5"; } -.fa-tint::before { - content: "\f043"; } +.fa-file { + --fa: "\f15b"; } -.fa-eraser::before { - content: "\f12d"; } +.fa-greater-than { + --fa: "\3e"; } -.fa-earth-americas::before { - content: "\f57d"; } +.fa-person-swimming { + --fa: "\f5c4"; } -.fa-earth::before { - content: "\f57d"; } +.fa-swimmer { + --fa: "\f5c4"; } -.fa-earth-america::before { - content: "\f57d"; } +.fa-arrow-down { + --fa: "\f063"; } -.fa-globe-americas::before { - content: "\f57d"; } +.fa-droplet { + --fa: "\f043"; } -.fa-person-burst::before { - content: "\e53b"; } +.fa-tint { + --fa: "\f043"; } -.fa-dove::before { - content: "\f4ba"; } +.fa-eraser { + --fa: "\f12d"; } -.fa-battery-empty::before { - content: "\f244"; } +.fa-earth-americas { + --fa: "\f57d"; } -.fa-battery-0::before { - content: "\f244"; } +.fa-earth { + --fa: "\f57d"; } -.fa-socks::before { - content: "\f696"; } +.fa-earth-america { + --fa: "\f57d"; } -.fa-inbox::before { - content: "\f01c"; } +.fa-globe-americas { + --fa: "\f57d"; } -.fa-section::before { - content: "\e447"; } +.fa-person-burst { + --fa: "\e53b"; } -.fa-gauge-high::before { - content: "\f625"; } +.fa-dove { + --fa: "\f4ba"; } -.fa-tachometer-alt::before { - content: "\f625"; } +.fa-battery-empty { + --fa: "\f244"; } -.fa-tachometer-alt-fast::before { - content: "\f625"; } +.fa-battery-0 { + --fa: "\f244"; } -.fa-envelope-open-text::before { - content: "\f658"; } +.fa-socks { + --fa: "\f696"; } -.fa-hospital::before { - content: "\f0f8"; } +.fa-inbox { + --fa: "\f01c"; } -.fa-hospital-alt::before { - content: "\f0f8"; } +.fa-section { + --fa: "\e447"; } -.fa-hospital-wide::before { - content: "\f0f8"; } +.fa-gauge-high { + --fa: "\f625"; } -.fa-wine-bottle::before { - content: "\f72f"; } +.fa-tachometer-alt { + --fa: "\f625"; } -.fa-chess-rook::before { - content: "\f447"; } +.fa-tachometer-alt-fast { + --fa: "\f625"; } -.fa-bars-staggered::before { - content: "\f550"; } +.fa-envelope-open-text { + --fa: "\f658"; } -.fa-reorder::before { - content: "\f550"; } +.fa-hospital { + --fa: "\f0f8"; } -.fa-stream::before { - content: "\f550"; } +.fa-hospital-alt { + --fa: "\f0f8"; } -.fa-dharmachakra::before { - content: "\f655"; } +.fa-hospital-wide { + --fa: "\f0f8"; } -.fa-hotdog::before { - content: "\f80f"; } +.fa-wine-bottle { + --fa: "\f72f"; } -.fa-person-walking-with-cane::before { - content: "\f29d"; } +.fa-chess-rook { + --fa: "\f447"; } -.fa-blind::before { - content: "\f29d"; } +.fa-bars-staggered { + --fa: "\f550"; } -.fa-drum::before { - content: "\f569"; } +.fa-reorder { + --fa: "\f550"; } -.fa-ice-cream::before { - content: "\f810"; } +.fa-stream { + --fa: "\f550"; } -.fa-heart-circle-bolt::before { - content: "\e4fc"; } +.fa-dharmachakra { + --fa: "\f655"; } -.fa-fax::before { - content: "\f1ac"; } +.fa-hotdog { + --fa: "\f80f"; } -.fa-paragraph::before { - content: "\f1dd"; } +.fa-person-walking-with-cane { + --fa: "\f29d"; } -.fa-check-to-slot::before { - content: "\f772"; } +.fa-blind { + --fa: "\f29d"; } -.fa-vote-yea::before { - content: "\f772"; } +.fa-drum { + --fa: "\f569"; } -.fa-star-half::before { - content: "\f089"; } +.fa-ice-cream { + --fa: "\f810"; } -.fa-boxes-stacked::before { - content: "\f468"; } +.fa-heart-circle-bolt { + --fa: "\e4fc"; } -.fa-boxes::before { - content: "\f468"; } +.fa-fax { + --fa: "\f1ac"; } -.fa-boxes-alt::before { - content: "\f468"; } +.fa-paragraph { + --fa: "\f1dd"; } -.fa-link::before { - content: "\f0c1"; } +.fa-check-to-slot { + --fa: "\f772"; } -.fa-chain::before { - content: "\f0c1"; } +.fa-vote-yea { + --fa: "\f772"; } -.fa-ear-listen::before { - content: "\f2a2"; } +.fa-star-half { + --fa: "\f089"; } -.fa-assistive-listening-systems::before { - content: "\f2a2"; } +.fa-boxes-stacked { + --fa: "\f468"; } -.fa-tree-city::before { - content: "\e587"; } +.fa-boxes { + --fa: "\f468"; } -.fa-play::before { - content: "\f04b"; } +.fa-boxes-alt { + --fa: "\f468"; } -.fa-font::before { - content: "\f031"; } +.fa-link { + --fa: "\f0c1"; } -.fa-table-cells-row-lock::before { - content: "\e67a"; } +.fa-chain { + --fa: "\f0c1"; } -.fa-rupiah-sign::before { - content: "\e23d"; } +.fa-ear-listen { + --fa: "\f2a2"; } -.fa-magnifying-glass::before { - content: "\f002"; } +.fa-assistive-listening-systems { + --fa: "\f2a2"; } -.fa-search::before { - content: "\f002"; } +.fa-tree-city { + --fa: "\e587"; } -.fa-table-tennis-paddle-ball::before { - content: "\f45d"; } +.fa-play { + --fa: "\f04b"; } -.fa-ping-pong-paddle-ball::before { - content: "\f45d"; } +.fa-font { + --fa: "\f031"; } -.fa-table-tennis::before { - content: "\f45d"; } +.fa-table-cells-row-lock { + --fa: "\e67a"; } -.fa-person-dots-from-line::before { - content: "\f470"; } +.fa-rupiah-sign { + --fa: "\e23d"; } -.fa-diagnoses::before { - content: "\f470"; } +.fa-magnifying-glass { + --fa: "\f002"; } -.fa-trash-can-arrow-up::before { - content: "\f82a"; } +.fa-search { + --fa: "\f002"; } -.fa-trash-restore-alt::before { - content: "\f82a"; } +.fa-table-tennis-paddle-ball { + --fa: "\f45d"; } -.fa-naira-sign::before { - content: "\e1f6"; } +.fa-ping-pong-paddle-ball { + --fa: "\f45d"; } -.fa-cart-arrow-down::before { - content: "\f218"; } +.fa-table-tennis { + --fa: "\f45d"; } -.fa-walkie-talkie::before { - content: "\f8ef"; } +.fa-person-dots-from-line { + --fa: "\f470"; } -.fa-file-pen::before { - content: "\f31c"; } +.fa-diagnoses { + --fa: "\f470"; } -.fa-file-edit::before { - content: "\f31c"; } +.fa-trash-can-arrow-up { + --fa: "\f82a"; } -.fa-receipt::before { - content: "\f543"; } +.fa-trash-restore-alt { + --fa: "\f82a"; } -.fa-square-pen::before { - content: "\f14b"; } +.fa-naira-sign { + --fa: "\e1f6"; } -.fa-pen-square::before { - content: "\f14b"; } +.fa-cart-arrow-down { + --fa: "\f218"; } -.fa-pencil-square::before { - content: "\f14b"; } +.fa-walkie-talkie { + --fa: "\f8ef"; } -.fa-suitcase-rolling::before { - content: "\f5c1"; } +.fa-file-pen { + --fa: "\f31c"; } -.fa-person-circle-exclamation::before { - content: "\e53f"; } +.fa-file-edit { + --fa: "\f31c"; } -.fa-chevron-down::before { - content: "\f078"; } +.fa-receipt { + --fa: "\f543"; } -.fa-battery-full::before { - content: "\f240"; } +.fa-square-pen { + --fa: "\f14b"; } -.fa-battery::before { - content: "\f240"; } +.fa-pen-square { + --fa: "\f14b"; } -.fa-battery-5::before { - content: "\f240"; } +.fa-pencil-square { + --fa: "\f14b"; } -.fa-skull-crossbones::before { - content: "\f714"; } +.fa-suitcase-rolling { + --fa: "\f5c1"; } -.fa-code-compare::before { - content: "\e13a"; } +.fa-person-circle-exclamation { + --fa: "\e53f"; } -.fa-list-ul::before { - content: "\f0ca"; } +.fa-chevron-down { + --fa: "\f078"; } -.fa-list-dots::before { - content: "\f0ca"; } +.fa-battery-full { + --fa: "\f240"; } -.fa-school-lock::before { - content: "\e56f"; } +.fa-battery { + --fa: "\f240"; } -.fa-tower-cell::before { - content: "\e585"; } +.fa-battery-5 { + --fa: "\f240"; } -.fa-down-long::before { - content: "\f309"; } +.fa-skull-crossbones { + --fa: "\f714"; } -.fa-long-arrow-alt-down::before { - content: "\f309"; } +.fa-code-compare { + --fa: "\e13a"; } -.fa-ranking-star::before { - content: "\e561"; } +.fa-list-ul { + --fa: "\f0ca"; } -.fa-chess-king::before { - content: "\f43f"; } +.fa-list-dots { + --fa: "\f0ca"; } -.fa-person-harassing::before { - content: "\e549"; } +.fa-school-lock { + --fa: "\e56f"; } -.fa-brazilian-real-sign::before { - content: "\e46c"; } +.fa-tower-cell { + --fa: "\e585"; } -.fa-landmark-dome::before { - content: "\f752"; } +.fa-down-long { + --fa: "\f309"; } -.fa-landmark-alt::before { - content: "\f752"; } +.fa-long-arrow-alt-down { + --fa: "\f309"; } -.fa-arrow-up::before { - content: "\f062"; } +.fa-ranking-star { + --fa: "\e561"; } -.fa-tv::before { - content: "\f26c"; } +.fa-chess-king { + --fa: "\f43f"; } -.fa-television::before { - content: "\f26c"; } +.fa-person-harassing { + --fa: "\e549"; } -.fa-tv-alt::before { - content: "\f26c"; } +.fa-brazilian-real-sign { + --fa: "\e46c"; } -.fa-shrimp::before { - content: "\e448"; } +.fa-landmark-dome { + --fa: "\f752"; } -.fa-list-check::before { - content: "\f0ae"; } +.fa-landmark-alt { + --fa: "\f752"; } -.fa-tasks::before { - content: "\f0ae"; } +.fa-arrow-up { + --fa: "\f062"; } -.fa-jug-detergent::before { - content: "\e519"; } +.fa-tv { + --fa: "\f26c"; } -.fa-circle-user::before { - content: "\f2bd"; } +.fa-television { + --fa: "\f26c"; } -.fa-user-circle::before { - content: "\f2bd"; } +.fa-tv-alt { + --fa: "\f26c"; } -.fa-user-shield::before { - content: "\f505"; } +.fa-shrimp { + --fa: "\e448"; } -.fa-wind::before { - content: "\f72e"; } +.fa-list-check { + --fa: "\f0ae"; } -.fa-car-burst::before { - content: "\f5e1"; } +.fa-tasks { + --fa: "\f0ae"; } -.fa-car-crash::before { - content: "\f5e1"; } +.fa-jug-detergent { + --fa: "\e519"; } -.fa-y::before { - content: "\59"; } +.fa-circle-user { + --fa: "\f2bd"; } -.fa-person-snowboarding::before { - content: "\f7ce"; } +.fa-user-circle { + --fa: "\f2bd"; } -.fa-snowboarding::before { - content: "\f7ce"; } +.fa-user-shield { + --fa: "\f505"; } -.fa-truck-fast::before { - content: "\f48b"; } +.fa-wind { + --fa: "\f72e"; } -.fa-shipping-fast::before { - content: "\f48b"; } +.fa-car-burst { + --fa: "\f5e1"; } -.fa-fish::before { - content: "\f578"; } +.fa-car-crash { + --fa: "\f5e1"; } -.fa-user-graduate::before { - content: "\f501"; } +.fa-y { + --fa: "\59"; } -.fa-circle-half-stroke::before { - content: "\f042"; } +.fa-person-snowboarding { + --fa: "\f7ce"; } -.fa-adjust::before { - content: "\f042"; } +.fa-snowboarding { + --fa: "\f7ce"; } -.fa-clapperboard::before { - content: "\e131"; } +.fa-truck-fast { + --fa: "\f48b"; } -.fa-circle-radiation::before { - content: "\f7ba"; } +.fa-shipping-fast { + --fa: "\f48b"; } -.fa-radiation-alt::before { - content: "\f7ba"; } +.fa-fish { + --fa: "\f578"; } -.fa-baseball::before { - content: "\f433"; } +.fa-user-graduate { + --fa: "\f501"; } -.fa-baseball-ball::before { - content: "\f433"; } +.fa-circle-half-stroke { + --fa: "\f042"; } -.fa-jet-fighter-up::before { - content: "\e518"; } +.fa-adjust { + --fa: "\f042"; } -.fa-diagram-project::before { - content: "\f542"; } +.fa-clapperboard { + --fa: "\e131"; } -.fa-project-diagram::before { - content: "\f542"; } +.fa-circle-radiation { + --fa: "\f7ba"; } -.fa-copy::before { - content: "\f0c5"; } +.fa-radiation-alt { + --fa: "\f7ba"; } -.fa-volume-xmark::before { - content: "\f6a9"; } +.fa-baseball { + --fa: "\f433"; } -.fa-volume-mute::before { - content: "\f6a9"; } +.fa-baseball-ball { + --fa: "\f433"; } -.fa-volume-times::before { - content: "\f6a9"; } +.fa-jet-fighter-up { + --fa: "\e518"; } -.fa-hand-sparkles::before { - content: "\e05d"; } +.fa-diagram-project { + --fa: "\f542"; } -.fa-grip::before { - content: "\f58d"; } +.fa-project-diagram { + --fa: "\f542"; } -.fa-grip-horizontal::before { - content: "\f58d"; } +.fa-copy { + --fa: "\f0c5"; } -.fa-share-from-square::before { - content: "\f14d"; } +.fa-volume-xmark { + --fa: "\f6a9"; } -.fa-share-square::before { - content: "\f14d"; } +.fa-volume-mute { + --fa: "\f6a9"; } -.fa-child-combatant::before { - content: "\e4e0"; } +.fa-volume-times { + --fa: "\f6a9"; } -.fa-child-rifle::before { - content: "\e4e0"; } +.fa-hand-sparkles { + --fa: "\e05d"; } -.fa-gun::before { - content: "\e19b"; } +.fa-grip { + --fa: "\f58d"; } -.fa-square-phone::before { - content: "\f098"; } +.fa-grip-horizontal { + --fa: "\f58d"; } -.fa-phone-square::before { - content: "\f098"; } +.fa-share-from-square { + --fa: "\f14d"; } -.fa-plus::before { - content: "\2b"; } +.fa-share-square { + --fa: "\f14d"; } -.fa-add::before { - content: "\2b"; } +.fa-child-combatant { + --fa: "\e4e0"; } -.fa-expand::before { - content: "\f065"; } +.fa-child-rifle { + --fa: "\e4e0"; } -.fa-computer::before { - content: "\e4e5"; } +.fa-gun { + --fa: "\e19b"; } -.fa-xmark::before { - content: "\f00d"; } +.fa-square-phone { + --fa: "\f098"; } -.fa-close::before { - content: "\f00d"; } +.fa-phone-square { + --fa: "\f098"; } -.fa-multiply::before { - content: "\f00d"; } +.fa-plus { + --fa: "\2b"; } -.fa-remove::before { - content: "\f00d"; } +.fa-add { + --fa: "\2b"; } -.fa-times::before { - content: "\f00d"; } +.fa-expand { + --fa: "\f065"; } -.fa-arrows-up-down-left-right::before { - content: "\f047"; } +.fa-computer { + --fa: "\e4e5"; } -.fa-arrows::before { - content: "\f047"; } +.fa-xmark { + --fa: "\f00d"; } -.fa-chalkboard-user::before { - content: "\f51c"; } +.fa-close { + --fa: "\f00d"; } -.fa-chalkboard-teacher::before { - content: "\f51c"; } +.fa-multiply { + --fa: "\f00d"; } -.fa-peso-sign::before { - content: "\e222"; } +.fa-remove { + --fa: "\f00d"; } -.fa-building-shield::before { - content: "\e4d8"; } +.fa-times { + --fa: "\f00d"; } -.fa-baby::before { - content: "\f77c"; } +.fa-arrows-up-down-left-right { + --fa: "\f047"; } -.fa-users-line::before { - content: "\e592"; } +.fa-arrows { + --fa: "\f047"; } -.fa-quote-left::before { - content: "\f10d"; } +.fa-chalkboard-user { + --fa: "\f51c"; } -.fa-quote-left-alt::before { - content: "\f10d"; } +.fa-chalkboard-teacher { + --fa: "\f51c"; } -.fa-tractor::before { - content: "\f722"; } +.fa-peso-sign { + --fa: "\e222"; } -.fa-trash-arrow-up::before { - content: "\f829"; } +.fa-building-shield { + --fa: "\e4d8"; } -.fa-trash-restore::before { - content: "\f829"; } +.fa-baby { + --fa: "\f77c"; } -.fa-arrow-down-up-lock::before { - content: "\e4b0"; } +.fa-users-line { + --fa: "\e592"; } -.fa-lines-leaning::before { - content: "\e51e"; } +.fa-quote-left { + --fa: "\f10d"; } -.fa-ruler-combined::before { - content: "\f546"; } +.fa-quote-left-alt { + --fa: "\f10d"; } -.fa-copyright::before { - content: "\f1f9"; } +.fa-tractor { + --fa: "\f722"; } -.fa-equals::before { - content: "\3d"; } +.fa-trash-arrow-up { + --fa: "\f829"; } -.fa-blender::before { - content: "\f517"; } +.fa-trash-restore { + --fa: "\f829"; } -.fa-teeth::before { - content: "\f62e"; } +.fa-arrow-down-up-lock { + --fa: "\e4b0"; } -.fa-shekel-sign::before { - content: "\f20b"; } +.fa-lines-leaning { + --fa: "\e51e"; } -.fa-ils::before { - content: "\f20b"; } +.fa-ruler-combined { + --fa: "\f546"; } -.fa-shekel::before { - content: "\f20b"; } +.fa-copyright { + --fa: "\f1f9"; } -.fa-sheqel::before { - content: "\f20b"; } +.fa-equals { + --fa: "\3d"; } -.fa-sheqel-sign::before { - content: "\f20b"; } +.fa-blender { + --fa: "\f517"; } -.fa-map::before { - content: "\f279"; } +.fa-teeth { + --fa: "\f62e"; } -.fa-rocket::before { - content: "\f135"; } +.fa-shekel-sign { + --fa: "\f20b"; } -.fa-photo-film::before { - content: "\f87c"; } +.fa-ils { + --fa: "\f20b"; } -.fa-photo-video::before { - content: "\f87c"; } +.fa-shekel { + --fa: "\f20b"; } -.fa-folder-minus::before { - content: "\f65d"; } +.fa-sheqel { + --fa: "\f20b"; } -.fa-store::before { - content: "\f54e"; } +.fa-sheqel-sign { + --fa: "\f20b"; } -.fa-arrow-trend-up::before { - content: "\e098"; } +.fa-map { + --fa: "\f279"; } -.fa-plug-circle-minus::before { - content: "\e55e"; } +.fa-rocket { + --fa: "\f135"; } -.fa-sign-hanging::before { - content: "\f4d9"; } +.fa-photo-film { + --fa: "\f87c"; } -.fa-sign::before { - content: "\f4d9"; } +.fa-photo-video { + --fa: "\f87c"; } -.fa-bezier-curve::before { - content: "\f55b"; } +.fa-folder-minus { + --fa: "\f65d"; } -.fa-bell-slash::before { - content: "\f1f6"; } +.fa-hexagon-nodes-bolt { + --fa: "\e69a"; } -.fa-tablet::before { - content: "\f3fb"; } +.fa-store { + --fa: "\f54e"; } -.fa-tablet-android::before { - content: "\f3fb"; } +.fa-arrow-trend-up { + --fa: "\e098"; } -.fa-school-flag::before { - content: "\e56e"; } +.fa-plug-circle-minus { + --fa: "\e55e"; } -.fa-fill::before { - content: "\f575"; } +.fa-sign-hanging { + --fa: "\f4d9"; } -.fa-angle-up::before { - content: "\f106"; } +.fa-sign { + --fa: "\f4d9"; } -.fa-drumstick-bite::before { - content: "\f6d7"; } +.fa-bezier-curve { + --fa: "\f55b"; } -.fa-holly-berry::before { - content: "\f7aa"; } +.fa-bell-slash { + --fa: "\f1f6"; } -.fa-chevron-left::before { - content: "\f053"; } +.fa-tablet { + --fa: "\f3fb"; } -.fa-bacteria::before { - content: "\e059"; } +.fa-tablet-android { + --fa: "\f3fb"; } -.fa-hand-lizard::before { - content: "\f258"; } +.fa-school-flag { + --fa: "\e56e"; } -.fa-notdef::before { - content: "\e1fe"; } +.fa-fill { + --fa: "\f575"; } -.fa-disease::before { - content: "\f7fa"; } +.fa-angle-up { + --fa: "\f106"; } -.fa-briefcase-medical::before { - content: "\f469"; } +.fa-drumstick-bite { + --fa: "\f6d7"; } -.fa-genderless::before { - content: "\f22d"; } +.fa-holly-berry { + --fa: "\f7aa"; } -.fa-chevron-right::before { - content: "\f054"; } +.fa-chevron-left { + --fa: "\f053"; } -.fa-retweet::before { - content: "\f079"; } +.fa-bacteria { + --fa: "\e059"; } -.fa-car-rear::before { - content: "\f5de"; } +.fa-hand-lizard { + --fa: "\f258"; } -.fa-car-alt::before { - content: "\f5de"; } +.fa-notdef { + --fa: "\e1fe"; } -.fa-pump-soap::before { - content: "\e06b"; } +.fa-disease { + --fa: "\f7fa"; } -.fa-video-slash::before { - content: "\f4e2"; } +.fa-briefcase-medical { + --fa: "\f469"; } -.fa-battery-quarter::before { - content: "\f243"; } +.fa-genderless { + --fa: "\f22d"; } -.fa-battery-2::before { - content: "\f243"; } +.fa-chevron-right { + --fa: "\f054"; } -.fa-radio::before { - content: "\f8d7"; } +.fa-retweet { + --fa: "\f079"; } -.fa-baby-carriage::before { - content: "\f77d"; } +.fa-car-rear { + --fa: "\f5de"; } -.fa-carriage-baby::before { - content: "\f77d"; } +.fa-car-alt { + --fa: "\f5de"; } -.fa-traffic-light::before { - content: "\f637"; } +.fa-pump-soap { + --fa: "\e06b"; } -.fa-thermometer::before { - content: "\f491"; } +.fa-video-slash { + --fa: "\f4e2"; } -.fa-vr-cardboard::before { - content: "\f729"; } +.fa-battery-quarter { + --fa: "\f243"; } -.fa-hand-middle-finger::before { - content: "\f806"; } +.fa-battery-2 { + --fa: "\f243"; } -.fa-percent::before { - content: "\25"; } +.fa-radio { + --fa: "\f8d7"; } -.fa-percentage::before { - content: "\25"; } +.fa-baby-carriage { + --fa: "\f77d"; } -.fa-truck-moving::before { - content: "\f4df"; } +.fa-carriage-baby { + --fa: "\f77d"; } -.fa-glass-water-droplet::before { - content: "\e4f5"; } +.fa-traffic-light { + --fa: "\f637"; } -.fa-display::before { - content: "\e163"; } +.fa-thermometer { + --fa: "\f491"; } -.fa-face-smile::before { - content: "\f118"; } +.fa-vr-cardboard { + --fa: "\f729"; } -.fa-smile::before { - content: "\f118"; } +.fa-hand-middle-finger { + --fa: "\f806"; } -.fa-thumbtack::before { - content: "\f08d"; } +.fa-percent { + --fa: "\25"; } -.fa-thumb-tack::before { - content: "\f08d"; } +.fa-percentage { + --fa: "\25"; } -.fa-trophy::before { - content: "\f091"; } +.fa-truck-moving { + --fa: "\f4df"; } -.fa-person-praying::before { - content: "\f683"; } +.fa-glass-water-droplet { + --fa: "\e4f5"; } -.fa-pray::before { - content: "\f683"; } +.fa-display { + --fa: "\e163"; } -.fa-hammer::before { - content: "\f6e3"; } +.fa-face-smile { + --fa: "\f118"; } -.fa-hand-peace::before { - content: "\f25b"; } +.fa-smile { + --fa: "\f118"; } -.fa-rotate::before { - content: "\f2f1"; } +.fa-thumbtack { + --fa: "\f08d"; } -.fa-sync-alt::before { - content: "\f2f1"; } +.fa-thumb-tack { + --fa: "\f08d"; } -.fa-spinner::before { - content: "\f110"; } +.fa-trophy { + --fa: "\f091"; } -.fa-robot::before { - content: "\f544"; } +.fa-person-praying { + --fa: "\f683"; } -.fa-peace::before { - content: "\f67c"; } +.fa-pray { + --fa: "\f683"; } -.fa-gears::before { - content: "\f085"; } +.fa-hammer { + --fa: "\f6e3"; } -.fa-cogs::before { - content: "\f085"; } +.fa-hand-peace { + --fa: "\f25b"; } -.fa-warehouse::before { - content: "\f494"; } +.fa-rotate { + --fa: "\f2f1"; } -.fa-arrow-up-right-dots::before { - content: "\e4b7"; } +.fa-sync-alt { + --fa: "\f2f1"; } -.fa-splotch::before { - content: "\f5bc"; } +.fa-spinner { + --fa: "\f110"; } -.fa-face-grin-hearts::before { - content: "\f584"; } +.fa-robot { + --fa: "\f544"; } -.fa-grin-hearts::before { - content: "\f584"; } +.fa-peace { + --fa: "\f67c"; } -.fa-dice-four::before { - content: "\f524"; } +.fa-gears { + --fa: "\f085"; } -.fa-sim-card::before { - content: "\f7c4"; } +.fa-cogs { + --fa: "\f085"; } -.fa-transgender::before { - content: "\f225"; } +.fa-warehouse { + --fa: "\f494"; } -.fa-transgender-alt::before { - content: "\f225"; } +.fa-arrow-up-right-dots { + --fa: "\e4b7"; } -.fa-mercury::before { - content: "\f223"; } +.fa-splotch { + --fa: "\f5bc"; } -.fa-arrow-turn-down::before { - content: "\f149"; } +.fa-face-grin-hearts { + --fa: "\f584"; } -.fa-level-down::before { - content: "\f149"; } +.fa-grin-hearts { + --fa: "\f584"; } -.fa-person-falling-burst::before { - content: "\e547"; } +.fa-dice-four { + --fa: "\f524"; } -.fa-award::before { - content: "\f559"; } +.fa-sim-card { + --fa: "\f7c4"; } -.fa-ticket-simple::before { - content: "\f3ff"; } +.fa-transgender { + --fa: "\f225"; } -.fa-ticket-alt::before { - content: "\f3ff"; } +.fa-transgender-alt { + --fa: "\f225"; } -.fa-building::before { - content: "\f1ad"; } +.fa-mercury { + --fa: "\f223"; } -.fa-angles-left::before { - content: "\f100"; } +.fa-arrow-turn-down { + --fa: "\f149"; } -.fa-angle-double-left::before { - content: "\f100"; } +.fa-level-down { + --fa: "\f149"; } -.fa-qrcode::before { - content: "\f029"; } +.fa-person-falling-burst { + --fa: "\e547"; } -.fa-clock-rotate-left::before { - content: "\f1da"; } +.fa-award { + --fa: "\f559"; } -.fa-history::before { - content: "\f1da"; } +.fa-ticket-simple { + --fa: "\f3ff"; } -.fa-face-grin-beam-sweat::before { - content: "\f583"; } +.fa-ticket-alt { + --fa: "\f3ff"; } -.fa-grin-beam-sweat::before { - content: "\f583"; } +.fa-building { + --fa: "\f1ad"; } -.fa-file-export::before { - content: "\f56e"; } +.fa-angles-left { + --fa: "\f100"; } -.fa-arrow-right-from-file::before { - content: "\f56e"; } +.fa-angle-double-left { + --fa: "\f100"; } -.fa-shield::before { - content: "\f132"; } +.fa-qrcode { + --fa: "\f029"; } -.fa-shield-blank::before { - content: "\f132"; } +.fa-clock-rotate-left { + --fa: "\f1da"; } -.fa-arrow-up-short-wide::before { - content: "\f885"; } +.fa-history { + --fa: "\f1da"; } -.fa-sort-amount-up-alt::before { - content: "\f885"; } +.fa-face-grin-beam-sweat { + --fa: "\f583"; } -.fa-house-medical::before { - content: "\e3b2"; } +.fa-grin-beam-sweat { + --fa: "\f583"; } -.fa-golf-ball-tee::before { - content: "\f450"; } +.fa-file-export { + --fa: "\f56e"; } -.fa-golf-ball::before { - content: "\f450"; } +.fa-arrow-right-from-file { + --fa: "\f56e"; } -.fa-circle-chevron-left::before { - content: "\f137"; } +.fa-shield { + --fa: "\f132"; } -.fa-chevron-circle-left::before { - content: "\f137"; } +.fa-shield-blank { + --fa: "\f132"; } -.fa-house-chimney-window::before { - content: "\e00d"; } +.fa-arrow-up-short-wide { + --fa: "\f885"; } -.fa-pen-nib::before { - content: "\f5ad"; } +.fa-sort-amount-up-alt { + --fa: "\f885"; } -.fa-tent-arrow-turn-left::before { - content: "\e580"; } +.fa-comment-nodes { + --fa: "\e696"; } -.fa-tents::before { - content: "\e582"; } +.fa-house-medical { + --fa: "\e3b2"; } -.fa-wand-magic::before { - content: "\f0d0"; } +.fa-golf-ball-tee { + --fa: "\f450"; } -.fa-magic::before { - content: "\f0d0"; } +.fa-golf-ball { + --fa: "\f450"; } -.fa-dog::before { - content: "\f6d3"; } +.fa-circle-chevron-left { + --fa: "\f137"; } -.fa-carrot::before { - content: "\f787"; } +.fa-chevron-circle-left { + --fa: "\f137"; } -.fa-moon::before { - content: "\f186"; } +.fa-house-chimney-window { + --fa: "\e00d"; } -.fa-wine-glass-empty::before { - content: "\f5ce"; } +.fa-pen-nib { + --fa: "\f5ad"; } -.fa-wine-glass-alt::before { - content: "\f5ce"; } +.fa-tent-arrow-turn-left { + --fa: "\e580"; } -.fa-cheese::before { - content: "\f7ef"; } +.fa-tents { + --fa: "\e582"; } -.fa-yin-yang::before { - content: "\f6ad"; } +.fa-wand-magic { + --fa: "\f0d0"; } -.fa-music::before { - content: "\f001"; } +.fa-magic { + --fa: "\f0d0"; } -.fa-code-commit::before { - content: "\f386"; } +.fa-dog { + --fa: "\f6d3"; } -.fa-temperature-low::before { - content: "\f76b"; } +.fa-carrot { + --fa: "\f787"; } -.fa-person-biking::before { - content: "\f84a"; } +.fa-moon { + --fa: "\f186"; } -.fa-biking::before { - content: "\f84a"; } +.fa-wine-glass-empty { + --fa: "\f5ce"; } -.fa-broom::before { - content: "\f51a"; } +.fa-wine-glass-alt { + --fa: "\f5ce"; } -.fa-shield-heart::before { - content: "\e574"; } +.fa-cheese { + --fa: "\f7ef"; } -.fa-gopuram::before { - content: "\f664"; } +.fa-yin-yang { + --fa: "\f6ad"; } -.fa-earth-oceania::before { - content: "\e47b"; } +.fa-music { + --fa: "\f001"; } -.fa-globe-oceania::before { - content: "\e47b"; } +.fa-code-commit { + --fa: "\f386"; } -.fa-square-xmark::before { - content: "\f2d3"; } +.fa-temperature-low { + --fa: "\f76b"; } -.fa-times-square::before { - content: "\f2d3"; } +.fa-person-biking { + --fa: "\f84a"; } -.fa-xmark-square::before { - content: "\f2d3"; } +.fa-biking { + --fa: "\f84a"; } -.fa-hashtag::before { - content: "\23"; } +.fa-broom { + --fa: "\f51a"; } -.fa-up-right-and-down-left-from-center::before { - content: "\f424"; } +.fa-shield-heart { + --fa: "\e574"; } -.fa-expand-alt::before { - content: "\f424"; } +.fa-gopuram { + --fa: "\f664"; } -.fa-oil-can::before { - content: "\f613"; } +.fa-earth-oceania { + --fa: "\e47b"; } -.fa-t::before { - content: "\54"; } +.fa-globe-oceania { + --fa: "\e47b"; } -.fa-hippo::before { - content: "\f6ed"; } +.fa-square-xmark { + --fa: "\f2d3"; } -.fa-chart-column::before { - content: "\e0e3"; } +.fa-times-square { + --fa: "\f2d3"; } -.fa-infinity::before { - content: "\f534"; } +.fa-xmark-square { + --fa: "\f2d3"; } -.fa-vial-circle-check::before { - content: "\e596"; } +.fa-hashtag { + --fa: "\23"; } -.fa-person-arrow-down-to-line::before { - content: "\e538"; } +.fa-up-right-and-down-left-from-center { + --fa: "\f424"; } -.fa-voicemail::before { - content: "\f897"; } +.fa-expand-alt { + --fa: "\f424"; } -.fa-fan::before { - content: "\f863"; } +.fa-oil-can { + --fa: "\f613"; } -.fa-person-walking-luggage::before { - content: "\e554"; } +.fa-t { + --fa: "\54"; } -.fa-up-down::before { - content: "\f338"; } +.fa-hippo { + --fa: "\f6ed"; } -.fa-arrows-alt-v::before { - content: "\f338"; } +.fa-chart-column { + --fa: "\e0e3"; } -.fa-cloud-moon-rain::before { - content: "\f73c"; } +.fa-infinity { + --fa: "\f534"; } -.fa-calendar::before { - content: "\f133"; } +.fa-vial-circle-check { + --fa: "\e596"; } -.fa-trailer::before { - content: "\e041"; } +.fa-person-arrow-down-to-line { + --fa: "\e538"; } -.fa-bahai::before { - content: "\f666"; } +.fa-voicemail { + --fa: "\f897"; } -.fa-haykal::before { - content: "\f666"; } +.fa-fan { + --fa: "\f863"; } -.fa-sd-card::before { - content: "\f7c2"; } +.fa-person-walking-luggage { + --fa: "\e554"; } -.fa-dragon::before { - content: "\f6d5"; } +.fa-up-down { + --fa: "\f338"; } -.fa-shoe-prints::before { - content: "\f54b"; } +.fa-arrows-alt-v { + --fa: "\f338"; } -.fa-circle-plus::before { - content: "\f055"; } +.fa-cloud-moon-rain { + --fa: "\f73c"; } -.fa-plus-circle::before { - content: "\f055"; } +.fa-calendar { + --fa: "\f133"; } -.fa-face-grin-tongue-wink::before { - content: "\f58b"; } +.fa-trailer { + --fa: "\e041"; } -.fa-grin-tongue-wink::before { - content: "\f58b"; } +.fa-bahai { + --fa: "\f666"; } -.fa-hand-holding::before { - content: "\f4bd"; } +.fa-haykal { + --fa: "\f666"; } -.fa-plug-circle-exclamation::before { - content: "\e55d"; } +.fa-sd-card { + --fa: "\f7c2"; } -.fa-link-slash::before { - content: "\f127"; } +.fa-dragon { + --fa: "\f6d5"; } -.fa-chain-broken::before { - content: "\f127"; } +.fa-shoe-prints { + --fa: "\f54b"; } -.fa-chain-slash::before { - content: "\f127"; } +.fa-circle-plus { + --fa: "\f055"; } -.fa-unlink::before { - content: "\f127"; } +.fa-plus-circle { + --fa: "\f055"; } -.fa-clone::before { - content: "\f24d"; } +.fa-face-grin-tongue-wink { + --fa: "\f58b"; } -.fa-person-walking-arrow-loop-left::before { - content: "\e551"; } +.fa-grin-tongue-wink { + --fa: "\f58b"; } -.fa-arrow-up-z-a::before { - content: "\f882"; } +.fa-hand-holding { + --fa: "\f4bd"; } -.fa-sort-alpha-up-alt::before { - content: "\f882"; } +.fa-plug-circle-exclamation { + --fa: "\e55d"; } -.fa-fire-flame-curved::before { - content: "\f7e4"; } +.fa-link-slash { + --fa: "\f127"; } -.fa-fire-alt::before { - content: "\f7e4"; } +.fa-chain-broken { + --fa: "\f127"; } -.fa-tornado::before { - content: "\f76f"; } +.fa-chain-slash { + --fa: "\f127"; } -.fa-file-circle-plus::before { - content: "\e494"; } +.fa-unlink { + --fa: "\f127"; } -.fa-book-quran::before { - content: "\f687"; } +.fa-clone { + --fa: "\f24d"; } -.fa-quran::before { - content: "\f687"; } +.fa-person-walking-arrow-loop-left { + --fa: "\e551"; } -.fa-anchor::before { - content: "\f13d"; } +.fa-arrow-up-z-a { + --fa: "\f882"; } -.fa-border-all::before { - content: "\f84c"; } +.fa-sort-alpha-up-alt { + --fa: "\f882"; } -.fa-face-angry::before { - content: "\f556"; } +.fa-fire-flame-curved { + --fa: "\f7e4"; } -.fa-angry::before { - content: "\f556"; } +.fa-fire-alt { + --fa: "\f7e4"; } -.fa-cookie-bite::before { - content: "\f564"; } +.fa-tornado { + --fa: "\f76f"; } -.fa-arrow-trend-down::before { - content: "\e097"; } +.fa-file-circle-plus { + --fa: "\e494"; } -.fa-rss::before { - content: "\f09e"; } +.fa-book-quran { + --fa: "\f687"; } -.fa-feed::before { - content: "\f09e"; } +.fa-quran { + --fa: "\f687"; } -.fa-draw-polygon::before { - content: "\f5ee"; } +.fa-anchor { + --fa: "\f13d"; } -.fa-scale-balanced::before { - content: "\f24e"; } +.fa-border-all { + --fa: "\f84c"; } -.fa-balance-scale::before { - content: "\f24e"; } +.fa-face-angry { + --fa: "\f556"; } -.fa-gauge-simple-high::before { - content: "\f62a"; } +.fa-angry { + --fa: "\f556"; } -.fa-tachometer::before { - content: "\f62a"; } +.fa-cookie-bite { + --fa: "\f564"; } -.fa-tachometer-fast::before { - content: "\f62a"; } +.fa-arrow-trend-down { + --fa: "\e097"; } -.fa-shower::before { - content: "\f2cc"; } +.fa-rss { + --fa: "\f09e"; } -.fa-desktop::before { - content: "\f390"; } +.fa-feed { + --fa: "\f09e"; } -.fa-desktop-alt::before { - content: "\f390"; } +.fa-draw-polygon { + --fa: "\f5ee"; } -.fa-m::before { - content: "\4d"; } +.fa-scale-balanced { + --fa: "\f24e"; } -.fa-table-list::before { - content: "\f00b"; } +.fa-balance-scale { + --fa: "\f24e"; } -.fa-th-list::before { - content: "\f00b"; } +.fa-gauge-simple-high { + --fa: "\f62a"; } -.fa-comment-sms::before { - content: "\f7cd"; } +.fa-tachometer { + --fa: "\f62a"; } -.fa-sms::before { - content: "\f7cd"; } +.fa-tachometer-fast { + --fa: "\f62a"; } -.fa-book::before { - content: "\f02d"; } +.fa-shower { + --fa: "\f2cc"; } -.fa-user-plus::before { - content: "\f234"; } +.fa-desktop { + --fa: "\f390"; } -.fa-check::before { - content: "\f00c"; } +.fa-desktop-alt { + --fa: "\f390"; } -.fa-battery-three-quarters::before { - content: "\f241"; } +.fa-m { + --fa: "\4d"; } -.fa-battery-4::before { - content: "\f241"; } +.fa-table-list { + --fa: "\f00b"; } -.fa-house-circle-check::before { - content: "\e509"; } +.fa-th-list { + --fa: "\f00b"; } -.fa-angle-left::before { - content: "\f104"; } +.fa-comment-sms { + --fa: "\f7cd"; } -.fa-diagram-successor::before { - content: "\e47a"; } +.fa-sms { + --fa: "\f7cd"; } -.fa-truck-arrow-right::before { - content: "\e58b"; } +.fa-book { + --fa: "\f02d"; } -.fa-arrows-split-up-and-left::before { - content: "\e4bc"; } +.fa-user-plus { + --fa: "\f234"; } -.fa-hand-fist::before { - content: "\f6de"; } +.fa-check { + --fa: "\f00c"; } -.fa-fist-raised::before { - content: "\f6de"; } +.fa-battery-three-quarters { + --fa: "\f241"; } -.fa-cloud-moon::before { - content: "\f6c3"; } +.fa-battery-4 { + --fa: "\f241"; } -.fa-briefcase::before { - content: "\f0b1"; } +.fa-house-circle-check { + --fa: "\e509"; } -.fa-person-falling::before { - content: "\e546"; } +.fa-angle-left { + --fa: "\f104"; } -.fa-image-portrait::before { - content: "\f3e0"; } +.fa-diagram-successor { + --fa: "\e47a"; } -.fa-portrait::before { - content: "\f3e0"; } +.fa-truck-arrow-right { + --fa: "\e58b"; } -.fa-user-tag::before { - content: "\f507"; } +.fa-arrows-split-up-and-left { + --fa: "\e4bc"; } -.fa-rug::before { - content: "\e569"; } +.fa-hand-fist { + --fa: "\f6de"; } -.fa-earth-europe::before { - content: "\f7a2"; } +.fa-fist-raised { + --fa: "\f6de"; } -.fa-globe-europe::before { - content: "\f7a2"; } +.fa-cloud-moon { + --fa: "\f6c3"; } -.fa-cart-flatbed-suitcase::before { - content: "\f59d"; } +.fa-briefcase { + --fa: "\f0b1"; } -.fa-luggage-cart::before { - content: "\f59d"; } +.fa-person-falling { + --fa: "\e546"; } -.fa-rectangle-xmark::before { - content: "\f410"; } +.fa-image-portrait { + --fa: "\f3e0"; } -.fa-rectangle-times::before { - content: "\f410"; } +.fa-portrait { + --fa: "\f3e0"; } -.fa-times-rectangle::before { - content: "\f410"; } +.fa-user-tag { + --fa: "\f507"; } -.fa-window-close::before { - content: "\f410"; } +.fa-rug { + --fa: "\e569"; } -.fa-baht-sign::before { - content: "\e0ac"; } +.fa-earth-europe { + --fa: "\f7a2"; } -.fa-book-open::before { - content: "\f518"; } +.fa-globe-europe { + --fa: "\f7a2"; } -.fa-book-journal-whills::before { - content: "\f66a"; } +.fa-cart-flatbed-suitcase { + --fa: "\f59d"; } -.fa-journal-whills::before { - content: "\f66a"; } +.fa-luggage-cart { + --fa: "\f59d"; } -.fa-handcuffs::before { - content: "\e4f8"; } +.fa-rectangle-xmark { + --fa: "\f410"; } -.fa-triangle-exclamation::before { - content: "\f071"; } +.fa-rectangle-times { + --fa: "\f410"; } -.fa-exclamation-triangle::before { - content: "\f071"; } +.fa-times-rectangle { + --fa: "\f410"; } -.fa-warning::before { - content: "\f071"; } +.fa-window-close { + --fa: "\f410"; } -.fa-database::before { - content: "\f1c0"; } +.fa-baht-sign { + --fa: "\e0ac"; } -.fa-share::before { - content: "\f064"; } +.fa-book-open { + --fa: "\f518"; } -.fa-mail-forward::before { - content: "\f064"; } +.fa-book-journal-whills { + --fa: "\f66a"; } -.fa-bottle-droplet::before { - content: "\e4c4"; } +.fa-journal-whills { + --fa: "\f66a"; } -.fa-mask-face::before { - content: "\e1d7"; } +.fa-handcuffs { + --fa: "\e4f8"; } -.fa-hill-rockslide::before { - content: "\e508"; } +.fa-triangle-exclamation { + --fa: "\f071"; } -.fa-right-left::before { - content: "\f362"; } +.fa-exclamation-triangle { + --fa: "\f071"; } -.fa-exchange-alt::before { - content: "\f362"; } +.fa-warning { + --fa: "\f071"; } -.fa-paper-plane::before { - content: "\f1d8"; } +.fa-database { + --fa: "\f1c0"; } -.fa-road-circle-exclamation::before { - content: "\e565"; } +.fa-share { + --fa: "\f064"; } -.fa-dungeon::before { - content: "\f6d9"; } +.fa-mail-forward { + --fa: "\f064"; } -.fa-align-right::before { - content: "\f038"; } +.fa-bottle-droplet { + --fa: "\e4c4"; } -.fa-money-bill-1-wave::before { - content: "\f53b"; } +.fa-mask-face { + --fa: "\e1d7"; } -.fa-money-bill-wave-alt::before { - content: "\f53b"; } +.fa-hill-rockslide { + --fa: "\e508"; } -.fa-life-ring::before { - content: "\f1cd"; } +.fa-right-left { + --fa: "\f362"; } -.fa-hands::before { - content: "\f2a7"; } +.fa-exchange-alt { + --fa: "\f362"; } -.fa-sign-language::before { - content: "\f2a7"; } +.fa-paper-plane { + --fa: "\f1d8"; } -.fa-signing::before { - content: "\f2a7"; } +.fa-road-circle-exclamation { + --fa: "\e565"; } -.fa-calendar-day::before { - content: "\f783"; } +.fa-dungeon { + --fa: "\f6d9"; } -.fa-water-ladder::before { - content: "\f5c5"; } +.fa-align-right { + --fa: "\f038"; } -.fa-ladder-water::before { - content: "\f5c5"; } +.fa-money-bill-1-wave { + --fa: "\f53b"; } -.fa-swimming-pool::before { - content: "\f5c5"; } +.fa-money-bill-wave-alt { + --fa: "\f53b"; } -.fa-arrows-up-down::before { - content: "\f07d"; } +.fa-life-ring { + --fa: "\f1cd"; } -.fa-arrows-v::before { - content: "\f07d"; } +.fa-hands { + --fa: "\f2a7"; } -.fa-face-grimace::before { - content: "\f57f"; } +.fa-sign-language { + --fa: "\f2a7"; } -.fa-grimace::before { - content: "\f57f"; } +.fa-signing { + --fa: "\f2a7"; } -.fa-wheelchair-move::before { - content: "\e2ce"; } +.fa-calendar-day { + --fa: "\f783"; } -.fa-wheelchair-alt::before { - content: "\e2ce"; } +.fa-water-ladder { + --fa: "\f5c5"; } -.fa-turn-down::before { - content: "\f3be"; } +.fa-ladder-water { + --fa: "\f5c5"; } -.fa-level-down-alt::before { - content: "\f3be"; } +.fa-swimming-pool { + --fa: "\f5c5"; } -.fa-person-walking-arrow-right::before { - content: "\e552"; } +.fa-arrows-up-down { + --fa: "\f07d"; } -.fa-square-envelope::before { - content: "\f199"; } +.fa-arrows-v { + --fa: "\f07d"; } -.fa-envelope-square::before { - content: "\f199"; } +.fa-face-grimace { + --fa: "\f57f"; } -.fa-dice::before { - content: "\f522"; } +.fa-grimace { + --fa: "\f57f"; } -.fa-bowling-ball::before { - content: "\f436"; } +.fa-wheelchair-move { + --fa: "\e2ce"; } -.fa-brain::before { - content: "\f5dc"; } +.fa-wheelchair-alt { + --fa: "\e2ce"; } -.fa-bandage::before { - content: "\f462"; } +.fa-turn-down { + --fa: "\f3be"; } -.fa-band-aid::before { - content: "\f462"; } +.fa-level-down-alt { + --fa: "\f3be"; } -.fa-calendar-minus::before { - content: "\f272"; } +.fa-person-walking-arrow-right { + --fa: "\e552"; } -.fa-circle-xmark::before { - content: "\f057"; } +.fa-square-envelope { + --fa: "\f199"; } -.fa-times-circle::before { - content: "\f057"; } +.fa-envelope-square { + --fa: "\f199"; } -.fa-xmark-circle::before { - content: "\f057"; } +.fa-dice { + --fa: "\f522"; } -.fa-gifts::before { - content: "\f79c"; } +.fa-bowling-ball { + --fa: "\f436"; } -.fa-hotel::before { - content: "\f594"; } +.fa-brain { + --fa: "\f5dc"; } -.fa-earth-asia::before { - content: "\f57e"; } +.fa-bandage { + --fa: "\f462"; } -.fa-globe-asia::before { - content: "\f57e"; } +.fa-band-aid { + --fa: "\f462"; } -.fa-id-card-clip::before { - content: "\f47f"; } +.fa-calendar-minus { + --fa: "\f272"; } -.fa-id-card-alt::before { - content: "\f47f"; } +.fa-circle-xmark { + --fa: "\f057"; } -.fa-magnifying-glass-plus::before { - content: "\f00e"; } +.fa-times-circle { + --fa: "\f057"; } -.fa-search-plus::before { - content: "\f00e"; } +.fa-xmark-circle { + --fa: "\f057"; } -.fa-thumbs-up::before { - content: "\f164"; } +.fa-gifts { + --fa: "\f79c"; } -.fa-user-clock::before { - content: "\f4fd"; } +.fa-hotel { + --fa: "\f594"; } -.fa-hand-dots::before { - content: "\f461"; } +.fa-earth-asia { + --fa: "\f57e"; } -.fa-allergies::before { - content: "\f461"; } +.fa-globe-asia { + --fa: "\f57e"; } -.fa-file-invoice::before { - content: "\f570"; } +.fa-id-card-clip { + --fa: "\f47f"; } -.fa-window-minimize::before { - content: "\f2d1"; } +.fa-id-card-alt { + --fa: "\f47f"; } -.fa-mug-saucer::before { - content: "\f0f4"; } +.fa-magnifying-glass-plus { + --fa: "\f00e"; } -.fa-coffee::before { - content: "\f0f4"; } +.fa-search-plus { + --fa: "\f00e"; } -.fa-brush::before { - content: "\f55d"; } +.fa-thumbs-up { + --fa: "\f164"; } -.fa-mask::before { - content: "\f6fa"; } +.fa-user-clock { + --fa: "\f4fd"; } -.fa-magnifying-glass-minus::before { - content: "\f010"; } +.fa-hand-dots { + --fa: "\f461"; } -.fa-search-minus::before { - content: "\f010"; } +.fa-allergies { + --fa: "\f461"; } -.fa-ruler-vertical::before { - content: "\f548"; } +.fa-file-invoice { + --fa: "\f570"; } -.fa-user-large::before { - content: "\f406"; } +.fa-window-minimize { + --fa: "\f2d1"; } -.fa-user-alt::before { - content: "\f406"; } +.fa-mug-saucer { + --fa: "\f0f4"; } -.fa-train-tram::before { - content: "\e5b4"; } +.fa-coffee { + --fa: "\f0f4"; } -.fa-user-nurse::before { - content: "\f82f"; } +.fa-brush { + --fa: "\f55d"; } -.fa-syringe::before { - content: "\f48e"; } +.fa-file-half-dashed { + --fa: "\e698"; } -.fa-cloud-sun::before { - content: "\f6c4"; } +.fa-mask { + --fa: "\f6fa"; } -.fa-stopwatch-20::before { - content: "\e06f"; } +.fa-magnifying-glass-minus { + --fa: "\f010"; } -.fa-square-full::before { - content: "\f45c"; } +.fa-search-minus { + --fa: "\f010"; } -.fa-magnet::before { - content: "\f076"; } +.fa-ruler-vertical { + --fa: "\f548"; } -.fa-jar::before { - content: "\e516"; } +.fa-user-large { + --fa: "\f406"; } -.fa-note-sticky::before { - content: "\f249"; } +.fa-user-alt { + --fa: "\f406"; } -.fa-sticky-note::before { - content: "\f249"; } +.fa-train-tram { + --fa: "\e5b4"; } -.fa-bug-slash::before { - content: "\e490"; } +.fa-user-nurse { + --fa: "\f82f"; } -.fa-arrow-up-from-water-pump::before { - content: "\e4b6"; } +.fa-syringe { + --fa: "\f48e"; } -.fa-bone::before { - content: "\f5d7"; } +.fa-cloud-sun { + --fa: "\f6c4"; } -.fa-user-injured::before { - content: "\f728"; } +.fa-stopwatch-20 { + --fa: "\e06f"; } -.fa-face-sad-tear::before { - content: "\f5b4"; } +.fa-square-full { + --fa: "\f45c"; } -.fa-sad-tear::before { - content: "\f5b4"; } +.fa-magnet { + --fa: "\f076"; } -.fa-plane::before { - content: "\f072"; } +.fa-jar { + --fa: "\e516"; } -.fa-tent-arrows-down::before { - content: "\e581"; } +.fa-note-sticky { + --fa: "\f249"; } -.fa-exclamation::before { - content: "\21"; } +.fa-sticky-note { + --fa: "\f249"; } -.fa-arrows-spin::before { - content: "\e4bb"; } +.fa-bug-slash { + --fa: "\e490"; } -.fa-print::before { - content: "\f02f"; } +.fa-arrow-up-from-water-pump { + --fa: "\e4b6"; } -.fa-turkish-lira-sign::before { - content: "\e2bb"; } +.fa-bone { + --fa: "\f5d7"; } -.fa-try::before { - content: "\e2bb"; } +.fa-table-cells-row-unlock { + --fa: "\e691"; } -.fa-turkish-lira::before { - content: "\e2bb"; } +.fa-user-injured { + --fa: "\f728"; } -.fa-dollar-sign::before { - content: "\24"; } +.fa-face-sad-tear { + --fa: "\f5b4"; } -.fa-dollar::before { - content: "\24"; } +.fa-sad-tear { + --fa: "\f5b4"; } -.fa-usd::before { - content: "\24"; } +.fa-plane { + --fa: "\f072"; } -.fa-x::before { - content: "\58"; } +.fa-tent-arrows-down { + --fa: "\e581"; } -.fa-magnifying-glass-dollar::before { - content: "\f688"; } +.fa-exclamation { + --fa: "\21"; } -.fa-search-dollar::before { - content: "\f688"; } +.fa-arrows-spin { + --fa: "\e4bb"; } -.fa-users-gear::before { - content: "\f509"; } +.fa-print { + --fa: "\f02f"; } -.fa-users-cog::before { - content: "\f509"; } +.fa-turkish-lira-sign { + --fa: "\e2bb"; } -.fa-person-military-pointing::before { - content: "\e54a"; } +.fa-try { + --fa: "\e2bb"; } -.fa-building-columns::before { - content: "\f19c"; } +.fa-turkish-lira { + --fa: "\e2bb"; } -.fa-bank::before { - content: "\f19c"; } +.fa-dollar-sign { + --fa: "\24"; } -.fa-institution::before { - content: "\f19c"; } +.fa-dollar { + --fa: "\24"; } -.fa-museum::before { - content: "\f19c"; } +.fa-usd { + --fa: "\24"; } -.fa-university::before { - content: "\f19c"; } +.fa-x { + --fa: "\58"; } -.fa-umbrella::before { - content: "\f0e9"; } +.fa-magnifying-glass-dollar { + --fa: "\f688"; } -.fa-trowel::before { - content: "\e589"; } +.fa-search-dollar { + --fa: "\f688"; } -.fa-d::before { - content: "\44"; } +.fa-users-gear { + --fa: "\f509"; } -.fa-stapler::before { - content: "\e5af"; } +.fa-users-cog { + --fa: "\f509"; } -.fa-masks-theater::before { - content: "\f630"; } +.fa-person-military-pointing { + --fa: "\e54a"; } -.fa-theater-masks::before { - content: "\f630"; } +.fa-building-columns { + --fa: "\f19c"; } -.fa-kip-sign::before { - content: "\e1c4"; } +.fa-bank { + --fa: "\f19c"; } -.fa-hand-point-left::before { - content: "\f0a5"; } +.fa-institution { + --fa: "\f19c"; } -.fa-handshake-simple::before { - content: "\f4c6"; } +.fa-museum { + --fa: "\f19c"; } -.fa-handshake-alt::before { - content: "\f4c6"; } +.fa-university { + --fa: "\f19c"; } -.fa-jet-fighter::before { - content: "\f0fb"; } +.fa-umbrella { + --fa: "\f0e9"; } -.fa-fighter-jet::before { - content: "\f0fb"; } +.fa-trowel { + --fa: "\e589"; } -.fa-square-share-nodes::before { - content: "\f1e1"; } +.fa-d { + --fa: "\44"; } -.fa-share-alt-square::before { - content: "\f1e1"; } +.fa-stapler { + --fa: "\e5af"; } -.fa-barcode::before { - content: "\f02a"; } +.fa-masks-theater { + --fa: "\f630"; } -.fa-plus-minus::before { - content: "\e43c"; } +.fa-theater-masks { + --fa: "\f630"; } -.fa-video::before { - content: "\f03d"; } +.fa-kip-sign { + --fa: "\e1c4"; } -.fa-video-camera::before { - content: "\f03d"; } +.fa-hand-point-left { + --fa: "\f0a5"; } -.fa-graduation-cap::before { - content: "\f19d"; } +.fa-handshake-simple { + --fa: "\f4c6"; } -.fa-mortar-board::before { - content: "\f19d"; } +.fa-handshake-alt { + --fa: "\f4c6"; } -.fa-hand-holding-medical::before { - content: "\e05c"; } +.fa-jet-fighter { + --fa: "\f0fb"; } -.fa-person-circle-check::before { - content: "\e53e"; } +.fa-fighter-jet { + --fa: "\f0fb"; } -.fa-turn-up::before { - content: "\f3bf"; } +.fa-square-share-nodes { + --fa: "\f1e1"; } -.fa-level-up-alt::before { - content: "\f3bf"; } +.fa-share-alt-square { + --fa: "\f1e1"; } + +.fa-barcode { + --fa: "\f02a"; } + +.fa-plus-minus { + --fa: "\e43c"; } + +.fa-video { + --fa: "\f03d"; } + +.fa-video-camera { + --fa: "\f03d"; } + +.fa-graduation-cap { + --fa: "\f19d"; } + +.fa-mortar-board { + --fa: "\f19d"; } + +.fa-hand-holding-medical { + --fa: "\e05c"; } + +.fa-person-circle-check { + --fa: "\e53e"; } + +.fa-turn-up { + --fa: "\f3bf"; } + +.fa-level-up-alt { + --fa: "\f3bf"; } .sr-only, .fa-sr-only { @@ -13222,1580 +13090,1595 @@ readers do not read off random characters that represent icons */ .fa-brands { font-weight: 400; } -.fa-monero:before { - content: "\f3d0"; } +.fa-monero { + --fa: "\f3d0"; } -.fa-hooli:before { - content: "\f427"; } +.fa-hooli { + --fa: "\f427"; } -.fa-yelp:before { - content: "\f1e9"; } +.fa-yelp { + --fa: "\f1e9"; } -.fa-cc-visa:before { - content: "\f1f0"; } +.fa-cc-visa { + --fa: "\f1f0"; } -.fa-lastfm:before { - content: "\f202"; } +.fa-lastfm { + --fa: "\f202"; } -.fa-shopware:before { - content: "\f5b5"; } +.fa-shopware { + --fa: "\f5b5"; } -.fa-creative-commons-nc:before { - content: "\f4e8"; } +.fa-creative-commons-nc { + --fa: "\f4e8"; } -.fa-aws:before { - content: "\f375"; } +.fa-aws { + --fa: "\f375"; } -.fa-redhat:before { - content: "\f7bc"; } +.fa-redhat { + --fa: "\f7bc"; } -.fa-yoast:before { - content: "\f2b1"; } +.fa-yoast { + --fa: "\f2b1"; } -.fa-cloudflare:before { - content: "\e07d"; } +.fa-cloudflare { + --fa: "\e07d"; } -.fa-ups:before { - content: "\f7e0"; } +.fa-ups { + --fa: "\f7e0"; } -.fa-pixiv:before { - content: "\e640"; } +.fa-pixiv { + --fa: "\e640"; } -.fa-wpexplorer:before { - content: "\f2de"; } +.fa-wpexplorer { + --fa: "\f2de"; } -.fa-dyalog:before { - content: "\f399"; } +.fa-dyalog { + --fa: "\f399"; } -.fa-bity:before { - content: "\f37a"; } +.fa-bity { + --fa: "\f37a"; } -.fa-stackpath:before { - content: "\f842"; } +.fa-stackpath { + --fa: "\f842"; } -.fa-buysellads:before { - content: "\f20d"; } +.fa-buysellads { + --fa: "\f20d"; } -.fa-first-order:before { - content: "\f2b0"; } +.fa-first-order { + --fa: "\f2b0"; } -.fa-modx:before { - content: "\f285"; } +.fa-modx { + --fa: "\f285"; } -.fa-guilded:before { - content: "\e07e"; } +.fa-guilded { + --fa: "\e07e"; } -.fa-vnv:before { - content: "\f40b"; } +.fa-vnv { + --fa: "\f40b"; } -.fa-square-js:before { - content: "\f3b9"; } +.fa-square-js { + --fa: "\f3b9"; } -.fa-js-square:before { - content: "\f3b9"; } +.fa-js-square { + --fa: "\f3b9"; } -.fa-microsoft:before { - content: "\f3ca"; } +.fa-microsoft { + --fa: "\f3ca"; } -.fa-qq:before { - content: "\f1d6"; } +.fa-qq { + --fa: "\f1d6"; } -.fa-orcid:before { - content: "\f8d2"; } +.fa-orcid { + --fa: "\f8d2"; } -.fa-java:before { - content: "\f4e4"; } +.fa-java { + --fa: "\f4e4"; } -.fa-invision:before { - content: "\f7b0"; } +.fa-invision { + --fa: "\f7b0"; } -.fa-creative-commons-pd-alt:before { - content: "\f4ed"; } +.fa-creative-commons-pd-alt { + --fa: "\f4ed"; } -.fa-centercode:before { - content: "\f380"; } +.fa-centercode { + --fa: "\f380"; } -.fa-glide-g:before { - content: "\f2a6"; } +.fa-glide-g { + --fa: "\f2a6"; } -.fa-drupal:before { - content: "\f1a9"; } +.fa-drupal { + --fa: "\f1a9"; } -.fa-jxl:before { - content: "\e67b"; } +.fa-jxl { + --fa: "\e67b"; } -.fa-hire-a-helper:before { - content: "\f3b0"; } +.fa-dart-lang { + --fa: "\e693"; } -.fa-creative-commons-by:before { - content: "\f4e7"; } +.fa-hire-a-helper { + --fa: "\f3b0"; } -.fa-unity:before { - content: "\e049"; } +.fa-creative-commons-by { + --fa: "\f4e7"; } -.fa-whmcs:before { - content: "\f40d"; } +.fa-unity { + --fa: "\e049"; } -.fa-rocketchat:before { - content: "\f3e8"; } +.fa-whmcs { + --fa: "\f40d"; } -.fa-vk:before { - content: "\f189"; } +.fa-rocketchat { + --fa: "\f3e8"; } -.fa-untappd:before { - content: "\f405"; } +.fa-vk { + --fa: "\f189"; } -.fa-mailchimp:before { - content: "\f59e"; } +.fa-untappd { + --fa: "\f405"; } -.fa-css3-alt:before { - content: "\f38b"; } +.fa-mailchimp { + --fa: "\f59e"; } -.fa-square-reddit:before { - content: "\f1a2"; } +.fa-css3-alt { + --fa: "\f38b"; } -.fa-reddit-square:before { - content: "\f1a2"; } +.fa-square-reddit { + --fa: "\f1a2"; } -.fa-vimeo-v:before { - content: "\f27d"; } +.fa-reddit-square { + --fa: "\f1a2"; } -.fa-contao:before { - content: "\f26d"; } +.fa-vimeo-v { + --fa: "\f27d"; } -.fa-square-font-awesome:before { - content: "\e5ad"; } +.fa-contao { + --fa: "\f26d"; } -.fa-deskpro:before { - content: "\f38f"; } +.fa-square-font-awesome { + --fa: "\e5ad"; } -.fa-brave:before { - content: "\e63c"; } +.fa-deskpro { + --fa: "\f38f"; } -.fa-sistrix:before { - content: "\f3ee"; } +.fa-brave { + --fa: "\e63c"; } -.fa-square-instagram:before { - content: "\e055"; } +.fa-sistrix { + --fa: "\f3ee"; } -.fa-instagram-square:before { - content: "\e055"; } +.fa-square-instagram { + --fa: "\e055"; } -.fa-battle-net:before { - content: "\f835"; } +.fa-instagram-square { + --fa: "\e055"; } -.fa-the-red-yeti:before { - content: "\f69d"; } +.fa-battle-net { + --fa: "\f835"; } -.fa-square-hacker-news:before { - content: "\f3af"; } +.fa-the-red-yeti { + --fa: "\f69d"; } -.fa-hacker-news-square:before { - content: "\f3af"; } +.fa-square-hacker-news { + --fa: "\f3af"; } -.fa-edge:before { - content: "\f282"; } +.fa-hacker-news-square { + --fa: "\f3af"; } -.fa-threads:before { - content: "\e618"; } +.fa-edge { + --fa: "\f282"; } -.fa-napster:before { - content: "\f3d2"; } +.fa-threads { + --fa: "\e618"; } -.fa-square-snapchat:before { - content: "\f2ad"; } +.fa-napster { + --fa: "\f3d2"; } -.fa-snapchat-square:before { - content: "\f2ad"; } +.fa-square-snapchat { + --fa: "\f2ad"; } -.fa-google-plus-g:before { - content: "\f0d5"; } +.fa-snapchat-square { + --fa: "\f2ad"; } -.fa-artstation:before { - content: "\f77a"; } +.fa-google-plus-g { + --fa: "\f0d5"; } -.fa-markdown:before { - content: "\f60f"; } +.fa-artstation { + --fa: "\f77a"; } -.fa-sourcetree:before { - content: "\f7d3"; } +.fa-markdown { + --fa: "\f60f"; } -.fa-google-plus:before { - content: "\f2b3"; } +.fa-sourcetree { + --fa: "\f7d3"; } -.fa-diaspora:before { - content: "\f791"; } +.fa-google-plus { + --fa: "\f2b3"; } -.fa-foursquare:before { - content: "\f180"; } +.fa-diaspora { + --fa: "\f791"; } -.fa-stack-overflow:before { - content: "\f16c"; } +.fa-foursquare { + --fa: "\f180"; } -.fa-github-alt:before { - content: "\f113"; } +.fa-stack-overflow { + --fa: "\f16c"; } -.fa-phoenix-squadron:before { - content: "\f511"; } +.fa-github-alt { + --fa: "\f113"; } -.fa-pagelines:before { - content: "\f18c"; } +.fa-phoenix-squadron { + --fa: "\f511"; } -.fa-algolia:before { - content: "\f36c"; } +.fa-pagelines { + --fa: "\f18c"; } -.fa-red-river:before { - content: "\f3e3"; } +.fa-algolia { + --fa: "\f36c"; } -.fa-creative-commons-sa:before { - content: "\f4ef"; } +.fa-red-river { + --fa: "\f3e3"; } -.fa-safari:before { - content: "\f267"; } +.fa-creative-commons-sa { + --fa: "\f4ef"; } -.fa-google:before { - content: "\f1a0"; } +.fa-safari { + --fa: "\f267"; } -.fa-square-font-awesome-stroke:before { - content: "\f35c"; } +.fa-google { + --fa: "\f1a0"; } -.fa-font-awesome-alt:before { - content: "\f35c"; } +.fa-square-font-awesome-stroke { + --fa: "\f35c"; } -.fa-atlassian:before { - content: "\f77b"; } +.fa-font-awesome-alt { + --fa: "\f35c"; } -.fa-linkedin-in:before { - content: "\f0e1"; } +.fa-atlassian { + --fa: "\f77b"; } -.fa-digital-ocean:before { - content: "\f391"; } +.fa-linkedin-in { + --fa: "\f0e1"; } -.fa-nimblr:before { - content: "\f5a8"; } +.fa-digital-ocean { + --fa: "\f391"; } -.fa-chromecast:before { - content: "\f838"; } +.fa-nimblr { + --fa: "\f5a8"; } -.fa-evernote:before { - content: "\f839"; } +.fa-chromecast { + --fa: "\f838"; } -.fa-hacker-news:before { - content: "\f1d4"; } +.fa-evernote { + --fa: "\f839"; } -.fa-creative-commons-sampling:before { - content: "\f4f0"; } +.fa-hacker-news { + --fa: "\f1d4"; } -.fa-adversal:before { - content: "\f36a"; } +.fa-creative-commons-sampling { + --fa: "\f4f0"; } -.fa-creative-commons:before { - content: "\f25e"; } +.fa-adversal { + --fa: "\f36a"; } -.fa-watchman-monitoring:before { - content: "\e087"; } +.fa-creative-commons { + --fa: "\f25e"; } -.fa-fonticons:before { - content: "\f280"; } +.fa-watchman-monitoring { + --fa: "\e087"; } -.fa-weixin:before { - content: "\f1d7"; } +.fa-fonticons { + --fa: "\f280"; } -.fa-shirtsinbulk:before { - content: "\f214"; } +.fa-weixin { + --fa: "\f1d7"; } -.fa-codepen:before { - content: "\f1cb"; } +.fa-shirtsinbulk { + --fa: "\f214"; } -.fa-git-alt:before { - content: "\f841"; } +.fa-codepen { + --fa: "\f1cb"; } -.fa-lyft:before { - content: "\f3c3"; } +.fa-git-alt { + --fa: "\f841"; } -.fa-rev:before { - content: "\f5b2"; } +.fa-lyft { + --fa: "\f3c3"; } -.fa-windows:before { - content: "\f17a"; } +.fa-rev { + --fa: "\f5b2"; } -.fa-wizards-of-the-coast:before { - content: "\f730"; } +.fa-windows { + --fa: "\f17a"; } -.fa-square-viadeo:before { - content: "\f2aa"; } +.fa-wizards-of-the-coast { + --fa: "\f730"; } -.fa-viadeo-square:before { - content: "\f2aa"; } +.fa-square-viadeo { + --fa: "\f2aa"; } -.fa-meetup:before { - content: "\f2e0"; } +.fa-viadeo-square { + --fa: "\f2aa"; } -.fa-centos:before { - content: "\f789"; } +.fa-meetup { + --fa: "\f2e0"; } -.fa-adn:before { - content: "\f170"; } +.fa-centos { + --fa: "\f789"; } -.fa-cloudsmith:before { - content: "\f384"; } +.fa-adn { + --fa: "\f170"; } -.fa-opensuse:before { - content: "\e62b"; } +.fa-cloudsmith { + --fa: "\f384"; } -.fa-pied-piper-alt:before { - content: "\f1a8"; } +.fa-opensuse { + --fa: "\e62b"; } -.fa-square-dribbble:before { - content: "\f397"; } +.fa-pied-piper-alt { + --fa: "\f1a8"; } -.fa-dribbble-square:before { - content: "\f397"; } +.fa-square-dribbble { + --fa: "\f397"; } -.fa-codiepie:before { - content: "\f284"; } +.fa-dribbble-square { + --fa: "\f397"; } -.fa-node:before { - content: "\f419"; } +.fa-codiepie { + --fa: "\f284"; } -.fa-mix:before { - content: "\f3cb"; } +.fa-node { + --fa: "\f419"; } -.fa-steam:before { - content: "\f1b6"; } +.fa-mix { + --fa: "\f3cb"; } -.fa-cc-apple-pay:before { - content: "\f416"; } +.fa-steam { + --fa: "\f1b6"; } -.fa-scribd:before { - content: "\f28a"; } +.fa-cc-apple-pay { + --fa: "\f416"; } -.fa-debian:before { - content: "\e60b"; } +.fa-scribd { + --fa: "\f28a"; } -.fa-openid:before { - content: "\f19b"; } +.fa-debian { + --fa: "\e60b"; } -.fa-instalod:before { - content: "\e081"; } +.fa-openid { + --fa: "\f19b"; } -.fa-expeditedssl:before { - content: "\f23e"; } +.fa-instalod { + --fa: "\e081"; } -.fa-sellcast:before { - content: "\f2da"; } +.fa-files-pinwheel { + --fa: "\e69f"; } -.fa-square-twitter:before { - content: "\f081"; } +.fa-expeditedssl { + --fa: "\f23e"; } -.fa-twitter-square:before { - content: "\f081"; } +.fa-sellcast { + --fa: "\f2da"; } -.fa-r-project:before { - content: "\f4f7"; } +.fa-square-twitter { + --fa: "\f081"; } -.fa-delicious:before { - content: "\f1a5"; } +.fa-twitter-square { + --fa: "\f081"; } -.fa-freebsd:before { - content: "\f3a4"; } +.fa-r-project { + --fa: "\f4f7"; } -.fa-vuejs:before { - content: "\f41f"; } +.fa-delicious { + --fa: "\f1a5"; } -.fa-accusoft:before { - content: "\f369"; } +.fa-freebsd { + --fa: "\f3a4"; } -.fa-ioxhost:before { - content: "\f208"; } +.fa-vuejs { + --fa: "\f41f"; } -.fa-fonticons-fi:before { - content: "\f3a2"; } +.fa-accusoft { + --fa: "\f369"; } -.fa-app-store:before { - content: "\f36f"; } +.fa-ioxhost { + --fa: "\f208"; } -.fa-cc-mastercard:before { - content: "\f1f1"; } +.fa-fonticons-fi { + --fa: "\f3a2"; } -.fa-itunes-note:before { - content: "\f3b5"; } +.fa-app-store { + --fa: "\f36f"; } -.fa-golang:before { - content: "\e40f"; } +.fa-cc-mastercard { + --fa: "\f1f1"; } -.fa-kickstarter:before { - content: "\f3bb"; } +.fa-itunes-note { + --fa: "\f3b5"; } -.fa-square-kickstarter:before { - content: "\f3bb"; } +.fa-golang { + --fa: "\e40f"; } -.fa-grav:before { - content: "\f2d6"; } +.fa-kickstarter { + --fa: "\f3bb"; } -.fa-weibo:before { - content: "\f18a"; } +.fa-square-kickstarter { + --fa: "\f3bb"; } -.fa-uncharted:before { - content: "\e084"; } +.fa-grav { + --fa: "\f2d6"; } -.fa-firstdraft:before { - content: "\f3a1"; } +.fa-weibo { + --fa: "\f18a"; } -.fa-square-youtube:before { - content: "\f431"; } +.fa-uncharted { + --fa: "\e084"; } -.fa-youtube-square:before { - content: "\f431"; } +.fa-firstdraft { + --fa: "\f3a1"; } -.fa-wikipedia-w:before { - content: "\f266"; } +.fa-square-youtube { + --fa: "\f431"; } -.fa-wpressr:before { - content: "\f3e4"; } +.fa-youtube-square { + --fa: "\f431"; } -.fa-rendact:before { - content: "\f3e4"; } +.fa-wikipedia-w { + --fa: "\f266"; } -.fa-angellist:before { - content: "\f209"; } +.fa-wpressr { + --fa: "\f3e4"; } -.fa-galactic-republic:before { - content: "\f50c"; } +.fa-rendact { + --fa: "\f3e4"; } -.fa-nfc-directional:before { - content: "\e530"; } +.fa-angellist { + --fa: "\f209"; } -.fa-skype:before { - content: "\f17e"; } +.fa-galactic-republic { + --fa: "\f50c"; } -.fa-joget:before { - content: "\f3b7"; } +.fa-nfc-directional { + --fa: "\e530"; } -.fa-fedora:before { - content: "\f798"; } +.fa-skype { + --fa: "\f17e"; } -.fa-stripe-s:before { - content: "\f42a"; } +.fa-joget { + --fa: "\f3b7"; } -.fa-meta:before { - content: "\e49b"; } +.fa-fedora { + --fa: "\f798"; } -.fa-laravel:before { - content: "\f3bd"; } +.fa-stripe-s { + --fa: "\f42a"; } -.fa-hotjar:before { - content: "\f3b1"; } +.fa-meta { + --fa: "\e49b"; } -.fa-bluetooth-b:before { - content: "\f294"; } +.fa-laravel { + --fa: "\f3bd"; } -.fa-square-letterboxd:before { - content: "\e62e"; } +.fa-hotjar { + --fa: "\f3b1"; } -.fa-sticker-mule:before { - content: "\f3f7"; } +.fa-bluetooth-b { + --fa: "\f294"; } -.fa-creative-commons-zero:before { - content: "\f4f3"; } +.fa-square-letterboxd { + --fa: "\e62e"; } -.fa-hips:before { - content: "\f452"; } +.fa-sticker-mule { + --fa: "\f3f7"; } -.fa-behance:before { - content: "\f1b4"; } +.fa-creative-commons-zero { + --fa: "\f4f3"; } -.fa-reddit:before { - content: "\f1a1"; } +.fa-hips { + --fa: "\f452"; } -.fa-discord:before { - content: "\f392"; } +.fa-css { + --fa: "\e6a2"; } -.fa-chrome:before { - content: "\f268"; } +.fa-behance { + --fa: "\f1b4"; } -.fa-app-store-ios:before { - content: "\f370"; } +.fa-reddit { + --fa: "\f1a1"; } -.fa-cc-discover:before { - content: "\f1f2"; } +.fa-discord { + --fa: "\f392"; } -.fa-wpbeginner:before { - content: "\f297"; } +.fa-chrome { + --fa: "\f268"; } -.fa-confluence:before { - content: "\f78d"; } +.fa-app-store-ios { + --fa: "\f370"; } -.fa-shoelace:before { - content: "\e60c"; } +.fa-cc-discover { + --fa: "\f1f2"; } -.fa-mdb:before { - content: "\f8ca"; } +.fa-wpbeginner { + --fa: "\f297"; } -.fa-dochub:before { - content: "\f394"; } +.fa-confluence { + --fa: "\f78d"; } -.fa-accessible-icon:before { - content: "\f368"; } +.fa-shoelace { + --fa: "\e60c"; } -.fa-ebay:before { - content: "\f4f4"; } +.fa-mdb { + --fa: "\f8ca"; } -.fa-amazon:before { - content: "\f270"; } +.fa-dochub { + --fa: "\f394"; } -.fa-unsplash:before { - content: "\e07c"; } +.fa-accessible-icon { + --fa: "\f368"; } -.fa-yarn:before { - content: "\f7e3"; } +.fa-ebay { + --fa: "\f4f4"; } -.fa-square-steam:before { - content: "\f1b7"; } +.fa-amazon { + --fa: "\f270"; } -.fa-steam-square:before { - content: "\f1b7"; } +.fa-unsplash { + --fa: "\e07c"; } -.fa-500px:before { - content: "\f26e"; } +.fa-yarn { + --fa: "\f7e3"; } -.fa-square-vimeo:before { - content: "\f194"; } +.fa-square-steam { + --fa: "\f1b7"; } -.fa-vimeo-square:before { - content: "\f194"; } +.fa-steam-square { + --fa: "\f1b7"; } -.fa-asymmetrik:before { - content: "\f372"; } +.fa-500px { + --fa: "\f26e"; } -.fa-font-awesome:before { - content: "\f2b4"; } +.fa-square-vimeo { + --fa: "\f194"; } -.fa-font-awesome-flag:before { - content: "\f2b4"; } +.fa-vimeo-square { + --fa: "\f194"; } -.fa-font-awesome-logo-full:before { - content: "\f2b4"; } +.fa-asymmetrik { + --fa: "\f372"; } -.fa-gratipay:before { - content: "\f184"; } +.fa-font-awesome { + --fa: "\f2b4"; } -.fa-apple:before { - content: "\f179"; } +.fa-font-awesome-flag { + --fa: "\f2b4"; } -.fa-hive:before { - content: "\e07f"; } +.fa-font-awesome-logo-full { + --fa: "\f2b4"; } -.fa-gitkraken:before { - content: "\f3a6"; } +.fa-gratipay { + --fa: "\f184"; } -.fa-keybase:before { - content: "\f4f5"; } +.fa-apple { + --fa: "\f179"; } -.fa-apple-pay:before { - content: "\f415"; } +.fa-hive { + --fa: "\e07f"; } -.fa-padlet:before { - content: "\e4a0"; } +.fa-gitkraken { + --fa: "\f3a6"; } -.fa-amazon-pay:before { - content: "\f42c"; } +.fa-keybase { + --fa: "\f4f5"; } -.fa-square-github:before { - content: "\f092"; } +.fa-apple-pay { + --fa: "\f415"; } -.fa-github-square:before { - content: "\f092"; } +.fa-padlet { + --fa: "\e4a0"; } -.fa-stumbleupon:before { - content: "\f1a4"; } +.fa-amazon-pay { + --fa: "\f42c"; } -.fa-fedex:before { - content: "\f797"; } +.fa-square-github { + --fa: "\f092"; } -.fa-phoenix-framework:before { - content: "\f3dc"; } +.fa-github-square { + --fa: "\f092"; } -.fa-shopify:before { - content: "\e057"; } +.fa-stumbleupon { + --fa: "\f1a4"; } -.fa-neos:before { - content: "\f612"; } +.fa-fedex { + --fa: "\f797"; } -.fa-square-threads:before { - content: "\e619"; } +.fa-phoenix-framework { + --fa: "\f3dc"; } -.fa-hackerrank:before { - content: "\f5f7"; } +.fa-shopify { + --fa: "\e057"; } -.fa-researchgate:before { - content: "\f4f8"; } +.fa-neos { + --fa: "\f612"; } -.fa-swift:before { - content: "\f8e1"; } +.fa-square-threads { + --fa: "\e619"; } -.fa-angular:before { - content: "\f420"; } +.fa-hackerrank { + --fa: "\f5f7"; } -.fa-speakap:before { - content: "\f3f3"; } +.fa-researchgate { + --fa: "\f4f8"; } -.fa-angrycreative:before { - content: "\f36e"; } +.fa-swift { + --fa: "\f8e1"; } -.fa-y-combinator:before { - content: "\f23b"; } +.fa-angular { + --fa: "\f420"; } -.fa-empire:before { - content: "\f1d1"; } +.fa-speakap { + --fa: "\f3f3"; } -.fa-envira:before { - content: "\f299"; } +.fa-angrycreative { + --fa: "\f36e"; } -.fa-google-scholar:before { - content: "\e63b"; } +.fa-y-combinator { + --fa: "\f23b"; } -.fa-square-gitlab:before { - content: "\e5ae"; } +.fa-empire { + --fa: "\f1d1"; } -.fa-gitlab-square:before { - content: "\e5ae"; } +.fa-envira { + --fa: "\f299"; } -.fa-studiovinari:before { - content: "\f3f8"; } +.fa-google-scholar { + --fa: "\e63b"; } -.fa-pied-piper:before { - content: "\f2ae"; } +.fa-square-gitlab { + --fa: "\e5ae"; } -.fa-wordpress:before { - content: "\f19a"; } +.fa-gitlab-square { + --fa: "\e5ae"; } -.fa-product-hunt:before { - content: "\f288"; } +.fa-studiovinari { + --fa: "\f3f8"; } -.fa-firefox:before { - content: "\f269"; } +.fa-pied-piper { + --fa: "\f2ae"; } -.fa-linode:before { - content: "\f2b8"; } +.fa-wordpress { + --fa: "\f19a"; } -.fa-goodreads:before { - content: "\f3a8"; } +.fa-product-hunt { + --fa: "\f288"; } -.fa-square-odnoklassniki:before { - content: "\f264"; } +.fa-firefox { + --fa: "\f269"; } -.fa-odnoklassniki-square:before { - content: "\f264"; } +.fa-linode { + --fa: "\f2b8"; } -.fa-jsfiddle:before { - content: "\f1cc"; } +.fa-goodreads { + --fa: "\f3a8"; } -.fa-sith:before { - content: "\f512"; } +.fa-square-odnoklassniki { + --fa: "\f264"; } -.fa-themeisle:before { - content: "\f2b2"; } +.fa-odnoklassniki-square { + --fa: "\f264"; } -.fa-page4:before { - content: "\f3d7"; } +.fa-jsfiddle { + --fa: "\f1cc"; } -.fa-hashnode:before { - content: "\e499"; } +.fa-sith { + --fa: "\f512"; } -.fa-react:before { - content: "\f41b"; } +.fa-themeisle { + --fa: "\f2b2"; } -.fa-cc-paypal:before { - content: "\f1f4"; } +.fa-page4 { + --fa: "\f3d7"; } -.fa-squarespace:before { - content: "\f5be"; } +.fa-hashnode { + --fa: "\e499"; } -.fa-cc-stripe:before { - content: "\f1f5"; } +.fa-react { + --fa: "\f41b"; } -.fa-creative-commons-share:before { - content: "\f4f2"; } +.fa-cc-paypal { + --fa: "\f1f4"; } -.fa-bitcoin:before { - content: "\f379"; } +.fa-squarespace { + --fa: "\f5be"; } -.fa-keycdn:before { - content: "\f3ba"; } +.fa-cc-stripe { + --fa: "\f1f5"; } -.fa-opera:before { - content: "\f26a"; } +.fa-creative-commons-share { + --fa: "\f4f2"; } -.fa-itch-io:before { - content: "\f83a"; } +.fa-bitcoin { + --fa: "\f379"; } -.fa-umbraco:before { - content: "\f8e8"; } +.fa-keycdn { + --fa: "\f3ba"; } -.fa-galactic-senate:before { - content: "\f50d"; } +.fa-opera { + --fa: "\f26a"; } -.fa-ubuntu:before { - content: "\f7df"; } +.fa-itch-io { + --fa: "\f83a"; } -.fa-draft2digital:before { - content: "\f396"; } +.fa-umbraco { + --fa: "\f8e8"; } -.fa-stripe:before { - content: "\f429"; } +.fa-galactic-senate { + --fa: "\f50d"; } -.fa-houzz:before { - content: "\f27c"; } +.fa-ubuntu { + --fa: "\f7df"; } -.fa-gg:before { - content: "\f260"; } +.fa-draft2digital { + --fa: "\f396"; } -.fa-dhl:before { - content: "\f790"; } +.fa-stripe { + --fa: "\f429"; } -.fa-square-pinterest:before { - content: "\f0d3"; } +.fa-houzz { + --fa: "\f27c"; } -.fa-pinterest-square:before { - content: "\f0d3"; } +.fa-gg { + --fa: "\f260"; } -.fa-xing:before { - content: "\f168"; } +.fa-dhl { + --fa: "\f790"; } -.fa-blackberry:before { - content: "\f37b"; } +.fa-square-pinterest { + --fa: "\f0d3"; } -.fa-creative-commons-pd:before { - content: "\f4ec"; } +.fa-pinterest-square { + --fa: "\f0d3"; } -.fa-playstation:before { - content: "\f3df"; } +.fa-xing { + --fa: "\f168"; } -.fa-quinscape:before { - content: "\f459"; } +.fa-blackberry { + --fa: "\f37b"; } -.fa-less:before { - content: "\f41d"; } +.fa-creative-commons-pd { + --fa: "\f4ec"; } -.fa-blogger-b:before { - content: "\f37d"; } +.fa-playstation { + --fa: "\f3df"; } -.fa-opencart:before { - content: "\f23d"; } +.fa-quinscape { + --fa: "\f459"; } -.fa-vine:before { - content: "\f1ca"; } +.fa-less { + --fa: "\f41d"; } -.fa-signal-messenger:before { - content: "\e663"; } +.fa-blogger-b { + --fa: "\f37d"; } -.fa-paypal:before { - content: "\f1ed"; } +.fa-opencart { + --fa: "\f23d"; } -.fa-gitlab:before { - content: "\f296"; } +.fa-vine { + --fa: "\f1ca"; } -.fa-typo3:before { - content: "\f42b"; } +.fa-signal-messenger { + --fa: "\e663"; } -.fa-reddit-alien:before { - content: "\f281"; } +.fa-paypal { + --fa: "\f1ed"; } -.fa-yahoo:before { - content: "\f19e"; } +.fa-gitlab { + --fa: "\f296"; } -.fa-dailymotion:before { - content: "\e052"; } +.fa-typo3 { + --fa: "\f42b"; } -.fa-affiliatetheme:before { - content: "\f36b"; } +.fa-reddit-alien { + --fa: "\f281"; } -.fa-pied-piper-pp:before { - content: "\f1a7"; } +.fa-yahoo { + --fa: "\f19e"; } -.fa-bootstrap:before { - content: "\f836"; } +.fa-dailymotion { + --fa: "\e052"; } -.fa-odnoklassniki:before { - content: "\f263"; } +.fa-affiliatetheme { + --fa: "\f36b"; } -.fa-nfc-symbol:before { - content: "\e531"; } +.fa-pied-piper-pp { + --fa: "\f1a7"; } -.fa-mintbit:before { - content: "\e62f"; } +.fa-bootstrap { + --fa: "\f836"; } -.fa-ethereum:before { - content: "\f42e"; } +.fa-odnoklassniki { + --fa: "\f263"; } -.fa-speaker-deck:before { - content: "\f83c"; } +.fa-nfc-symbol { + --fa: "\e531"; } -.fa-creative-commons-nc-eu:before { - content: "\f4e9"; } +.fa-mintbit { + --fa: "\e62f"; } -.fa-patreon:before { - content: "\f3d9"; } +.fa-ethereum { + --fa: "\f42e"; } -.fa-avianex:before { - content: "\f374"; } +.fa-speaker-deck { + --fa: "\f83c"; } -.fa-ello:before { - content: "\f5f1"; } +.fa-creative-commons-nc-eu { + --fa: "\f4e9"; } -.fa-gofore:before { - content: "\f3a7"; } +.fa-patreon { + --fa: "\f3d9"; } -.fa-bimobject:before { - content: "\f378"; } +.fa-avianex { + --fa: "\f374"; } -.fa-brave-reverse:before { - content: "\e63d"; } +.fa-ello { + --fa: "\f5f1"; } -.fa-facebook-f:before { - content: "\f39e"; } +.fa-gofore { + --fa: "\f3a7"; } -.fa-square-google-plus:before { - content: "\f0d4"; } +.fa-bimobject { + --fa: "\f378"; } -.fa-google-plus-square:before { - content: "\f0d4"; } +.fa-brave-reverse { + --fa: "\e63d"; } -.fa-web-awesome:before { - content: "\e682"; } +.fa-facebook-f { + --fa: "\f39e"; } -.fa-mandalorian:before { - content: "\f50f"; } +.fa-square-google-plus { + --fa: "\f0d4"; } -.fa-first-order-alt:before { - content: "\f50a"; } +.fa-google-plus-square { + --fa: "\f0d4"; } -.fa-osi:before { - content: "\f41a"; } +.fa-web-awesome { + --fa: "\e682"; } -.fa-google-wallet:before { - content: "\f1ee"; } +.fa-mandalorian { + --fa: "\f50f"; } -.fa-d-and-d-beyond:before { - content: "\f6ca"; } +.fa-first-order-alt { + --fa: "\f50a"; } -.fa-periscope:before { - content: "\f3da"; } +.fa-osi { + --fa: "\f41a"; } -.fa-fulcrum:before { - content: "\f50b"; } +.fa-google-wallet { + --fa: "\f1ee"; } -.fa-cloudscale:before { - content: "\f383"; } +.fa-d-and-d-beyond { + --fa: "\f6ca"; } -.fa-forumbee:before { - content: "\f211"; } +.fa-periscope { + --fa: "\f3da"; } -.fa-mizuni:before { - content: "\f3cc"; } +.fa-fulcrum { + --fa: "\f50b"; } -.fa-schlix:before { - content: "\f3ea"; } +.fa-cloudscale { + --fa: "\f383"; } -.fa-square-xing:before { - content: "\f169"; } +.fa-forumbee { + --fa: "\f211"; } -.fa-xing-square:before { - content: "\f169"; } +.fa-mizuni { + --fa: "\f3cc"; } -.fa-bandcamp:before { - content: "\f2d5"; } +.fa-schlix { + --fa: "\f3ea"; } -.fa-wpforms:before { - content: "\f298"; } +.fa-square-xing { + --fa: "\f169"; } -.fa-cloudversify:before { - content: "\f385"; } +.fa-xing-square { + --fa: "\f169"; } -.fa-usps:before { - content: "\f7e1"; } +.fa-bandcamp { + --fa: "\f2d5"; } -.fa-megaport:before { - content: "\f5a3"; } +.fa-wpforms { + --fa: "\f298"; } -.fa-magento:before { - content: "\f3c4"; } +.fa-cloudversify { + --fa: "\f385"; } -.fa-spotify:before { - content: "\f1bc"; } +.fa-usps { + --fa: "\f7e1"; } -.fa-optin-monster:before { - content: "\f23c"; } +.fa-megaport { + --fa: "\f5a3"; } -.fa-fly:before { - content: "\f417"; } +.fa-magento { + --fa: "\f3c4"; } -.fa-aviato:before { - content: "\f421"; } +.fa-spotify { + --fa: "\f1bc"; } -.fa-itunes:before { - content: "\f3b4"; } +.fa-optin-monster { + --fa: "\f23c"; } -.fa-cuttlefish:before { - content: "\f38c"; } +.fa-fly { + --fa: "\f417"; } -.fa-blogger:before { - content: "\f37c"; } +.fa-square-bluesky { + --fa: "\e6a3"; } -.fa-flickr:before { - content: "\f16e"; } +.fa-aviato { + --fa: "\f421"; } -.fa-viber:before { - content: "\f409"; } +.fa-itunes { + --fa: "\f3b4"; } -.fa-soundcloud:before { - content: "\f1be"; } +.fa-cuttlefish { + --fa: "\f38c"; } -.fa-digg:before { - content: "\f1a6"; } +.fa-blogger { + --fa: "\f37c"; } -.fa-tencent-weibo:before { - content: "\f1d5"; } +.fa-flickr { + --fa: "\f16e"; } -.fa-letterboxd:before { - content: "\e62d"; } +.fa-viber { + --fa: "\f409"; } -.fa-symfony:before { - content: "\f83d"; } +.fa-soundcloud { + --fa: "\f1be"; } -.fa-maxcdn:before { - content: "\f136"; } +.fa-digg { + --fa: "\f1a6"; } -.fa-etsy:before { - content: "\f2d7"; } +.fa-tencent-weibo { + --fa: "\f1d5"; } -.fa-facebook-messenger:before { - content: "\f39f"; } +.fa-letterboxd { + --fa: "\e62d"; } -.fa-audible:before { - content: "\f373"; } +.fa-symfony { + --fa: "\f83d"; } -.fa-think-peaks:before { - content: "\f731"; } +.fa-maxcdn { + --fa: "\f136"; } -.fa-bilibili:before { - content: "\e3d9"; } +.fa-etsy { + --fa: "\f2d7"; } -.fa-erlang:before { - content: "\f39d"; } +.fa-facebook-messenger { + --fa: "\f39f"; } -.fa-x-twitter:before { - content: "\e61b"; } +.fa-audible { + --fa: "\f373"; } -.fa-cotton-bureau:before { - content: "\f89e"; } +.fa-think-peaks { + --fa: "\f731"; } -.fa-dashcube:before { - content: "\f210"; } +.fa-bilibili { + --fa: "\e3d9"; } -.fa-42-group:before { - content: "\e080"; } +.fa-erlang { + --fa: "\f39d"; } -.fa-innosoft:before { - content: "\e080"; } +.fa-x-twitter { + --fa: "\e61b"; } -.fa-stack-exchange:before { - content: "\f18d"; } +.fa-cotton-bureau { + --fa: "\f89e"; } -.fa-elementor:before { - content: "\f430"; } +.fa-dashcube { + --fa: "\f210"; } -.fa-square-pied-piper:before { - content: "\e01e"; } +.fa-42-group { + --fa: "\e080"; } -.fa-pied-piper-square:before { - content: "\e01e"; } +.fa-innosoft { + --fa: "\e080"; } -.fa-creative-commons-nd:before { - content: "\f4eb"; } +.fa-stack-exchange { + --fa: "\f18d"; } -.fa-palfed:before { - content: "\f3d8"; } +.fa-elementor { + --fa: "\f430"; } -.fa-superpowers:before { - content: "\f2dd"; } +.fa-square-pied-piper { + --fa: "\e01e"; } -.fa-resolving:before { - content: "\f3e7"; } +.fa-pied-piper-square { + --fa: "\e01e"; } -.fa-xbox:before { - content: "\f412"; } +.fa-creative-commons-nd { + --fa: "\f4eb"; } -.fa-square-web-awesome-stroke:before { - content: "\e684"; } +.fa-palfed { + --fa: "\f3d8"; } -.fa-searchengin:before { - content: "\f3eb"; } +.fa-superpowers { + --fa: "\f2dd"; } -.fa-tiktok:before { - content: "\e07b"; } +.fa-resolving { + --fa: "\f3e7"; } -.fa-square-facebook:before { - content: "\f082"; } +.fa-xbox { + --fa: "\f412"; } -.fa-facebook-square:before { - content: "\f082"; } +.fa-square-web-awesome-stroke { + --fa: "\e684"; } -.fa-renren:before { - content: "\f18b"; } +.fa-searchengin { + --fa: "\f3eb"; } -.fa-linux:before { - content: "\f17c"; } +.fa-tiktok { + --fa: "\e07b"; } -.fa-glide:before { - content: "\f2a5"; } +.fa-square-facebook { + --fa: "\f082"; } -.fa-linkedin:before { - content: "\f08c"; } +.fa-facebook-square { + --fa: "\f082"; } -.fa-hubspot:before { - content: "\f3b2"; } +.fa-renren { + --fa: "\f18b"; } -.fa-deploydog:before { - content: "\f38e"; } +.fa-linux { + --fa: "\f17c"; } -.fa-twitch:before { - content: "\f1e8"; } +.fa-glide { + --fa: "\f2a5"; } -.fa-ravelry:before { - content: "\f2d9"; } +.fa-linkedin { + --fa: "\f08c"; } -.fa-mixer:before { - content: "\e056"; } +.fa-hubspot { + --fa: "\f3b2"; } -.fa-square-lastfm:before { - content: "\f203"; } +.fa-deploydog { + --fa: "\f38e"; } -.fa-lastfm-square:before { - content: "\f203"; } +.fa-twitch { + --fa: "\f1e8"; } -.fa-vimeo:before { - content: "\f40a"; } +.fa-flutter { + --fa: "\e694"; } -.fa-mendeley:before { - content: "\f7b3"; } +.fa-ravelry { + --fa: "\f2d9"; } -.fa-uniregistry:before { - content: "\f404"; } +.fa-mixer { + --fa: "\e056"; } -.fa-figma:before { - content: "\f799"; } +.fa-square-lastfm { + --fa: "\f203"; } -.fa-creative-commons-remix:before { - content: "\f4ee"; } +.fa-lastfm-square { + --fa: "\f203"; } -.fa-cc-amazon-pay:before { - content: "\f42d"; } +.fa-vimeo { + --fa: "\f40a"; } -.fa-dropbox:before { - content: "\f16b"; } +.fa-mendeley { + --fa: "\f7b3"; } -.fa-instagram:before { - content: "\f16d"; } +.fa-uniregistry { + --fa: "\f404"; } -.fa-cmplid:before { - content: "\e360"; } +.fa-figma { + --fa: "\f799"; } -.fa-upwork:before { - content: "\e641"; } +.fa-creative-commons-remix { + --fa: "\f4ee"; } -.fa-facebook:before { - content: "\f09a"; } +.fa-cc-amazon-pay { + --fa: "\f42d"; } -.fa-gripfire:before { - content: "\f3ac"; } +.fa-dropbox { + --fa: "\f16b"; } -.fa-jedi-order:before { - content: "\f50e"; } +.fa-instagram { + --fa: "\f16d"; } -.fa-uikit:before { - content: "\f403"; } +.fa-cmplid { + --fa: "\e360"; } -.fa-fort-awesome-alt:before { - content: "\f3a3"; } +.fa-upwork { + --fa: "\e641"; } -.fa-phabricator:before { - content: "\f3db"; } +.fa-facebook { + --fa: "\f09a"; } -.fa-ussunnah:before { - content: "\f407"; } +.fa-gripfire { + --fa: "\f3ac"; } -.fa-earlybirds:before { - content: "\f39a"; } +.fa-jedi-order { + --fa: "\f50e"; } -.fa-trade-federation:before { - content: "\f513"; } +.fa-uikit { + --fa: "\f403"; } -.fa-autoprefixer:before { - content: "\f41c"; } +.fa-fort-awesome-alt { + --fa: "\f3a3"; } -.fa-whatsapp:before { - content: "\f232"; } +.fa-phabricator { + --fa: "\f3db"; } -.fa-square-upwork:before { - content: "\e67c"; } +.fa-ussunnah { + --fa: "\f407"; } -.fa-slideshare:before { - content: "\f1e7"; } +.fa-earlybirds { + --fa: "\f39a"; } -.fa-google-play:before { - content: "\f3ab"; } +.fa-trade-federation { + --fa: "\f513"; } -.fa-viadeo:before { - content: "\f2a9"; } +.fa-autoprefixer { + --fa: "\f41c"; } -.fa-line:before { - content: "\f3c0"; } +.fa-whatsapp { + --fa: "\f232"; } -.fa-google-drive:before { - content: "\f3aa"; } +.fa-square-upwork { + --fa: "\e67c"; } -.fa-servicestack:before { - content: "\f3ec"; } +.fa-slideshare { + --fa: "\f1e7"; } -.fa-simplybuilt:before { - content: "\f215"; } +.fa-google-play { + --fa: "\f3ab"; } -.fa-bitbucket:before { - content: "\f171"; } +.fa-viadeo { + --fa: "\f2a9"; } -.fa-imdb:before { - content: "\f2d8"; } +.fa-line { + --fa: "\f3c0"; } -.fa-deezer:before { - content: "\e077"; } +.fa-google-drive { + --fa: "\f3aa"; } -.fa-raspberry-pi:before { - content: "\f7bb"; } +.fa-servicestack { + --fa: "\f3ec"; } -.fa-jira:before { - content: "\f7b1"; } +.fa-simplybuilt { + --fa: "\f215"; } -.fa-docker:before { - content: "\f395"; } +.fa-bitbucket { + --fa: "\f171"; } -.fa-screenpal:before { - content: "\e570"; } +.fa-imdb { + --fa: "\f2d8"; } -.fa-bluetooth:before { - content: "\f293"; } +.fa-deezer { + --fa: "\e077"; } -.fa-gitter:before { - content: "\f426"; } +.fa-raspberry-pi { + --fa: "\f7bb"; } -.fa-d-and-d:before { - content: "\f38d"; } +.fa-jira { + --fa: "\f7b1"; } -.fa-microblog:before { - content: "\e01a"; } +.fa-docker { + --fa: "\f395"; } -.fa-cc-diners-club:before { - content: "\f24c"; } +.fa-screenpal { + --fa: "\e570"; } -.fa-gg-circle:before { - content: "\f261"; } +.fa-bluetooth { + --fa: "\f293"; } -.fa-pied-piper-hat:before { - content: "\f4e5"; } +.fa-gitter { + --fa: "\f426"; } -.fa-kickstarter-k:before { - content: "\f3bc"; } +.fa-d-and-d { + --fa: "\f38d"; } -.fa-yandex:before { - content: "\f413"; } +.fa-microblog { + --fa: "\e01a"; } -.fa-readme:before { - content: "\f4d5"; } +.fa-cc-diners-club { + --fa: "\f24c"; } -.fa-html5:before { - content: "\f13b"; } +.fa-gg-circle { + --fa: "\f261"; } -.fa-sellsy:before { - content: "\f213"; } +.fa-pied-piper-hat { + --fa: "\f4e5"; } -.fa-square-web-awesome:before { - content: "\e683"; } +.fa-kickstarter-k { + --fa: "\f3bc"; } -.fa-sass:before { - content: "\f41e"; } +.fa-yandex { + --fa: "\f413"; } -.fa-wirsindhandwerk:before { - content: "\e2d0"; } +.fa-readme { + --fa: "\f4d5"; } -.fa-wsh:before { - content: "\e2d0"; } +.fa-html5 { + --fa: "\f13b"; } -.fa-buromobelexperte:before { - content: "\f37f"; } +.fa-sellsy { + --fa: "\f213"; } -.fa-salesforce:before { - content: "\f83b"; } +.fa-square-web-awesome { + --fa: "\e683"; } -.fa-octopus-deploy:before { - content: "\e082"; } +.fa-sass { + --fa: "\f41e"; } -.fa-medapps:before { - content: "\f3c6"; } +.fa-wirsindhandwerk { + --fa: "\e2d0"; } -.fa-ns8:before { - content: "\f3d5"; } +.fa-wsh { + --fa: "\e2d0"; } -.fa-pinterest-p:before { - content: "\f231"; } +.fa-buromobelexperte { + --fa: "\f37f"; } -.fa-apper:before { - content: "\f371"; } +.fa-salesforce { + --fa: "\f83b"; } -.fa-fort-awesome:before { - content: "\f286"; } +.fa-octopus-deploy { + --fa: "\e082"; } -.fa-waze:before { - content: "\f83f"; } +.fa-medapps { + --fa: "\f3c6"; } -.fa-bluesky:before { - content: "\e671"; } +.fa-ns8 { + --fa: "\f3d5"; } -.fa-cc-jcb:before { - content: "\f24b"; } +.fa-pinterest-p { + --fa: "\f231"; } -.fa-snapchat:before { - content: "\f2ab"; } +.fa-apper { + --fa: "\f371"; } -.fa-snapchat-ghost:before { - content: "\f2ab"; } +.fa-fort-awesome { + --fa: "\f286"; } -.fa-fantasy-flight-games:before { - content: "\f6dc"; } +.fa-waze { + --fa: "\f83f"; } -.fa-rust:before { - content: "\e07a"; } +.fa-bluesky { + --fa: "\e671"; } -.fa-wix:before { - content: "\f5cf"; } +.fa-cc-jcb { + --fa: "\f24b"; } -.fa-square-behance:before { - content: "\f1b5"; } +.fa-snapchat { + --fa: "\f2ab"; } -.fa-behance-square:before { - content: "\f1b5"; } +.fa-snapchat-ghost { + --fa: "\f2ab"; } -.fa-supple:before { - content: "\f3f9"; } +.fa-fantasy-flight-games { + --fa: "\f6dc"; } -.fa-webflow:before { - content: "\e65c"; } +.fa-rust { + --fa: "\e07a"; } -.fa-rebel:before { - content: "\f1d0"; } +.fa-wix { + --fa: "\f5cf"; } -.fa-css3:before { - content: "\f13c"; } +.fa-square-behance { + --fa: "\f1b5"; } -.fa-staylinked:before { - content: "\f3f5"; } +.fa-behance-square { + --fa: "\f1b5"; } -.fa-kaggle:before { - content: "\f5fa"; } +.fa-supple { + --fa: "\f3f9"; } -.fa-space-awesome:before { - content: "\e5ac"; } +.fa-webflow { + --fa: "\e65c"; } -.fa-deviantart:before { - content: "\f1bd"; } +.fa-rebel { + --fa: "\f1d0"; } -.fa-cpanel:before { - content: "\f388"; } +.fa-css3 { + --fa: "\f13c"; } -.fa-goodreads-g:before { - content: "\f3a9"; } +.fa-staylinked { + --fa: "\f3f5"; } -.fa-square-git:before { - content: "\f1d2"; } +.fa-kaggle { + --fa: "\f5fa"; } -.fa-git-square:before { - content: "\f1d2"; } +.fa-space-awesome { + --fa: "\e5ac"; } -.fa-square-tumblr:before { - content: "\f174"; } +.fa-deviantart { + --fa: "\f1bd"; } -.fa-tumblr-square:before { - content: "\f174"; } +.fa-cpanel { + --fa: "\f388"; } -.fa-trello:before { - content: "\f181"; } +.fa-goodreads-g { + --fa: "\f3a9"; } -.fa-creative-commons-nc-jp:before { - content: "\f4ea"; } +.fa-square-git { + --fa: "\f1d2"; } -.fa-get-pocket:before { - content: "\f265"; } +.fa-git-square { + --fa: "\f1d2"; } -.fa-perbyte:before { - content: "\e083"; } +.fa-square-tumblr { + --fa: "\f174"; } -.fa-grunt:before { - content: "\f3ad"; } +.fa-tumblr-square { + --fa: "\f174"; } -.fa-weebly:before { - content: "\f5cc"; } +.fa-trello { + --fa: "\f181"; } -.fa-connectdevelop:before { - content: "\f20e"; } +.fa-creative-commons-nc-jp { + --fa: "\f4ea"; } -.fa-leanpub:before { - content: "\f212"; } +.fa-get-pocket { + --fa: "\f265"; } -.fa-black-tie:before { - content: "\f27e"; } +.fa-perbyte { + --fa: "\e083"; } -.fa-themeco:before { - content: "\f5c6"; } +.fa-grunt { + --fa: "\f3ad"; } -.fa-python:before { - content: "\f3e2"; } +.fa-weebly { + --fa: "\f5cc"; } -.fa-android:before { - content: "\f17b"; } +.fa-connectdevelop { + --fa: "\f20e"; } -.fa-bots:before { - content: "\e340"; } +.fa-leanpub { + --fa: "\f212"; } -.fa-free-code-camp:before { - content: "\f2c5"; } +.fa-black-tie { + --fa: "\f27e"; } -.fa-hornbill:before { - content: "\f592"; } +.fa-themeco { + --fa: "\f5c6"; } -.fa-js:before { - content: "\f3b8"; } +.fa-python { + --fa: "\f3e2"; } -.fa-ideal:before { - content: "\e013"; } +.fa-android { + --fa: "\f17b"; } -.fa-git:before { - content: "\f1d3"; } +.fa-bots { + --fa: "\e340"; } -.fa-dev:before { - content: "\f6cc"; } +.fa-free-code-camp { + --fa: "\f2c5"; } -.fa-sketch:before { - content: "\f7c6"; } +.fa-hornbill { + --fa: "\f592"; } -.fa-yandex-international:before { - content: "\f414"; } +.fa-js { + --fa: "\f3b8"; } -.fa-cc-amex:before { - content: "\f1f3"; } +.fa-ideal { + --fa: "\e013"; } -.fa-uber:before { - content: "\f402"; } +.fa-git { + --fa: "\f1d3"; } -.fa-github:before { - content: "\f09b"; } +.fa-dev { + --fa: "\f6cc"; } -.fa-php:before { - content: "\f457"; } +.fa-sketch { + --fa: "\f7c6"; } -.fa-alipay:before { - content: "\f642"; } +.fa-yandex-international { + --fa: "\f414"; } -.fa-youtube:before { - content: "\f167"; } +.fa-cc-amex { + --fa: "\f1f3"; } -.fa-skyatlas:before { - content: "\f216"; } +.fa-uber { + --fa: "\f402"; } -.fa-firefox-browser:before { - content: "\e007"; } +.fa-github { + --fa: "\f09b"; } -.fa-replyd:before { - content: "\f3e6"; } +.fa-php { + --fa: "\f457"; } -.fa-suse:before { - content: "\f7d6"; } +.fa-alipay { + --fa: "\f642"; } -.fa-jenkins:before { - content: "\f3b6"; } +.fa-youtube { + --fa: "\f167"; } -.fa-twitter:before { - content: "\f099"; } +.fa-skyatlas { + --fa: "\f216"; } -.fa-rockrms:before { - content: "\f3e9"; } +.fa-firefox-browser { + --fa: "\e007"; } -.fa-pinterest:before { - content: "\f0d2"; } +.fa-replyd { + --fa: "\f3e6"; } -.fa-buffer:before { - content: "\f837"; } +.fa-suse { + --fa: "\f7d6"; } -.fa-npm:before { - content: "\f3d4"; } +.fa-jenkins { + --fa: "\f3b6"; } -.fa-yammer:before { - content: "\f840"; } +.fa-twitter { + --fa: "\f099"; } -.fa-btc:before { - content: "\f15a"; } +.fa-rockrms { + --fa: "\f3e9"; } -.fa-dribbble:before { - content: "\f17d"; } +.fa-pinterest { + --fa: "\f0d2"; } -.fa-stumbleupon-circle:before { - content: "\f1a3"; } +.fa-buffer { + --fa: "\f837"; } -.fa-internet-explorer:before { - content: "\f26b"; } +.fa-npm { + --fa: "\f3d4"; } -.fa-stubber:before { - content: "\e5c7"; } +.fa-yammer { + --fa: "\f840"; } -.fa-telegram:before { - content: "\f2c6"; } +.fa-btc { + --fa: "\f15a"; } -.fa-telegram-plane:before { - content: "\f2c6"; } +.fa-dribbble { + --fa: "\f17d"; } -.fa-old-republic:before { - content: "\f510"; } +.fa-stumbleupon-circle { + --fa: "\f1a3"; } -.fa-odysee:before { - content: "\e5c6"; } +.fa-internet-explorer { + --fa: "\f26b"; } -.fa-square-whatsapp:before { - content: "\f40c"; } +.fa-stubber { + --fa: "\e5c7"; } -.fa-whatsapp-square:before { - content: "\f40c"; } +.fa-telegram { + --fa: "\f2c6"; } -.fa-node-js:before { - content: "\f3d3"; } +.fa-telegram-plane { + --fa: "\f2c6"; } -.fa-edge-legacy:before { - content: "\e078"; } +.fa-old-republic { + --fa: "\f510"; } -.fa-slack:before { - content: "\f198"; } +.fa-odysee { + --fa: "\e5c6"; } -.fa-slack-hash:before { - content: "\f198"; } +.fa-square-whatsapp { + --fa: "\f40c"; } -.fa-medrt:before { - content: "\f3c8"; } +.fa-whatsapp-square { + --fa: "\f40c"; } -.fa-usb:before { - content: "\f287"; } +.fa-node-js { + --fa: "\f3d3"; } -.fa-tumblr:before { - content: "\f173"; } +.fa-edge-legacy { + --fa: "\e078"; } -.fa-vaadin:before { - content: "\f408"; } +.fa-slack { + --fa: "\f198"; } -.fa-quora:before { - content: "\f2c4"; } +.fa-slack-hash { + --fa: "\f198"; } -.fa-square-x-twitter:before { - content: "\e61a"; } +.fa-medrt { + --fa: "\f3c8"; } -.fa-reacteurope:before { - content: "\f75d"; } +.fa-usb { + --fa: "\f287"; } -.fa-medium:before { - content: "\f23a"; } +.fa-tumblr { + --fa: "\f173"; } -.fa-medium-m:before { - content: "\f23a"; } +.fa-vaadin { + --fa: "\f408"; } -.fa-amilia:before { - content: "\f36d"; } +.fa-quora { + --fa: "\f2c4"; } -.fa-mixcloud:before { - content: "\f289"; } +.fa-square-x-twitter { + --fa: "\e61a"; } -.fa-flipboard:before { - content: "\f44d"; } +.fa-reacteurope { + --fa: "\f75d"; } -.fa-viacoin:before { - content: "\f237"; } +.fa-medium { + --fa: "\f23a"; } -.fa-critical-role:before { - content: "\f6c9"; } +.fa-medium-m { + --fa: "\f23a"; } -.fa-sitrox:before { - content: "\e44a"; } +.fa-amilia { + --fa: "\f36d"; } -.fa-discourse:before { - content: "\f393"; } +.fa-mixcloud { + --fa: "\f289"; } -.fa-joomla:before { - content: "\f1aa"; } +.fa-flipboard { + --fa: "\f44d"; } -.fa-mastodon:before { - content: "\f4f6"; } +.fa-viacoin { + --fa: "\f237"; } -.fa-airbnb:before { - content: "\f834"; } +.fa-critical-role { + --fa: "\f6c9"; } -.fa-wolf-pack-battalion:before { - content: "\f514"; } +.fa-sitrox { + --fa: "\e44a"; } -.fa-buy-n-large:before { - content: "\f8a6"; } +.fa-discourse { + --fa: "\f393"; } -.fa-gulp:before { - content: "\f3ae"; } +.fa-joomla { + --fa: "\f1aa"; } -.fa-creative-commons-sampling-plus:before { - content: "\f4f1"; } +.fa-mastodon { + --fa: "\f4f6"; } -.fa-strava:before { - content: "\f428"; } +.fa-airbnb { + --fa: "\f834"; } -.fa-ember:before { - content: "\f423"; } +.fa-wolf-pack-battalion { + --fa: "\f514"; } -.fa-canadian-maple-leaf:before { - content: "\f785"; } +.fa-buy-n-large { + --fa: "\f8a6"; } -.fa-teamspeak:before { - content: "\f4f9"; } +.fa-gulp { + --fa: "\f3ae"; } -.fa-pushed:before { - content: "\f3e1"; } +.fa-creative-commons-sampling-plus { + --fa: "\f4f1"; } -.fa-wordpress-simple:before { - content: "\f411"; } +.fa-strava { + --fa: "\f428"; } -.fa-nutritionix:before { - content: "\f3d6"; } +.fa-ember { + --fa: "\f423"; } -.fa-wodu:before { - content: "\e088"; } +.fa-canadian-maple-leaf { + --fa: "\f785"; } -.fa-google-pay:before { - content: "\e079"; } +.fa-teamspeak { + --fa: "\f4f9"; } -.fa-intercom:before { - content: "\f7af"; } +.fa-pushed { + --fa: "\f3e1"; } -.fa-zhihu:before { - content: "\f63f"; } +.fa-wordpress-simple { + --fa: "\f411"; } -.fa-korvue:before { - content: "\f42f"; } +.fa-nutritionix { + --fa: "\f3d6"; } -.fa-pix:before { - content: "\e43a"; } +.fa-wodu { + --fa: "\e088"; } -.fa-steam-symbol:before { - content: "\f3f6"; } +.fa-google-pay { + --fa: "\e079"; } + +.fa-intercom { + --fa: "\f7af"; } + +.fa-zhihu { + --fa: "\f63f"; } + +.fa-korvue { + --fa: "\f42f"; } + +.fa-pix { + --fa: "\e43a"; } + +.fa-steam-symbol { + --fa: "\f3f6"; } :root, :host { --fa-style-family-classic: 'Font Awesome 6 Free'; --fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; } @@ -21122,9 +21005,10 @@ hr { .ekko-lightbox{display:-ms-flexbox!important;display:flex!important;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;padding-right:0!important}.ekko-lightbox-container{position:relative}.ekko-lightbox-container>div.ekko-lightbox-item{position:absolute;top:0;left:0;bottom:0;right:0;width:100%}.ekko-lightbox iframe{width:100%;height:100%}.ekko-lightbox-nav-overlay{z-index:1;position:absolute;top:0;left:0;width:100%;height:100%;display:-ms-flexbox;display:flex}.ekko-lightbox-nav-overlay a{-ms-flex:1;flex:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;opacity:0;transition:opacity .5s;color:#fff;font-size:30px;z-index:1}.ekko-lightbox-nav-overlay a>*{-ms-flex-positive:1;flex-grow:1}.ekko-lightbox-nav-overlay a>:focus{outline:none}.ekko-lightbox-nav-overlay a span{padding:0 30px}.ekko-lightbox-nav-overlay a:last-child span{text-align:right}.ekko-lightbox-nav-overlay a:hover{text-decoration:none}.ekko-lightbox-nav-overlay a:focus{outline:none}.ekko-lightbox-nav-overlay a.disabled{cursor:default;visibility:hidden}.ekko-lightbox a:hover{opacity:1;text-decoration:none}.ekko-lightbox .modal-dialog{display:none}.ekko-lightbox .modal-footer{text-align:left}.ekko-lightbox-loader{position:absolute;top:0;left:0;bottom:0;right:0;width:100%;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}.ekko-lightbox-loader>div{width:40px;height:40px;position:relative;text-align:center}.ekko-lightbox-loader>div>div{width:100%;height:100%;border-radius:50%;background-color:#fff;opacity:.6;position:absolute;top:0;left:0;animation:a 2s infinite ease-in-out}.ekko-lightbox-loader>div>div:last-child{animation-delay:-1s}.modal-dialog .ekko-lightbox-loader>div>div{background-color:#333}@keyframes a{0%,to{transform:scale(0);-webkit-transform:scale(0)}50%{transform:scale(1);-webkit-transform:scale(1)}} /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImVra28tbGlnaHRib3guY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGVBQ0UsOEJBQXlCLEFBQXpCLHVCQUF5QixBQUN6QixzQkFBb0IsQUFBcEIsbUJBQW9CLEFBQ3BCLHFCQUF3QixBQUF4Qix1QkFBd0IsQUFDeEIseUJBQTZCLENBQzlCLEFBQ0QseUJBQ0UsaUJBQW1CLENBQ3BCLEFBQ0QsZ0RBQ0Usa0JBQW1CLEFBQ25CLE1BQU8sQUFDUCxPQUFRLEFBQ1IsU0FBVSxBQUNWLFFBQVMsQUFDVCxVQUFZLENBQ2IsQUFDRCxzQkFDRSxXQUFZLEFBQ1osV0FBYSxDQUNkLEFBQ0QsMkJBQ0UsVUFBYSxBQUNiLGtCQUFtQixBQUNuQixNQUFPLEFBQ1AsT0FBUSxBQUNSLFdBQVksQUFDWixZQUFhLEFBQ2Isb0JBQWMsQUFBZCxZQUFjLENBQ2YsQUFDRCw2QkFDRSxXQUFRLEFBQVIsT0FBUSxBQUNSLG9CQUFjLEFBQWQsYUFBYyxBQUNkLHNCQUFvQixBQUFwQixtQkFBb0IsQUFDcEIsVUFBVyxBQUNYLHVCQUF5QixBQUN6QixXQUFZLEFBQ1osZUFBZ0IsQUFDaEIsU0FBYSxDQUNkLEFBQ0QsK0JBQ0Usb0JBQWEsQUFBYixXQUFhLENBQ2QsQUFDRCxvQ0FDRSxZQUFjLENBQ2YsQUFDRCxrQ0FDRSxjQUFnQixDQUNqQixBQUNELDZDQUNFLGdCQUFrQixDQUNuQixBQUNELG1DQUNFLG9CQUFzQixDQUN2QixBQUNELG1DQUNFLFlBQWMsQ0FDZixBQUNELHNDQUNFLGVBQWdCLEFBQ2hCLGlCQUFtQixDQUNwQixBQUNELHVCQUNFLFVBQVcsQUFDWCxvQkFBc0IsQ0FDdkIsQUFDRCw2QkFDRSxZQUFjLENBQ2YsQUFDRCw2QkFDRSxlQUFpQixDQUNsQixBQUNELHNCQUNFLGtCQUFtQixBQUNuQixNQUFPLEFBQ1AsT0FBUSxBQUNSLFNBQVUsQUFDVixRQUFTLEFBQ1QsV0FBWSxBQUNaLG9CQUFjLEFBQWQsYUFBYyxBQUVkLDBCQUF1QixBQUF2QixzQkFBdUIsQUFFdkIscUJBQXdCLEFBQXhCLHVCQUF3QixBQUV4QixzQkFBb0IsQUFBcEIsa0JBQW9CLENBQ3JCLEFBQ0QsMEJBQ0UsV0FBWSxBQUNaLFlBQWEsQUFDYixrQkFBbUIsQUFDbkIsaUJBQW1CLENBQ3BCLEFBQ0QsOEJBQ0UsV0FBWSxBQUNaLFlBQWEsQUFDYixrQkFBbUIsQUFDbkIsc0JBQXVCLEFBQ3ZCLFdBQWEsQUFDYixrQkFBbUIsQUFDbkIsTUFBTyxBQUNQLE9BQVEsQUFDUixtQ0FBNkMsQ0FDOUMsQUFDRCx5Q0FDRSxtQkFBcUIsQ0FDdEIsQUFDRCw0Q0FDRSxxQkFBdUIsQ0FDeEIsQUFVRCxhQUNFLE1BRUUsbUJBQW9CLEFBQ3BCLDBCQUE0QixDQUM3QixBQUNELElBQ0UsbUJBQW9CLEFBQ3BCLDBCQUE0QixDQUM3QixDQUNGIiwiZmlsZSI6ImVra28tbGlnaHRib3guY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmVra28tbGlnaHRib3gge1xuICBkaXNwbGF5OiBmbGV4ICFpbXBvcnRhbnQ7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICBwYWRkaW5nLXJpZ2h0OiAwcHghaW1wb3J0YW50O1xufVxuLmVra28tbGlnaHRib3gtY29udGFpbmVyIHtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xufVxuLmVra28tbGlnaHRib3gtY29udGFpbmVyID4gZGl2LmVra28tbGlnaHRib3gtaXRlbSB7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgdG9wOiAwO1xuICBsZWZ0OiAwO1xuICBib3R0b206IDA7XG4gIHJpZ2h0OiAwO1xuICB3aWR0aDogMTAwJTtcbn1cbi5la2tvLWxpZ2h0Ym94IGlmcmFtZSB7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDEwMCU7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSB7XG4gIHotaW5kZXg6IDEwMDtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDEwMCU7XG4gIGRpc3BsYXk6IGZsZXg7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSBhIHtcbiAgZmxleDogMTtcbiAgZGlzcGxheTogZmxleDtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgb3BhY2l0eTogMDtcbiAgdHJhbnNpdGlvbjogb3BhY2l0eSAwLjVzO1xuICBjb2xvcjogI2ZmZjtcbiAgZm9udC1zaXplOiAzMHB4O1xuICB6LWluZGV4OiAxMDA7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSBhID4gKiB7XG4gIGZsZXgtZ3JvdzogMTtcbn1cbi5la2tvLWxpZ2h0Ym94LW5hdi1vdmVybGF5IGEgPiAqOmZvY3VzIHtcbiAgb3V0bGluZTogbm9uZTtcbn1cbi5la2tvLWxpZ2h0Ym94LW5hdi1vdmVybGF5IGEgc3BhbiB7XG4gIHBhZGRpbmc6IDAgMzBweDtcbn1cbi5la2tvLWxpZ2h0Ym94LW5hdi1vdmVybGF5IGE6bGFzdC1jaGlsZCBzcGFuIHtcbiAgdGV4dC1hbGlnbjogcmlnaHQ7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSBhOmhvdmVyIHtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xufVxuLmVra28tbGlnaHRib3gtbmF2LW92ZXJsYXkgYTpmb2N1cyB7XG4gIG91dGxpbmU6IG5vbmU7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSBhLmRpc2FibGVkIHtcbiAgY3Vyc29yOiBkZWZhdWx0O1xuICB2aXNpYmlsaXR5OiBoaWRkZW47XG59XG4uZWtrby1saWdodGJveCBhOmhvdmVyIHtcbiAgb3BhY2l0eTogMTtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xufVxuLmVra28tbGlnaHRib3ggLm1vZGFsLWRpYWxvZyB7XG4gIGRpc3BsYXk6IG5vbmU7XG59XG4uZWtrby1saWdodGJveCAubW9kYWwtZm9vdGVyIHtcbiAgdGV4dC1hbGlnbjogbGVmdDtcbn1cbi5la2tvLWxpZ2h0Ym94LWxvYWRlciB7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgdG9wOiAwO1xuICBsZWZ0OiAwO1xuICBib3R0b206IDA7XG4gIHJpZ2h0OiAwO1xuICB3aWR0aDogMTAwJTtcbiAgZGlzcGxheTogZmxleDtcbiAgLyogZXN0YWJsaXNoIGZsZXggY29udGFpbmVyICovXG4gIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gIC8qIG1ha2UgbWFpbiBheGlzIHZlcnRpY2FsICovXG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICAvKiBjZW50ZXIgaXRlbXMgdmVydGljYWxseSwgaW4gdGhpcyBjYXNlICovXG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG59XG4uZWtrby1saWdodGJveC1sb2FkZXIgPiBkaXYge1xuICB3aWR0aDogNDBweDtcbiAgaGVpZ2h0OiA0MHB4O1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbn1cbi5la2tvLWxpZ2h0Ym94LWxvYWRlciA+IGRpdiA+IGRpdiB7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDEwMCU7XG4gIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjtcbiAgb3BhY2l0eTogMC42O1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgbGVmdDogMDtcbiAgYW5pbWF0aW9uOiBzay1ib3VuY2UgMnMgaW5maW5pdGUgZWFzZS1pbi1vdXQ7XG59XG4uZWtrby1saWdodGJveC1sb2FkZXIgPiBkaXYgPiBkaXY6bGFzdC1jaGlsZCB7XG4gIGFuaW1hdGlvbi1kZWxheTogLTFzO1xufVxuLm1vZGFsLWRpYWxvZyAuZWtrby1saWdodGJveC1sb2FkZXIgPiBkaXYgPiBkaXYge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMzMzO1xufVxuQC13ZWJraXQta2V5ZnJhbWVzIHNrLWJvdW5jZSB7XG4gIDAlLFxuICAxMDAlIHtcbiAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMCk7XG4gIH1cbiAgNTAlIHtcbiAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMSk7XG4gIH1cbn1cbkBrZXlmcmFtZXMgc2stYm91bmNlIHtcbiAgMCUsXG4gIDEwMCUge1xuICAgIHRyYW5zZm9ybTogc2NhbGUoMCk7XG4gICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlKDApO1xuICB9XG4gIDUwJSB7XG4gICAgdHJhbnNmb3JtOiBzY2FsZSgxKTtcbiAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMSk7XG4gIH1cbn1cbiJdfQ== */ +@charset "UTF-8"; /** * @author zhixin wen - * version: 1.22.5 + * version: 1.24.1 * https://github.com/wenzhixin/bootstrap-table/ */ /* stylelint-disable annotation-no-unknown, max-line-length */ @@ -21134,7 +21018,6 @@ hr { display: block; clear: both; } - .bootstrap-table .fixed-table-toolbar .bs-bars, .bootstrap-table .fixed-table-toolbar .search, .bootstrap-table .fixed-table-toolbar .columns { @@ -21142,26 +21025,21 @@ hr { margin-top: 10px; margin-bottom: 10px; } - .bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group { display: inline-block; margin-left: -1px !important; } - .bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group > .btn { border-radius: 0; } - .bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:first-child > .btn { border-top-left-radius: 4px; border-bottom-left-radius: 4px; } - .bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:last-child > .btn { border-top-right-radius: 4px; border-bottom-right-radius: 4px; } - .bootstrap-table .fixed-table-toolbar .columns .dropdown-menu { text-align: left; max-height: 300px; @@ -21169,7 +21047,6 @@ hr { -ms-overflow-style: scrollbar; z-index: 1001; } - .bootstrap-table .fixed-table-toolbar .columns label { display: block; padding: 3px 20px; @@ -21177,53 +21054,43 @@ hr { font-weight: normal; line-height: 1.4286; } - .bootstrap-table .fixed-table-toolbar .columns-left { margin-right: 5px; } - .bootstrap-table .fixed-table-toolbar .columns-right { margin-left: 5px; } - .bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu { right: 0; left: auto; } - .bootstrap-table .fixed-table-container { position: relative; clear: both; } - .bootstrap-table .fixed-table-container .table { width: 100%; margin-bottom: 0 !important; } - .bootstrap-table .fixed-table-container .table th, .bootstrap-table .fixed-table-container .table td { vertical-align: middle; box-sizing: border-box; } - .bootstrap-table .fixed-table-container .table thead th, .bootstrap-table .fixed-table-container .table tfoot th { vertical-align: bottom; padding: 0; margin: 0; } - .bootstrap-table .fixed-table-container .table thead th:focus, .bootstrap-table .fixed-table-container .table tfoot th:focus { outline: 0 solid transparent; } - .bootstrap-table .fixed-table-container .table thead th.detail, .bootstrap-table .fixed-table-container .table tfoot th.detail { width: 30px; } - .bootstrap-table .fixed-table-container .table thead th .th-inner, .bootstrap-table .fixed-table-container .table tfoot th .th-inner { padding: 0.75rem; @@ -21232,7 +21099,6 @@ hr { text-overflow: ellipsis; white-space: nowrap; } - .bootstrap-table .fixed-table-container .table thead th .sortable, .bootstrap-table .fixed-table-container .table tfoot th .sortable { cursor: pointer; @@ -21240,40 +21106,34 @@ hr { background-repeat: no-repeat; padding-right: 30px !important; } - .bootstrap-table .fixed-table-container .table thead th .sortable.sortable-center, .bootstrap-table .fixed-table-container .table tfoot th .sortable.sortable-center { padding-left: 20px !important; padding-right: 20px !important; } - .bootstrap-table .fixed-table-container .table thead th .both, .bootstrap-table .fixed-table-container .table tfoot th .both { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC"); + background-image: url('data:image/svg+xml;utf8,'); + background-size: 16px 16px; + background-position: center right 2px; } - .bootstrap-table .fixed-table-container .table thead th .asc, .bootstrap-table .fixed-table-container .table tfoot th .asc { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg=="); + background-image: url('data:image/svg+xml;utf8,'); } - .bootstrap-table .fixed-table-container .table thead th .desc, .bootstrap-table .fixed-table-container .table tfoot th .desc { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII= "); + background-image: url('data:image/svg+xml;utf8,'); } - .bootstrap-table .fixed-table-container .table tbody tr.selected td { background-color: rgba(0, 0, 0, 0.075); } - .bootstrap-table .fixed-table-container .table tbody tr.no-records-found td { text-align: center; } - .bootstrap-table .fixed-table-container .table tbody tr .card-view { display: flex; } - .bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title { font-weight: bold; display: inline-block; @@ -21281,60 +21141,47 @@ hr { width: auto !important; text-align: left !important; } - .bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value { width: 100% !important; text-align: left !important; } - .bootstrap-table .fixed-table-container .table .bs-checkbox { text-align: center; } - .bootstrap-table .fixed-table-container .table .bs-checkbox label { margin-bottom: 0; } - -.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type="radio"], -.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type="checkbox"] { +.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio], +.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox] { margin: 0 auto !important; } - .bootstrap-table .fixed-table-container .table.table-sm .th-inner { padding: 0.25rem; } - .bootstrap-table .fixed-table-container.fixed-height:not(.has-footer) { border-bottom: 1px solid #dee2e6; } - .bootstrap-table .fixed-table-container.fixed-height.has-card-view { border-top: 1px solid #dee2e6; border-bottom: 1px solid #dee2e6; } - .bootstrap-table .fixed-table-container.fixed-height .fixed-table-border { border-left: 1px solid #dee2e6; border-right: 1px solid #dee2e6; } - .bootstrap-table .fixed-table-container.fixed-height .table thead th { border-bottom: 1px solid #dee2e6; } - .bootstrap-table .fixed-table-container.fixed-height .table-dark thead th { border-bottom: 1px solid #32383e; } - .bootstrap-table .fixed-table-container .fixed-table-header { overflow: hidden; } - .bootstrap-table .fixed-table-container .fixed-table-body { - overflow: auto auto; + overflow: auto; height: 100%; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading { align-items: center; background: #fff; @@ -21349,28 +21196,23 @@ hr { opacity: 0; visibility: hidden; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open { visibility: visible; opacity: 1; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap { align-items: baseline; display: flex; justify-content: center; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text { margin-right: 6px; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap { align-items: center; display: flex; justify-content: center; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot, .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after, .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before { @@ -21386,81 +21228,64 @@ hr { opacity: 0; width: 5px; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot { animation-delay: 0.3s; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after { animation-delay: 0.6s; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark { background: #212529; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot, .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after, .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before { background: #fff; } - .bootstrap-table .fixed-table-container .fixed-table-footer { overflow: hidden; } - .bootstrap-table .fixed-table-pagination::after { content: ""; display: block; clear: both; } - .bootstrap-table .fixed-table-pagination > .pagination-detail, .bootstrap-table .fixed-table-pagination > .pagination { margin-top: 10px; margin-bottom: 10px; } - .bootstrap-table .fixed-table-pagination > .pagination-detail .pagination-info { line-height: 34px; margin-right: 5px; } - .bootstrap-table .fixed-table-pagination > .pagination-detail .page-list { display: inline-block; } - .bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group { position: relative; display: inline-block; vertical-align: middle; } - .bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group .dropdown-menu { margin-bottom: 0; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination { margin: 0; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a { color: #c8c8c8; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::before { - content: "\2B05"; + content: "⬅"; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::after { - content: "\27A1"; + content: "➡"; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.disabled a { pointer-events: none; cursor: default; } - .bootstrap-table.fullscreen { position: fixed; top: 0; @@ -21468,18 +21293,15 @@ hr { z-index: 1050; width: 100% !important; background: #fff; - height: calc(100vh); + height: 100vh; overflow-y: scroll; } - .bootstrap-table.bootstrap4 .pagination-lg .page-link, .bootstrap-table.bootstrap5 .pagination-lg .page-link { padding: 0.5rem 1rem; } - .bootstrap-table.bootstrap5 .float-left { float: left; } - .bootstrap-table.bootstrap5 .float-right { float: right; } @@ -21825,7 +21647,19 @@ a.accordion-header { a.logo.no-hover a:hover { background-color: transparent; } -.required { +.index-block { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.index-block:hover { + overflow: visible; + white-space: normal; + height: auto; +} +input:required, +select:required, +textarea:required { border-right: 6px solid orange; } .sidebar-menu { @@ -21908,7 +21742,7 @@ img.navbar-brand-img, padding: 10px; background: #f4f4f4; margin-bottom: 3px; - border-left: 2px solid #e6e7e8; + border-inline: 2px solid #e6e7e8; color: #444; cursor: move; } @@ -22199,8 +22033,13 @@ a.link-danger:hover { a.logo.no-hover a:hover { background-color: transparent; } -.required { - border-right: 6px solid orange; +input:required, +select:required { + border-right: 5px solid orange; +} +select:required + .select2-container .select2-selection, +select:required + .select2-container .select2-selection .select2-selection--multiple { + border-right: 5px solid orange !important; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; @@ -22210,18 +22049,156 @@ body { font-size: 14px; white-space: normal; } +.modal-warning .modal-help { + color: #fff8af; +} .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading { z-index: 0 !important; } @media print { - a[href]:after { - content: none; + @page { + size: A4; + margin: 0mm; } .tab-content > .tab-pane { display: block !important; opacity: 1 !important; visibility: visible !important; } + .img-responsive { + width: 200px; + } + html, + body { + width: 1024px; + } + body { + margin: 0 auto; + line-height: 1em; + word-spacing: 1px; + letter-spacing: 0.2px; + font: 15px "Times New Roman", Times, serif; + background: white; + color: black; + width: 100%; + float: none; + } + /* avoid page-breaks inside a listingContainer*/ + .listingContainer { + page-break-inside: avoid; + } + h1 { + font: 28px "Times New Roman", Times, serif; + } + h2 { + font: 24px "Times New Roman", Times, serif; + } + h3 { + font: 20px "Times New Roman", Times, serif; + } + /* Improve colour contrast of links */ + a:link, + a:visited { + color: #781351; + } + /* URL */ + a:link, + a:visited { + background: transparent; + color: #333; + text-decoration: none; + } + a[href]:after { + content: "" !important; + } + a[href^="http://"] { + color: #000; + } + #header { + height: 75px; + font-size: 24pt; + color: black; + } + div.row-new-striped { + margin: 0px; + padding: 0px; + } + .pagination-detail, + .fixed-table-toolbar { + visibility: hidden; + } + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11, + .col-sm-12 .col-sm-pull-3 .col-sm-push-9 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } +} +.select2-selection__choice__remove { + color: white !important; +} +.select2-selection--multiple { + border-color: #d2d6de !important; + overflow-y: auto; +} +.select2-selection__choice { + border-radius: 0px !important; +} +.select2-search select2-search--inline { + height: 35px !important; + float: left; + margin: 0; +} +.select2-results__option { + padding: 5px; + -moz-user-select: none; + user-select: none; + -webkit-user-select: none; + margin: 0px; } img.navbar-brand-img, .navbar-brand > img { @@ -22229,8 +22206,10 @@ img.navbar-brand-img, padding: 5px 5px 5px 0; max-height: 50px; } -.input-daterange { - border-radius: 0px; +.input-daterange, +.input-daterange input:first-child, +.input-daterange input:last-child { + border-radius: 0px !important; } .btn.bg-maroon, .btn.bg-purple { @@ -22309,20 +22288,21 @@ h4 { background-color: #f9f9f9; border-top: 1px solid #dddddd; display: table-cell; + word-wrap: break-word; } .row-striped .row:nth-of-type(even) div { background: #FFFFFF; border-top: 1px solid #dddddd; display: table-cell; + word-wrap: break-word; } .row-new-striped { vertical-align: top; - line-height: 2.6; - padding: 0px; - margin-left: 20px; + padding: 3px; display: table; width: 100%; - padding-right: 20px; + word-wrap: break-word; + table-layout: fixed; } /** * NEW STRIPING @@ -22332,20 +22312,25 @@ h4 { .row-new-striped > .row:nth-of-type(even) { background: #FFFFFF; border-top: 1px solid #dddddd; + line-height: 1.9; display: table-row; } .row-new-striped > .row:nth-of-type(odd) { background-color: #F8F8F8; border-top: 1px solid #dddddd; display: table-row; + line-height: 1.9; + padding: 2px; } .row-new-striped div { display: table-cell; border-top: 1px solid #dddddd; + padding: 6px; } .row-new-striped div { display: table-cell; border-top: 1px solid #dddddd; + padding: 6px; } .row-new-striped div[class^="col"]:first-child { font-weight: bold; @@ -22385,18 +22370,24 @@ h4 { any HTML used in the UserPresenter "title" attribute breaks the column selector HTML. Instead, we use CSS to add the icon into the table header, which leaves the column selector - "title" text as-is. - - See https://github.com/snipe/snipe-it/issues/7989 + "title" text as-is and hides the icon. + See https://github.com/grokability/snipe-it/issues/7989 */ +th.css-accessory > .th-inner, +th.css-accessory-alt > .th-inner, th.css-barcode > .th-inner, -th.css-license > .th-inner, +th.css-component > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, -th.css-users > .th-inner, +th.css-house-flag > .th-inner, +th.css-house-laptop > .th-inner, +th.css-house-user > .th-inner, +th.css-license > .th-inner, th.css-location > .th-inner, -th.css-accessory > .th-inner { +th.css-users > .th-inner, +th.css-currency > .th-inner, +th.css-history > .th-inner { font-size: 0px; line-height: 0.75 !important; text-align: left; @@ -22404,26 +22395,27 @@ th.css-accessory > .th-inner { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -th.css-padlock > .th-inner::before, +th.css-location > .th-inner::before, +th.css-accessory > .th-inner::before, +th.css-accessory-alt > .th-inner::before, th.css-barcode > .th-inner::before, -th.css-license > .th-inner::before, +th.css-component > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, -th.css-users > .th-inner::before, +th.css-house-flag > .th-inner::before, +th.css-house-laptop > .th-inner::before, +th.css-house-user > .th-inner::before, +th.css-license > .th-inner::before, th.css-location > .th-inner::before, -th.css-accessory > .th-inner::before { +th.css-padlock > .th-inner::before, +th.css-users > .th-inner::before, +th.css-currency > .th-inner::before, +th.css-history > .th-inner::before { display: inline-block; font-size: 20px; font-family: "Font Awesome 5 Free"; font-weight: 900; } -th.css-padlock > .th-inner::before { - content: "\f023"; - font-family: "Font Awesome 5 Free"; - font-weight: 900; - padding-right: 4px; - font-size: 12px; -} /** BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). @@ -22464,6 +22456,52 @@ th.css-location > .th-inner::before { font-size: 19px; margin-bottom: 0px; } +th.css-component > .th-inner::before { + content: "\f0a0"; + font-family: "Font Awesome 5 Free"; + font-weight: 500; +} +th.css-padlock > .th-inner::before { + content: "\f023"; + font-family: "Font Awesome 5 Free"; + font-weight: 900; +} +th.css-house-user > .th-inner::before { + content: "\e1b0"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-house-flag > .th-inner::before { + content: "\e50d"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-house-laptop > .th-inner::before { + content: "\e066"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-accessory-alt > .th-inner::before { + content: "\f11c"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-currency > .th-inner::before { + content: "\24"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-history > .th-inner::before { + content: "\f1da"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -22482,7 +22520,8 @@ th.css-location > .th-inner::before { padding: 6px 12px; height: 34px; } -.form-group.has-error label { +.form-group.has-error label, +.form-group.has-error .help-block { color: #a94442; } .select2-container--default .select2-selection--multiple { @@ -22512,10 +22551,41 @@ th.css-location > .th-inner::before { margin-top: 50px; } } -.ellipsis { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; +@media screen and (max-width: 992px) { + .info-stack-container { + flex-direction: column; + } + .col-md-3.col-xs-12.col-sm-push-9.info-stack { + left: auto; + order: 1; + } + .col-md-9.col-xs-12.col-sm-pull-3.info-stack { + right: auto; + order: 2; + } + .info-stack-container > .col-md-9.col-xs-12.col-sm-pull-3.info-stack > .row-new-striped > .row > .col-sm-2 { + width: auto; + float: none; + } +} +@media screen and (max-width: 992px) { + .row-new-striped div { + width: 100%; + } +} +@media screen and (max-width: 1318px) and (min-width: 1200px) { + .admin.box { + height: 170px; + } +} +@media screen and (max-width: 1494px) and (min-width: 1200px) { + .dashboard.small-box { + white-space: nowrap; + text-overflow: ellipsis; + max-width: 188px; + display: block; + overflow: hidden; + } } /** Form-stuff overrides for checkboxes and stuff **/ label.form-control { @@ -22653,6 +22723,9 @@ input[type="radio"]:checked::before { .select2-container .select2-search--inline .select2-search__field { padding-left: 15px; } +.nav-tabs-custom > .nav-tabs > li.active { + font-weight: bold; +} /** --------------------------------------- **/ /** End checkbox styles to replace iCheck **/ /** --------------------------------------- **/ @@ -22687,6 +22760,47 @@ input[type="radio"]:checked::before { filter: brightness(70%); font-size: 70%; } +/** this is needed to override ekko-lightboxes card view styles **/ +.bootstrap-table .fixed-table-container .table tbody tr .card-view { + display: table-row !important; +} +td.text-right.text-padding-number-cell { + padding-right: 30px !important; + white-space: nowrap; +} +th.text-right.text-padding-number-footer-cell { + padding-right: 20px !important; + white-space: nowrap; +} +code.single-line { + white-space: pre-wrap; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + max-width: 400px; +} +p.monospace, +span.monospace { + font-family: monospace, monospace; +} +legend.highlight { + background: repeating-linear-gradient(45deg, #222d32, #222d32 10px, #444 10px, #444 11px); + color: #fff; + font-size: 18px; + padding: 6px 6px 6px 10px; +} +legend.highlight a { + color: #fff; + cursor: pointer; +} +fieldset.bottom-padded { + padding-bottom: 20px; +} +caption.tableCaption { + font-size: 18px; + padding-left: 8px; +} .select2-container { @@ -23201,7 +23315,7 @@ input[type="radio"]:checked::before { padding: 10px; background: #f4f4f4; margin-bottom: 3px; - border-left: 2px solid #e6e7e8; + border-inline: 2px solid #e6e7e8; color: #444; cursor: move; } @@ -23492,8 +23606,13 @@ a.link-danger:hover { a.logo.no-hover a:hover { background-color: transparent; } -.required { - border-right: 6px solid orange; +input:required, +select:required { + border-right: 5px solid orange; +} +select:required + .select2-container .select2-selection, +select:required + .select2-container .select2-selection .select2-selection--multiple { + border-right: 5px solid orange !important; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; @@ -23503,18 +23622,156 @@ body { font-size: 14px; white-space: normal; } +.modal-warning .modal-help { + color: #fff8af; +} .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading { z-index: 0 !important; } @media print { - a[href]:after { - content: none; + @page { + size: A4; + margin: 0mm; } .tab-content > .tab-pane { display: block !important; opacity: 1 !important; visibility: visible !important; } + .img-responsive { + width: 200px; + } + html, + body { + width: 1024px; + } + body { + margin: 0 auto; + line-height: 1em; + word-spacing: 1px; + letter-spacing: 0.2px; + font: 15px "Times New Roman", Times, serif; + background: white; + color: black; + width: 100%; + float: none; + } + /* avoid page-breaks inside a listingContainer*/ + .listingContainer { + page-break-inside: avoid; + } + h1 { + font: 28px "Times New Roman", Times, serif; + } + h2 { + font: 24px "Times New Roman", Times, serif; + } + h3 { + font: 20px "Times New Roman", Times, serif; + } + /* Improve colour contrast of links */ + a:link, + a:visited { + color: #781351; + } + /* URL */ + a:link, + a:visited { + background: transparent; + color: #333; + text-decoration: none; + } + a[href]:after { + content: "" !important; + } + a[href^="http://"] { + color: #000; + } + #header { + height: 75px; + font-size: 24pt; + color: black; + } + div.row-new-striped { + margin: 0px; + padding: 0px; + } + .pagination-detail, + .fixed-table-toolbar { + visibility: hidden; + } + .col-sm-1, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-10, + .col-sm-11, + .col-sm-12 .col-sm-pull-3 .col-sm-push-9 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } +} +.select2-selection__choice__remove { + color: white !important; +} +.select2-selection--multiple { + border-color: #d2d6de !important; + overflow-y: auto; +} +.select2-selection__choice { + border-radius: 0px !important; +} +.select2-search select2-search--inline { + height: 35px !important; + float: left; + margin: 0; +} +.select2-results__option { + padding: 5px; + -moz-user-select: none; + user-select: none; + -webkit-user-select: none; + margin: 0px; } img.navbar-brand-img, .navbar-brand > img { @@ -23522,8 +23779,10 @@ img.navbar-brand-img, padding: 5px 5px 5px 0; max-height: 50px; } -.input-daterange { - border-radius: 0px; +.input-daterange, +.input-daterange input:first-child, +.input-daterange input:last-child { + border-radius: 0px !important; } .btn.bg-maroon, .btn.bg-purple { @@ -23602,20 +23861,21 @@ h4 { background-color: #f9f9f9; border-top: 1px solid #dddddd; display: table-cell; + word-wrap: break-word; } .row-striped .row:nth-of-type(even) div { background: #FFFFFF; border-top: 1px solid #dddddd; display: table-cell; + word-wrap: break-word; } .row-new-striped { vertical-align: top; - line-height: 2.6; - padding: 0px; - margin-left: 20px; + padding: 3px; display: table; width: 100%; - padding-right: 20px; + word-wrap: break-word; + table-layout: fixed; } /** * NEW STRIPING @@ -23625,20 +23885,25 @@ h4 { .row-new-striped > .row:nth-of-type(even) { background: #FFFFFF; border-top: 1px solid #dddddd; + line-height: 1.9; display: table-row; } .row-new-striped > .row:nth-of-type(odd) { background-color: #F8F8F8; border-top: 1px solid #dddddd; display: table-row; + line-height: 1.9; + padding: 2px; } .row-new-striped div { display: table-cell; border-top: 1px solid #dddddd; + padding: 6px; } .row-new-striped div { display: table-cell; border-top: 1px solid #dddddd; + padding: 6px; } .row-new-striped div[class^="col"]:first-child { font-weight: bold; @@ -23678,18 +23943,24 @@ h4 { any HTML used in the UserPresenter "title" attribute breaks the column selector HTML. Instead, we use CSS to add the icon into the table header, which leaves the column selector - "title" text as-is. - - See https://github.com/snipe/snipe-it/issues/7989 + "title" text as-is and hides the icon. + See https://github.com/grokability/snipe-it/issues/7989 */ +th.css-accessory > .th-inner, +th.css-accessory-alt > .th-inner, th.css-barcode > .th-inner, -th.css-license > .th-inner, +th.css-component > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, -th.css-users > .th-inner, +th.css-house-flag > .th-inner, +th.css-house-laptop > .th-inner, +th.css-house-user > .th-inner, +th.css-license > .th-inner, th.css-location > .th-inner, -th.css-accessory > .th-inner { +th.css-users > .th-inner, +th.css-currency > .th-inner, +th.css-history > .th-inner { font-size: 0px; line-height: 0.75 !important; text-align: left; @@ -23697,26 +23968,27 @@ th.css-accessory > .th-inner { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -th.css-padlock > .th-inner::before, +th.css-location > .th-inner::before, +th.css-accessory > .th-inner::before, +th.css-accessory-alt > .th-inner::before, th.css-barcode > .th-inner::before, -th.css-license > .th-inner::before, +th.css-component > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, -th.css-users > .th-inner::before, +th.css-house-flag > .th-inner::before, +th.css-house-laptop > .th-inner::before, +th.css-house-user > .th-inner::before, +th.css-license > .th-inner::before, th.css-location > .th-inner::before, -th.css-accessory > .th-inner::before { +th.css-padlock > .th-inner::before, +th.css-users > .th-inner::before, +th.css-currency > .th-inner::before, +th.css-history > .th-inner::before { display: inline-block; font-size: 20px; font-family: "Font Awesome 5 Free"; font-weight: 900; } -th.css-padlock > .th-inner::before { - content: "\f023"; - font-family: "Font Awesome 5 Free"; - font-weight: 900; - padding-right: 4px; - font-size: 12px; -} /** BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). @@ -23757,6 +24029,52 @@ th.css-location > .th-inner::before { font-size: 19px; margin-bottom: 0px; } +th.css-component > .th-inner::before { + content: "\f0a0"; + font-family: "Font Awesome 5 Free"; + font-weight: 500; +} +th.css-padlock > .th-inner::before { + content: "\f023"; + font-family: "Font Awesome 5 Free"; + font-weight: 900; +} +th.css-house-user > .th-inner::before { + content: "\e1b0"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-house-flag > .th-inner::before { + content: "\e50d"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-house-laptop > .th-inner::before { + content: "\e066"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-accessory-alt > .th-inner::before { + content: "\f11c"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-currency > .th-inner::before { + content: "\24"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} +th.css-history > .th-inner::before { + content: "\f1da"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -23775,7 +24093,8 @@ th.css-location > .th-inner::before { padding: 6px 12px; height: 34px; } -.form-group.has-error label { +.form-group.has-error label, +.form-group.has-error .help-block { color: #a94442; } .select2-container--default .select2-selection--multiple { @@ -23805,10 +24124,41 @@ th.css-location > .th-inner::before { margin-top: 50px; } } -.ellipsis { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; +@media screen and (max-width: 992px) { + .info-stack-container { + flex-direction: column; + } + .col-md-3.col-xs-12.col-sm-push-9.info-stack { + left: auto; + order: 1; + } + .col-md-9.col-xs-12.col-sm-pull-3.info-stack { + right: auto; + order: 2; + } + .info-stack-container > .col-md-9.col-xs-12.col-sm-pull-3.info-stack > .row-new-striped > .row > .col-sm-2 { + width: auto; + float: none; + } +} +@media screen and (max-width: 992px) { + .row-new-striped div { + width: 100%; + } +} +@media screen and (max-width: 1318px) and (min-width: 1200px) { + .admin.box { + height: 170px; + } +} +@media screen and (max-width: 1494px) and (min-width: 1200px) { + .dashboard.small-box { + white-space: nowrap; + text-overflow: ellipsis; + max-width: 188px; + display: block; + overflow: hidden; + } } /** Form-stuff overrides for checkboxes and stuff **/ label.form-control { @@ -23946,6 +24296,9 @@ input[type="radio"]:checked::before { .select2-container .select2-search--inline .select2-search__field { padding-left: 15px; } +.nav-tabs-custom > .nav-tabs > li.active { + font-weight: bold; +} /** --------------------------------------- **/ /** End checkbox styles to replace iCheck **/ /** --------------------------------------- **/ @@ -23980,4 +24333,45 @@ input[type="radio"]:checked::before { filter: brightness(70%); font-size: 70%; } +/** this is needed to override ekko-lightboxes card view styles **/ +.bootstrap-table .fixed-table-container .table tbody tr .card-view { + display: table-row !important; +} +td.text-right.text-padding-number-cell { + padding-right: 30px !important; + white-space: nowrap; +} +th.text-right.text-padding-number-footer-cell { + padding-right: 20px !important; + white-space: nowrap; +} +code.single-line { + white-space: pre-wrap; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + max-width: 400px; +} +p.monospace, +span.monospace { + font-family: monospace, monospace; +} +legend.highlight { + background: repeating-linear-gradient(45deg, #222d32, #222d32 10px, #444 10px, #444 11px); + color: #fff; + font-size: 18px; + padding: 6px 6px 6px 10px; +} +legend.highlight a { + color: #fff; + cursor: pointer; +} +fieldset.bottom-padded { + padding-bottom: 20px; +} +caption.tableCaption { + font-size: 18px; + padding-left: 8px; +} diff --git a/public/css/dist/bootstrap-table.css b/public/css/dist/bootstrap-table.css index eb5b8c0b19..1204064366 100644 --- a/public/css/dist/bootstrap-table.css +++ b/public/css/dist/bootstrap-table.css @@ -1,6 +1,7 @@ +@charset "UTF-8"; /** * @author zhixin wen - * version: 1.22.5 + * version: 1.24.1 * https://github.com/wenzhixin/bootstrap-table/ */ /* stylelint-disable annotation-no-unknown, max-line-length */ @@ -10,7 +11,6 @@ display: block; clear: both; } - .bootstrap-table .fixed-table-toolbar .bs-bars, .bootstrap-table .fixed-table-toolbar .search, .bootstrap-table .fixed-table-toolbar .columns { @@ -18,26 +18,21 @@ margin-top: 10px; margin-bottom: 10px; } - .bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group { display: inline-block; margin-left: -1px !important; } - .bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group > .btn { border-radius: 0; } - .bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:first-child > .btn { border-top-left-radius: 4px; border-bottom-left-radius: 4px; } - .bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:last-child > .btn { border-top-right-radius: 4px; border-bottom-right-radius: 4px; } - .bootstrap-table .fixed-table-toolbar .columns .dropdown-menu { text-align: left; max-height: 300px; @@ -45,7 +40,6 @@ -ms-overflow-style: scrollbar; z-index: 1001; } - .bootstrap-table .fixed-table-toolbar .columns label { display: block; padding: 3px 20px; @@ -53,53 +47,43 @@ font-weight: normal; line-height: 1.4286; } - .bootstrap-table .fixed-table-toolbar .columns-left { margin-right: 5px; } - .bootstrap-table .fixed-table-toolbar .columns-right { margin-left: 5px; } - .bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu { right: 0; left: auto; } - .bootstrap-table .fixed-table-container { position: relative; clear: both; } - .bootstrap-table .fixed-table-container .table { width: 100%; margin-bottom: 0 !important; } - .bootstrap-table .fixed-table-container .table th, .bootstrap-table .fixed-table-container .table td { vertical-align: middle; box-sizing: border-box; } - .bootstrap-table .fixed-table-container .table thead th, .bootstrap-table .fixed-table-container .table tfoot th { vertical-align: bottom; padding: 0; margin: 0; } - .bootstrap-table .fixed-table-container .table thead th:focus, .bootstrap-table .fixed-table-container .table tfoot th:focus { outline: 0 solid transparent; } - .bootstrap-table .fixed-table-container .table thead th.detail, .bootstrap-table .fixed-table-container .table tfoot th.detail { width: 30px; } - .bootstrap-table .fixed-table-container .table thead th .th-inner, .bootstrap-table .fixed-table-container .table tfoot th .th-inner { padding: 0.75rem; @@ -108,7 +92,6 @@ text-overflow: ellipsis; white-space: nowrap; } - .bootstrap-table .fixed-table-container .table thead th .sortable, .bootstrap-table .fixed-table-container .table tfoot th .sortable { cursor: pointer; @@ -116,40 +99,34 @@ background-repeat: no-repeat; padding-right: 30px !important; } - .bootstrap-table .fixed-table-container .table thead th .sortable.sortable-center, .bootstrap-table .fixed-table-container .table tfoot th .sortable.sortable-center { padding-left: 20px !important; padding-right: 20px !important; } - .bootstrap-table .fixed-table-container .table thead th .both, .bootstrap-table .fixed-table-container .table tfoot th .both { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC"); + background-image: url('data:image/svg+xml;utf8,'); + background-size: 16px 16px; + background-position: center right 2px; } - .bootstrap-table .fixed-table-container .table thead th .asc, .bootstrap-table .fixed-table-container .table tfoot th .asc { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg=="); + background-image: url('data:image/svg+xml;utf8,'); } - .bootstrap-table .fixed-table-container .table thead th .desc, .bootstrap-table .fixed-table-container .table tfoot th .desc { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII= "); + background-image: url('data:image/svg+xml;utf8,'); } - .bootstrap-table .fixed-table-container .table tbody tr.selected td { background-color: rgba(0, 0, 0, 0.075); } - .bootstrap-table .fixed-table-container .table tbody tr.no-records-found td { text-align: center; } - .bootstrap-table .fixed-table-container .table tbody tr .card-view { display: flex; } - .bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title { font-weight: bold; display: inline-block; @@ -157,60 +134,47 @@ width: auto !important; text-align: left !important; } - .bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value { width: 100% !important; text-align: left !important; } - .bootstrap-table .fixed-table-container .table .bs-checkbox { text-align: center; } - .bootstrap-table .fixed-table-container .table .bs-checkbox label { margin-bottom: 0; } - -.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type="radio"], -.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type="checkbox"] { +.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio], +.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox] { margin: 0 auto !important; } - .bootstrap-table .fixed-table-container .table.table-sm .th-inner { padding: 0.25rem; } - .bootstrap-table .fixed-table-container.fixed-height:not(.has-footer) { border-bottom: 1px solid #dee2e6; } - .bootstrap-table .fixed-table-container.fixed-height.has-card-view { border-top: 1px solid #dee2e6; border-bottom: 1px solid #dee2e6; } - .bootstrap-table .fixed-table-container.fixed-height .fixed-table-border { border-left: 1px solid #dee2e6; border-right: 1px solid #dee2e6; } - .bootstrap-table .fixed-table-container.fixed-height .table thead th { border-bottom: 1px solid #dee2e6; } - .bootstrap-table .fixed-table-container.fixed-height .table-dark thead th { border-bottom: 1px solid #32383e; } - .bootstrap-table .fixed-table-container .fixed-table-header { overflow: hidden; } - .bootstrap-table .fixed-table-container .fixed-table-body { - overflow: auto auto; + overflow: auto; height: 100%; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading { align-items: center; background: #fff; @@ -225,28 +189,23 @@ opacity: 0; visibility: hidden; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open { visibility: visible; opacity: 1; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap { align-items: baseline; display: flex; justify-content: center; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text { margin-right: 6px; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap { align-items: center; display: flex; justify-content: center; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot, .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after, .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before { @@ -262,81 +221,64 @@ opacity: 0; width: 5px; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot { animation-delay: 0.3s; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after { animation-delay: 0.6s; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark { background: #212529; } - .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot, .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after, .bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before { background: #fff; } - .bootstrap-table .fixed-table-container .fixed-table-footer { overflow: hidden; } - .bootstrap-table .fixed-table-pagination::after { content: ""; display: block; clear: both; } - .bootstrap-table .fixed-table-pagination > .pagination-detail, .bootstrap-table .fixed-table-pagination > .pagination { margin-top: 10px; margin-bottom: 10px; } - .bootstrap-table .fixed-table-pagination > .pagination-detail .pagination-info { line-height: 34px; margin-right: 5px; } - .bootstrap-table .fixed-table-pagination > .pagination-detail .page-list { display: inline-block; } - .bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group { position: relative; display: inline-block; vertical-align: middle; } - .bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group .dropdown-menu { margin-bottom: 0; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination { margin: 0; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a { color: #c8c8c8; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::before { - content: "\2B05"; + content: "⬅"; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::after { - content: "\27A1"; + content: "➡"; } - .bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.disabled a { pointer-events: none; cursor: default; } - .bootstrap-table.fullscreen { position: fixed; top: 0; @@ -344,18 +286,15 @@ z-index: 1050; width: 100% !important; background: #fff; - height: calc(100vh); + height: 100vh; overflow-y: scroll; } - .bootstrap-table.bootstrap4 .pagination-lg .page-link, .bootstrap-table.bootstrap5 .pagination-lg .page-link { padding: 0.5rem 1rem; } - .bootstrap-table.bootstrap5 .float-left { float: left; } - .bootstrap-table.bootstrap5 .float-right { float: right; } diff --git a/public/css/dist/skins/_all-skins.css b/public/css/dist/skins/_all-skins.css new file mode 100644 index 0000000000..6e7722f34b --- /dev/null +++ b/public/css/dist/skins/_all-skins.css @@ -0,0 +1,5473 @@ +/* + * Skin: Black + * ---------- + */ +.skin-black .main-header .navbar { + background-color: #111; +} +.skin-black .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-black .main-header .navbar .nav > li > a:hover, +.skin-black .main-header .navbar .nav > li > a:active, +.skin-black .main-header .navbar .nav > li > a:focus, +.skin-black .main-header .navbar .nav .open > a, +.skin-black .main-header .navbar .nav .open > a:hover, +.skin-black .main-header .navbar .nav .open > a:focus, +.skin-black .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-black .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-black .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-black .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-black .main-header .navbar .sidebar-toggle:hover { + background-color: #040404; +} +@media (max-width: 767px) { + .skin-black .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-black .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-black .main-header .navbar .dropdown-menu li a:hover { + background: #040404; + } +} +.skin-black .main-header li.user-header { + background-color: #111; +} +.skin-black .content-header { + background: transparent; +} +.skin-black .wrapper, +.skin-black .main-sidebar, +.skin-black .left-side { + background-color: #222d32; +} +.skin-black .user-panel > .info, +.skin-black .user-panel > .info > a { + color: #fff; +} +.skin-black .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-black .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-black .sidebar-menu > li:hover > a, +.skin-black .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #111; +} +.skin-black .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-black .sidebar a { + color: #b8c7ce; +} +.skin-black .sidebar a:hover { + text-decoration: none; +} +.skin-black .treeview-menu > li > a { + color: #8aa4af; +} +.skin-black .treeview-menu > li.active > a, +.skin-black .treeview-menu > li > a:hover { + color: #fff; +} +.skin-black .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-black .sidebar-form input[type="text"], +.skin-black .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-black .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-black .sidebar-form input[type="text"]:focus, +.skin-black .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-black .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-black .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-black.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #000; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #505156; + border-color: #FFF; + color: #FFF; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #111; + border-color: #1f1f21; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + color: #fff; +} +.btn.btn-white:visited, +.btn:hover.btn-white:visited { + color: #fff; +} +a { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #000000; +} +:root { + --button-default: #000000; + --button-primary: #000000; + --button-hover: #000000; + --header: #111; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #black; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #111; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #999999; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +.btn-danger.btn-sm.disabled { + color: #FFF; +} +.far fa-life-ring { + color: var(--link); +} +.sidebar-toggle-mobile { + color: #FFF !important; +} +.skin-black .main-header .navbar .nav > li > a, +.skin-black .main-header .navbar .nav > li > a { + text-decoration: none; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #111; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: black dark + * ---------- + */ +.skin-black-dark .main-header .navbar { + background-color: #111; +} +.skin-black-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-black-dark .main-header .navbar .nav > li > a:hover, +.skin-black-dark .main-header .navbar .nav > li > a:active, +.skin-black-dark .main-header .navbar .nav > li > a:focus, +.skin-black-dark .main-header .navbar .nav .open > a, +.skin-black-dark .main-header .navbar .nav .open > a:hover, +.skin-black-dark .main-header .navbar .nav .open > a:focus, +.skin-black-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-black-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-black-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-black-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-black-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #040404; +} +@media (max-width: 767px) { + .skin-black-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-black-dark .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-black-dark .main-header .navbar .dropdown-menu li a:hover { + background: #040404; + color: #fff; + } +} +@media (max-width: 991px) { + .skin-black-dark .main-header .navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-sub); + } +} +.skin-black-dark .main-header li.user-header { + background-color: #111; +} +.skin-black-dark .content-header { + background: transparent; +} +.skin-black-dark .wrapper, +.skin-black-dark .main-sidebar, +.skin-black-dark .left-side { + background-color: #222d32; +} +.skin-black-dark .user-panel > .info, +.skin-black-dark .user-panel > .info > a { + color: #fff; +} +.skin-black-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-black-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-black-dark .sidebar-menu > li:hover > a, +.skin-black-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #111; +} +.skin-black-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-black-dark .sidebar a { + color: #b8c7ce; +} +.skin-black-dark .sidebar a:hover { + text-decoration: none; +} +.skin-black-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-black-dark .treeview-menu > li.active > a, +.skin-black-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-black-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-black-dark .sidebar-form input[type="text"], +.skin-black-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-black-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-black-dark .sidebar-form input[type="text"]:focus, +.skin-black-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-black-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-black-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-black-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #000000; + border-color: #FFF; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #000000; + border-color: #000000; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #000000; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #000000; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: #fff; +} +a:visited { + color: #fff; +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #3d4144; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #000000; + --button-primary: #000000; + --button-hover: #000000; + --header: #111; + /* Use same as Header picker */ + --text-main: #fff; + --text-sub: #9b9b9b; + --link: #fff; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #fff; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #949494; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +a.actions { + color: #fff !important; +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +a:hover > h2.task_menu { + color: var(--header); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu > li.header { + background-color: var(--header); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.far fa-life-ring { + color: var(--link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +a:link.btn-default { + color: var(--nav-link); +} +/* Trying to figure out a way of having links different shades based on what row they are on (even/odd). +a unique challenge for this dark mode skin. +Probably writing the specificity for this incorrectly. --Godfrey Martinez 02/04/21 + +.table-striped>tbody>tr:nth-of-type(even)>a:link{ + color:#000d07; +} +.table-striped>tbody>tr:nth-of-type(odd)>a:link{ + color:#bfbfbf; + +}*/ +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--nav-link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-black { + color: var(--nav-link) !important; +} +.btn-primary:hover { + background-color: var(--button-primary); + color: var(--link) !important; +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: var(--back-sub) !important; +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--nav-link); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--back-sub); + color: var(--visited-link); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-black-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-black-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #000000; +} +.fixed-table-body thead th .th-inner, +.skin-black-dark .sidebar-menu > li.active > a, +.skin-black .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); + text-decoration: underline; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +div.container.row-new-striped { + background-color: var(--back-sub); +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: var(--back-sub); +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Blue + * ---------- + */ +.skin-blue .main-header .navbar { + background-color: #3c8dbc; +} +.skin-blue .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-blue .main-header .navbar .nav > li > a:hover, +.skin-blue .main-header .navbar .nav > li > a:active, +.skin-blue .main-header .navbar .nav > li > a:focus, +.skin-blue .main-header .navbar .nav .open > a, +.skin-blue .main-header .navbar .nav .open > a:hover, +.skin-blue .main-header .navbar .nav .open > a:focus, +.skin-blue .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-blue .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-blue .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-blue .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-blue .main-header .navbar .sidebar-toggle:hover { + background-color: #367fa9; +} +@media (max-width: 767px) { + .skin-blue .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-blue .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-blue .main-header .navbar .dropdown-menu li a:hover { + background: #367fa9; + } +} +.skin-blue .main-header li.user-header { + background-color: #3c8dbc; +} +.skin-blue .content-header { + background: transparent; +} +.skin-blue .wrapper, +.skin-blue .main-sidebar, +.skin-blue .left-side { + background-color: #222d32; +} +.skin-blue .user-panel > .info, +.skin-blue .user-panel > .info > a { + color: #fff; +} +.skin-blue .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-blue .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-blue .sidebar-menu > li:hover > a, +.skin-blue .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #3c8dbc; +} +.skin-blue .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-blue .sidebar a { + color: #b8c7ce; +} +.skin-blue .sidebar a:hover { + text-decoration: none; +} +.skin-blue .treeview-menu > li > a { + color: #8aa4af; +} +.skin-blue .treeview-menu > li.active > a, +.skin-blue .treeview-menu > li > a:hover { + color: #fff; +} +.skin-blue .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-blue .sidebar-form input[type="text"], +.skin-blue .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-blue .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-blue .sidebar-form input[type="text"]:focus, +.skin-blue .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-blue .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-blue .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-blue.layout-top-nav .main-header > .logo .logo-variant { + background-color: unset; +} +.btn.btn-primary, +btn-sm.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +btn-sm .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #307095; + border-color: #23536f; + color: #fff !important; +} +.btna.btn-primary:hover, +btn-sma.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #23536f; + border-color: #23536f; + color: #fff; +} +.btn.btn-white:link, +btn-sm.btn-white:link, +.btn:hover.btn-white:link { + background-color: #307095; + color: #fff; +} +.btn.btn-white:hover, +btn-sm.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #173648; + color: #fff; +} +.btn.btn-white:visited, +btn-sm.btn-white:visited, +.btn:hover.btn-white:visited { + background-color: #173648; + color: #fff; +} +.btn-warning, +.btn-warning:link, +a.btn-warning:hover, +.btn-warning:link, +.btn-warning:visited, +.btn-danger, +.btn-danger:link, +a.btn-danger:hover, +.btn-danger:link, +.btn-danger:visited { + color: #fff; +} +.btn-default:link, +a.btn-default:hover, +.btn-default:visited { + color: #505156; +} +:root { + --button-default: #505156; + --button-primary: #1d455b; + --button-hover: #173648; + --header: #3c8dbc; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #296282; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #5fa4cc; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #86bad8; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +a:link { + color: var(--link); +} +a:visited { + color: var(--visited-link); +} +a:hover { + color: var(--hover-link); +} +.text-primary { + color: #23536f; +} +.far fa-life-ring { + color: var(--link); +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #3c8dbc; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +a.settings_button:link, +a.settings_button:visited, +a.settings_button:hover { + color: #3c8dbc; +} +a.label.label-default:link { + color: #307095; + /* Use same as Header picker, lighten by 70% */ +} +a.label.label-default:visited { + color: #23536f; + /* Use same as Header picker, lighten by 70% */ +} +a.label.label-default:hover { + background-color: #BBB; + color: #296282; +} +/* + * Skin: blue dark + * ---------- + */ +.skin-blue-dark .main-header .navbar { + background-color: #3c8dbc; +} +.skin-blue-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-blue-dark .main-header .navbar .nav > li > a:hover, +.skin-blue-dark .main-header .navbar .nav > li > a:active, +.skin-blue-dark .main-header .navbar .nav > li > a:focus, +.skin-blue-dark .main-header .navbar .nav .open > a, +.skin-blue-dark .main-header .navbar .nav .open > a:hover, +.skin-blue-dark .main-header .navbar .nav .open > a:focus, +.skin-blue-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-blue-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-blue-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-blue-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-blue-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #367fa9; +} +@media (max-width: 767px) { + .skin-blue-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-blue-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-blue-dark .main-header .navbar .dropdown-menu li a:hover { + background: #367fa9; + color: #fff; + } +} +.skin-blue-dark .main-header li.user-header { + background-color: #3c8dbc; +} +.skin-blue-dark .content-header { + background: transparent; +} +.skin-blue-dark .wrapper, +.skin-blue-dark .main-sidebar, +.skin-blue-dark .left-side { + background-color: #222d32; +} +.skin-blue-dark .user-panel > .info, +.skin-blue-dark .user-panel > .info > a { + color: #fff; +} +.skin-blue-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-blue-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-blue-dark .sidebar-menu > li:hover > a, +.skin-blue-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #3c8dbc; +} +.skin-blue-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-blue-dark .sidebar a { + color: #b8c7ce; +} +.skin-blue-dark .sidebar a:hover { + text-decoration: none; +} +.skin-blue-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-blue-dark .treeview-menu > li.active > a, +.skin-blue-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-blue-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-blue-dark .sidebar-form input[type="text"], +.skin-blue-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-blue-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-blue-dark .sidebar-form input[type="text"]:focus, +.skin-blue-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-blue-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-blue-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-blue-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff !important; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #307095; + border-color: #FFF; + color: #FFF; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #23536f; + border-color: #23536f; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #307095; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #173648; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #272727; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #296282; + --button-primary: #1d455b; + --button-hover: #173648; + --header: #3c8dbc; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #00BFFF; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #4dd2ff; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #99e5ff; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +a.actions { + color: #fff !important; +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.far fa-life-ring { + color: var(--link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link { + color: var(--link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-blue { + color: var(--text-main); +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-blue-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-blue-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #3c8dbc; +} +.fixed-table-body thead th .th-inner, +.skin-blue-dark .sidebar-menu > li.active > a, +.skin-blue .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-main); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); + text-decoration: underline; +} +a:visited { + color: var(--visited-link); +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} +#customFieldsTable a[href*='/fieldsets'] { + background-color: transparent; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Contrast + * ---------- + */ +.skin-contrast .main-header .navbar { + background-color: #001F3F; +} +.skin-contrast .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-contrast .main-header .navbar .nav > li > a:hover, +.skin-contrast .main-header .navbar .nav > li > a:active, +.skin-contrast .main-header .navbar .nav > li > a:focus, +.skin-contrast .main-header .navbar .nav .open > a, +.skin-contrast .main-header .navbar .nav .open > a:hover, +.skin-contrast .main-header .navbar .nav .open > a:focus, +.skin-contrast .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-contrast .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-contrast .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-contrast .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-contrast .main-header .navbar .sidebar-toggle:hover { + background-color: #001226; +} +@media (max-width: 767px) { + .skin-contrast .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-contrast .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-contrast .main-header .navbar .dropdown-menu li a:hover { + background: #001226; + } +} +.skin-contrast .main-header li.user-header { + background-color: #001F3F; +} +.skin-contrast .content-header { + background: transparent; +} +.skin-contrast .wrapper, +.skin-contrast .main-sidebar, +.skin-contrast .left-side { + background-color: #222d32; +} +.skin-contrast .user-panel > .info, +.skin-contrast .user-panel > .info > a { + color: #fff; +} +.skin-contrast .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-contrast .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-contrast .sidebar-menu > li:hover > a, +.skin-contrast .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #001F3F; +} +.skin-contrast .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-contrast .sidebar a { + color: #b8c7ce; +} +.skin-contrast .sidebar a:hover { + text-decoration: none; +} +.skin-contrast .treeview-menu > li > a { + color: #8aa4af; +} +.skin-contrast .treeview-menu > li.active > a, +.skin-contrast .treeview-menu > li > a:hover { + color: #fff; +} +.skin-contrast .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-contrast .sidebar-form input[type="text"], +.skin-contrast .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-contrast .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-contrast .sidebar-form input[type="text"]:focus, +.skin-contrast .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-contrast .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-contrast .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-contrast.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #00060c; + border-color: #000000; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #000000; + border-color: #000000; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #00060c; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #000000; + color: #fff; +} +a.btn.btn-link.text-left { + color: #001F3F; + border: 1px solid #000; +} +a.btn.btn-link.text-left:hover { + color: #001F3F; +} +a { + color: #001F3F; +} +a:hover { + color: #001F3F; + text-decoration: underline; +} +a:visited { + color: #001F3F; +} +a.btn:hover { + color: #fff; + text-decoration: underline; +} +.text-primary { + color: #000000; +} +.skin-contrast .treeview-menu > li > a { + color: #FFF; +} +.bg-teal { + background-color: #1b6a6a !important; +} +.bg-orange { + background-color: #995400 !important; +} +.bg-purple { + background-color: #3a3767 !important; +} +.bg-maroon { + background-color: #7d1038 !important; +} +.pagination > li > a { + color: #00060c !important; +} +.pagination > .active > a { + background-color: #001F3F; + color: #FFF !important; +} +btn-success { + background-color: #000d07; +} +input::-webkit-input-placeholder { + /* Chrome/Opera/Safari */ + color: #b5bbc8 !important; +} +input::-moz-placeholder { + /* Firefox 19+ */ + color: #b5bbc8 !important; +} +input::-ms-input-placeholder { + /* IE 10+ */ + color: #b5bbc8 !important; +} +.select2-default, +.select2-selection__placeholder { + color: #37383c !important; +} +.callout.callout-info { + background-color: #23536f !important; +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: Green + * ---------- + */ +:root { + --background: #222; + --back-main: #333; + --back-sub: #3d4144; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #005a31; + --button-primary: #002715; + --button-hover: #000d07; + --header: #00a65a; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #00a65a; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #00f383; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #0dff90; +} +.skin-green .main-header .navbar { + background-color: #00a65a; +} +.skin-green .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-green .main-header .navbar .nav > li > a:hover, +.skin-green .main-header .navbar .nav > li > a:active, +.skin-green .main-header .navbar .nav > li > a:focus, +.skin-green .main-header .navbar .nav .open > a, +.skin-green .main-header .navbar .nav .open > a:hover, +.skin-green .main-header .navbar .nav .open > a:focus, +.skin-green .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-green .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-green .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-green .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-green .main-header .navbar .sidebar-toggle:hover { + background-color: #008d4c; +} +@media (max-width: 767px) { + .skin-green .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-green .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-green .main-header .navbar .dropdown-menu li a:hover { + background: #008d4c; + } +} +.skin-green .main-header li.user-header { + background-color: #00a65a; +} +.skin-green .content-header { + background: transparent; +} +.skin-green .wrapper, +.skin-green .main-sidebar, +.skin-green .left-side { + background-color: #222d32; +} +.skin-green .user-panel > .info, +.skin-green .user-panel > .info > a { + color: #fff; +} +.skin-green .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-green .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-green .sidebar-menu > li:hover > a, +.skin-green .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #00a65a; +} +.skin-green .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-green .sidebar a { + color: #b8c7ce; +} +.skin-green .sidebar a:hover { + text-decoration: none; +} +.skin-green .treeview-menu > li > a { + color: #8aa4af; +} +.skin-green .treeview-menu > li.active > a, +.skin-green .treeview-menu > li > a:hover { + color: #fff; +} +.skin-green .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-green .sidebar-form input[type="text"], +.skin-green .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-green .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-green .sidebar-form input[type="text"]:focus, +.skin-green .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-green .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-green .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-green.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #00733e; + border-color: #004023; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #004023; + border-color: #004023; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #00733e; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #000d07; + color: #fff; +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +a:link { + color: var(--link); +} +a:visited { + color: var(--visited-link); +} +a { + color: #00a65a; +} +a:hover { + color: #004023; +} +a:visited { + color: #00a65a; +} +.text-primary { + color: #004023; +} +.far fa-life-ring { + color: var(--link); +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #00a65a; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: green dark + * ---------- + */ +.skin-green-dark .main-header .navbar { + background-color: #00a65a; +} +.skin-green-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-green-dark .main-header .navbar .nav > li > a:hover, +.skin-green-dark .main-header .navbar .nav > li > a:active, +.skin-green-dark .main-header .navbar .nav > li > a:focus, +.skin-green-dark .main-header .navbar .nav .open > a, +.skin-green-dark .main-header .navbar .nav .open > a:hover, +.skin-green-dark .main-header .navbar .nav .open > a:focus, +.skin-green-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-green-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-green-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-green-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-green-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #008d4c; +} +@media (max-width: 767px) { + .skin-green-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-green-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-green-dark .main-header .navbar .dropdown-menu li a:hover { + background: #008d4c; + color: #fff; + } +} +.skin-green-dark .main-header li.user-header { + background-color: #00a65a; +} +.skin-green-dark .content-header { + background: transparent; +} +.skin-green-dark .wrapper, +.skin-green-dark .main-sidebar, +.skin-green-dark .left-side { + background-color: #222d32; +} +.skin-green-dark .user-panel > .info, +.skin-green-dark .user-panel > .info > a { + color: #fff; +} +.skin-green-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-green-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-green-dark .sidebar-menu > li:hover > a, +.skin-green-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #00a65a; +} +.skin-green-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-green-dark .sidebar a { + color: #b8c7ce; +} +.skin-green-dark .sidebar a:hover { + text-decoration: none; +} +.skin-green-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-green-dark .treeview-menu > li.active > a, +.skin-green-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-green-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-green-dark .sidebar-form input[type="text"], +.skin-green-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-green-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-green-dark .sidebar-form input[type="text"]:focus, +.skin-green-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-green-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-green-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-green-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #FFF !important; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #00733e; + border-color: #FFF; + color: #FFF; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #004023; + border-color: #004023; + color: #FFF; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #00733e; + color: #FFF; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #000d07; + color: #FFF; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #312F2F; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #006400; + --button-primary: #003100; + --button-hover: #000000; + --header: #006400; + /* Use same as Header picker */ + --text-main: #FFF; + --text-sub: #9b9b9b; + --link: #9ACD32; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #b8dc70; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #d7ebad; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +a.actions { + color: #fff !important; +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.far fa-life-ring { + color: var(--link); +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-green { + color: var(--text-main); +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-green-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-green-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #006300; +} +.fixed-table-body thead th .th-inner, +.skin-green-dark .sidebar-menu > li.active > a, +.skin-green .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); + text-decoration: underline; +} +a:visited { + color: var(--visited-link); +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} +#customFieldsTable a[href*='/fieldsets'] { + background-color: transparent; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: orange dark + * ---------- + */ +.skin-orange-dark .main-header .navbar { + background-color: #FF8C00; +} +.skin-orange-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-orange-dark .main-header .navbar .nav > li > a:hover, +.skin-orange-dark .main-header .navbar .nav > li > a:active, +.skin-orange-dark .main-header .navbar .nav > li > a:focus, +.skin-orange-dark .main-header .navbar .nav .open > a, +.skin-orange-dark .main-header .navbar .nav .open > a:hover, +.skin-orange-dark .main-header .navbar .nav .open > a:focus, +.skin-orange-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-orange-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-orange-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-orange-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-orange-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #e67e00; +} +@media (max-width: 767px) { + .skin-orange-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-orange-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-orange-dark .main-header .navbar .dropdown-menu li a:hover { + background: #e67e00; + color: #fff; + } +} +.skin-orange-dark .main-header li.user-header { + background-color: #FF8C00; +} +.skin-orange-dark .content-header { + background: transparent; +} +.skin-orange-dark .wrapper, +.skin-orange-dark .main-sidebar, +.skin-orange-dark .left-side { + background-color: #222d32; +} +.skin-orange-dark .user-panel > .info, +.skin-orange-dark .user-panel > .info > a { + color: #fff; +} +.skin-orange-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-orange-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-orange-dark .sidebar-menu > li:hover > a, +.skin-orange-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #FF8C00; +} +.skin-orange-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-orange-dark .sidebar a { + color: #b8c7ce; +} +.skin-orange-dark .sidebar a:hover { + text-decoration: none; +} +.skin-orange-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-orange-dark .treeview-menu > li.active > a, +.skin-orange-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-orange-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-orange-dark .sidebar-form input[type="text"], +.skin-orange-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-orange-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-orange-dark .sidebar-form input[type="text"]:focus, +.skin-orange-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-orange-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-orange-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-orange-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff !important; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #cc7000; + border-color: #fff; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #995400; + border-color: #995400; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #cc7000; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #663800; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #2E2C2C; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #b36200; + --button-primary: #804600; + --button-hover: #663800; + --header: #FF8C00; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #FFA500; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #ffc04d; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #ffdb99; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +a.actions { + color: #fff !important; +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.far fa-life-ring { + color: var(--link); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link { + color: var(--link); +} +a:visited { + color: var(--visited-link); +} +a:hover { + color: var(--hover-link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--nav-link); +} +a:link.btn-default { + color: var(--nav-link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--background); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-orange { + color: var(--nav-link) !important; + border-color: var(--button-primary); +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-orange-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-orange-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #ff8c00; +} +.fixed-table-body thead th .th-inner, +.skin-orange-dark .sidebar-menu > li.active > a, +.skin-orange .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} +#customFieldsTable a[href*='/fieldsets'] { + background-color: transparent; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Purple + * ---------- + */ +.skin-purple .main-header .navbar { + background-color: #605ca8; +} +.skin-purple .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-purple .main-header .navbar .nav > li > a:hover, +.skin-purple .main-header .navbar .nav > li > a:active, +.skin-purple .main-header .navbar .nav > li > a:focus, +.skin-purple .main-header .navbar .nav .open > a, +.skin-purple .main-header .navbar .nav .open > a:hover, +.skin-purple .main-header .navbar .nav .open > a:focus, +.skin-purple .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-purple .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-purple .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-purple .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-purple .main-header .navbar .sidebar-toggle:hover { + background-color: #555299; +} +@media (max-width: 767px) { + .skin-purple .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-purple .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-purple .main-header .navbar .dropdown-menu li a:hover { + background: #555299; + } +} +.skin-purple .main-header li.user-header { + background-color: #605ca8; +} +.skin-purple .content-header { + background: transparent; +} +.skin-purple .wrapper, +.skin-purple .main-sidebar, +.skin-purple .left-side { + background-color: #222d32; +} +.skin-purple .user-panel > .info, +.skin-purple .user-panel > .info > a { + color: #fff; +} +.skin-purple .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-purple .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-purple .sidebar-menu > li:hover > a, +.skin-purple .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #605ca8; +} +.skin-purple .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-purple .sidebar a { + color: #b8c7ce; +} +.skin-purple .sidebar a:hover { + text-decoration: none; +} +.skin-purple .treeview-menu > li > a { + color: #8aa4af; +} +.skin-purple .treeview-menu > li.active > a, +.skin-purple .treeview-menu > li > a:hover { + color: #fff; +} +.skin-purple .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-purple .sidebar-form input[type="text"], +.skin-purple .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-purple .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-purple .sidebar-form input[type="text"]:focus, +.skin-purple .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-purple .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-purple .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-purple.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #4c4988; + border-color: #3a3767; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #3a3767; + border-color: #3a3767; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #4c4988; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #272546; + color: #fff; +} +a { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #3a3767; +} +:root { + --button-default: #434078; + --button-primary: #302e56; + --button-hover: #272546; + --header: #605ca8; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #605ca8; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #918ec3; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #c1c0dd; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #605ca8; +} +.far fa-life-ring { + color: var(--link); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: purple dark + * ---------- + */ +.skin-purple-dark .main-header .navbar { + background-color: #605ca8; +} +.skin-purple-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-purple-dark .main-header .navbar .nav > li > a:hover, +.skin-purple-dark .main-header .navbar .nav > li > a:active, +.skin-purple-dark .main-header .navbar .nav > li > a:focus, +.skin-purple-dark .main-header .navbar .nav .open > a, +.skin-purple-dark .main-header .navbar .nav .open > a:hover, +.skin-purple-dark .main-header .navbar .nav .open > a:focus, +.skin-purple-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-purple-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-purple-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-purple-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-purple-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #555299; +} +@media (max-width: 767px) { + .skin-purple-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-purple-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-purple-dark .main-header .navbar .dropdown-menu li a:hover { + background: #555299; + color: #fff; + } +} +.skin-purple-dark .main-header li.user-header { + background-color: #605ca8; +} +.skin-purple-dark .content-header { + background: transparent; +} +.skin-purple-dark .wrapper, +.skin-purple-dark .main-sidebar, +.skin-purple-dark .left-side { + background-color: #222d32; +} +.skin-purple-dark .user-panel > .info, +.skin-purple-dark .user-panel > .info > a { + color: #fff; +} +.skin-purple-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-purple-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-purple-dark .sidebar-menu > li:hover > a, +.skin-purple-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #605ca8; +} +.skin-purple-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-purple-dark .sidebar a { + color: #b8c7ce; +} +.skin-purple-dark .sidebar a:hover { + text-decoration: none; +} +.skin-purple-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-purple-dark .treeview-menu > li.active > a, +.skin-purple-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-purple-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-purple-dark .sidebar-form input[type="text"], +.skin-purple-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-purple-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-purple-dark .sidebar-form input[type="text"]:focus, +.skin-purple-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-purple-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-purple-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-purple-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff !important; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #4c4988; + border-color: #FFF; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #3a3767; + border-color: #3a3767; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #4c4988; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #272546; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #080808; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #434078; + --button-primary: #302e56; + --button-hover: #272546; + --header: #605ca8; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #AC83FF; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #dfd0ff; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #ffffff; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--button-default); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.label-default { + background-color: var(--back-sub); + color: #fff; +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link { + color: var(--link); +} +.far fa-life-ring { + color: var(--link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +a:link.btn-default { + color: var(--nav-link); +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--nav-link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-purple { + color: var(--nav-link) !important; +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--link); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-purple-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-purple-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #5f5ca8; +} +.fixed-table-body thead th .th-inner, +.skin-purple-dark .sidebar-menu > li.active > a, +.skin-purple .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); + text-decoration: underline; +} +a:visited { + color: var(--visited-link); +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--back-sub); + color: var(--visited-link); +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Red + * ---------- + */ +.skin-red .main-header .navbar { + background-color: #dd4b39; +} +.skin-red .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-red .main-header .navbar .nav > li > a:hover, +.skin-red .main-header .navbar .nav > li > a:active, +.skin-red .main-header .navbar .nav > li > a:focus, +.skin-red .main-header .navbar .nav .open > a, +.skin-red .main-header .navbar .nav .open > a:hover, +.skin-red .main-header .navbar .nav .open > a:focus, +.skin-red .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-red .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-red .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-red .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-red .main-header .navbar .sidebar-toggle:hover { + background-color: #d73925; +} +@media (max-width: 767px) { + .skin-red .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-red .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-red .main-header .navbar .dropdown-menu li a:hover { + background: #d73925; + } +} +.skin-red .main-header li.user-header { + background-color: #dd4b39; +} +.skin-red .content-header { + background: transparent; +} +.skin-red .wrapper, +.skin-red .main-sidebar, +.skin-red .left-side { + background-color: #222d32; +} +.skin-red .user-panel > .info, +.skin-red .user-panel > .info > a { + color: #fff; +} +.skin-red .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-red .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-red .sidebar-menu > li:hover > a, +.skin-red .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #dd4b39; +} +.skin-red .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-red .sidebar a { + color: #b8c7ce; +} +.skin-red .sidebar a:hover { + text-decoration: none; +} +.skin-red .treeview-menu > li > a { + color: #8aa4af; +} +.skin-red .treeview-menu > li.active > a, +.skin-red .treeview-menu > li > a:hover { + color: #fff; +} +.skin-red .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-red .sidebar-form input[type="text"], +.skin-red .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-red .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-red .sidebar-form input[type="text"]:focus, +.skin-red .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-red .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-red .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-red.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #c23321; + border-color: #96271a; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #96271a; + border-color: #96271a; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #c23321; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #6b1c12; + color: #fff; +} +a { + color: #dd4b39; +} +a:hover { + color: #96271a; +} +a:visited { + color: #dd4b39; +} +.text-primary { + color: #96271a; +} +:root { + --button-default: #ac2d1e; + --button-primary: #802216; + --button-hover: #6b1c12; + --header: #dd4b39; + /* Use same as Header picker */ + --text-main: #BBB; + --text-alt: #222; + --text-sub: #9b9b9b; + --link: #dd4b39; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #e8867a; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #f3c2bc; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a:link { + color: var(--link); +} +a:visited { + color: var(--visited-link); +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #dd4b39; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.far fa-life-ring { + color: var(--link); +} +/* + * Skin: red dark + * ---------- + */ +.skin-red-dark .main-header .navbar { + background-color: #dd4b39; +} +.skin-red-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-red-dark .main-header .navbar .nav > li > a:hover, +.skin-red-dark .main-header .navbar .nav > li > a:active, +.skin-red-dark .main-header .navbar .nav > li > a:focus, +.skin-red-dark .main-header .navbar .nav .open > a, +.skin-red-dark .main-header .navbar .nav .open > a:hover, +.skin-red-dark .main-header .navbar .nav .open > a:focus, +.skin-red-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-red-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-red-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-red-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-red-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #d73925; +} +@media (max-width: 767px) { + .skin-red-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-red-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-red-dark .main-header .navbar .dropdown-menu li a:hover { + background: #d73925; + color: #fff; + } +} +.skin-red-dark .main-header li.user-header { + background-color: #dd4b39; +} +.skin-red-dark .content-header { + background: transparent; +} +.skin-red-dark .wrapper, +.skin-red-dark .main-sidebar, +.skin-red-dark .left-side { + background-color: #222d32; +} +.skin-red-dark .user-panel > .info, +.skin-red-dark .user-panel > .info > a { + color: #fff; +} +.skin-red-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-red-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-red-dark .sidebar-menu > li:hover > a, +.skin-red-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #dd4b39; +} +.skin-red-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-red-dark .sidebar a { + color: #b8c7ce; +} +.skin-red-dark .sidebar a:hover { + text-decoration: none; +} +.skin-red-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-red-dark .treeview-menu > li.active > a, +.skin-red-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-red-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-red-dark .sidebar-form input[type="text"], +.skin-red-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-red-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-red-dark .sidebar-form input[type="text"]:focus, +.skin-red-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-red-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-red-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-red-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #c23321; + border-color: #FFF; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #96271a; + border-color: #96271a; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #c23321; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #6b1c12; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: #fff; +} +a:visited { + color: #fff; +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #3d4144; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #ac2d1e; + --button-primary: #802216; + --button-hover: #6b1c12; + --header: #dd4b39; + /* Use same as Header picker */ + --text-main: #BBB; + --text-alt: #222; + --text-sub: #9b9b9b; + --link: #e00000; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #D24D57; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #D24D57; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--nav-link); +} +.far fa-life-ring { + color: var(--link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--back-sub); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-red { + color: var(--button-primary); +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-red-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-red-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #c23320; +} +.fixed-table-body thead th .th-inner, +.skin-red-dark .sidebar-menu > li.active > a, +.skin-red .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +#upload-table tbody > tr.warning > td, +#upload-table h3, +#upload-table p { + background-color: #fcf8e3; + color: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--nav-link); +} +a:hover { + color: var(--nav-link); + text-decoration: underline; +} +a:visited { + color: var(--nav-link); +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--back-sub); + color: var(--visited-link); +} +#customFieldsTable a[href*='/fieldsets'] { + background-color: transparent; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Orange + * ---------- + */ +.skin-orange .main-header .navbar { + background-color: #FF8C00; +} +.skin-orange .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-orange .main-header .navbar .nav > li > a:hover, +.skin-orange .main-header .navbar .nav > li > a:active, +.skin-orange .main-header .navbar .nav > li > a:focus, +.skin-orange .main-header .navbar .nav .open > a, +.skin-orange .main-header .navbar .nav .open > a:hover, +.skin-orange .main-header .navbar .nav .open > a:focus, +.skin-orange .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-orange .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-orange .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-orange .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-orange .main-header .navbar .sidebar-toggle:hover { + background-color: #e67e00; +} +@media (max-width: 767px) { + .skin-orange .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-orange .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-orange .main-header .navbar .dropdown-menu li a:hover { + background: #e67e00; + } +} +.skin-orange .main-header li.user-header { + background-color: #FF8C00; +} +.skin-orange .content-header { + background: transparent; +} +.skin-orange .wrapper, +.skin-orange .main-sidebar, +.skin-orange .left-side { + background-color: #222d32; +} +.skin-orange .user-panel > .info, +.skin-orange .user-panel > .info > a { + color: #fff; +} +.skin-orange .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-orange .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-orange .sidebar-menu > li:hover > a, +.skin-orange .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #FF8C00; +} +.skin-orange .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-orange .sidebar a { + color: #b8c7ce; +} +.skin-orange .sidebar a:hover { + text-decoration: none; +} +.skin-orange .treeview-menu > li > a { + color: #8aa4af; +} +.skin-orange .treeview-menu > li.active > a, +.skin-orange .treeview-menu > li > a:hover { + color: #fff; +} +.skin-orange .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-orange .sidebar-form input[type="text"], +.skin-orange .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-orange .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-orange .sidebar-form input[type="text"]:focus, +.skin-orange .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-orange .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-orange .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-orange.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #cc7000; + border-color: #995400; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #995400; + border-color: #995400; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #cc7000; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #663800; + color: #fff; +} +a { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #995400; +} +:root { + --button-default: #b36200; + --button-primary: #804600; + --button-hover: #663800; + --header: #FF8C00; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #FF8C00; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #ffa333; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #ffaf4d; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +.far fa-life-ring { + color: var(--link); +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #FF8C00; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: Yellow + * ---------- + */ +.skin-yellow .main-header .navbar { + background-color: #FBCC34; +} +.skin-yellow .main-header .navbar .nav > li > a { + color: rgba(255, 255, 255, 0.8); +} +.skin-yellow .main-header .navbar .nav > li > a:hover, +.skin-yellow .main-header .navbar .nav > li > a:active, +.skin-yellow .main-header .navbar .nav > li > a:focus, +.skin-yellow .main-header .navbar .nav .open > a, +.skin-yellow .main-header .navbar .nav .open > a:hover, +.skin-yellow .main-header .navbar .nav .open > a:focus, +.skin-yellow .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-yellow .main-header .navbar .sidebar-toggle { + color: rgba(255, 255, 255, 0.8); +} +.skin-yellow .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-yellow .main-header .navbar .sidebar-toggle { + color: #000; +} +.skin-yellow .main-header .navbar .sidebar-toggle:hover { + background-color: #fbc61b; +} +@media (max-width: 767px) { + .skin-yellow .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-yellow .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-yellow .main-header .navbar .dropdown-menu li a:hover { + background: #fbc61b; + } +} +.skin-yellow .main-header li.user-header { + background-color: #FBCC34; +} +.skin-yellow .content-header { + background: transparent; +} +.skin-yellow .wrapper, +.skin-yellow .main-sidebar, +.skin-yellow .left-side { + background-color: #222d32; +} +.skin-yellow .user-panel > .info, +.skin-yellow .user-panel > .info > a { + color: #fff; +} +.skin-yellow .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-yellow .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-yellow .sidebar-menu > li:hover > a, +.skin-yellow .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #FBCC34; +} +.skin-yellow .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-yellow .sidebar a { + color: #b8c7ce; +} +.skin-yellow .sidebar a:hover { + text-decoration: none; +} +.skin-yellow .treeview-menu > li > a { + color: #8aa4af; +} +.skin-yellow .treeview-menu > li.active > a, +.skin-yellow .treeview-menu > li > a:hover { + color: #fff; +} +.skin-yellow .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-yellow .sidebar-form input[type="text"], +.skin-yellow .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-yellow .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-yellow .sidebar-form input[type="text"]:focus, +.skin-yellow .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-yellow .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-yellow .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-yellow.layout-top-nav .main-header > .logo .logo-variant { + color: #413F42; + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #f7be05; + border-color: #c59704; + color: var(--nav-link); +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #c59704; + border-color: #c59704; + color: var(--nav-link); +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #f7be05; + color: var(--nav-link); +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #937103; + color: var(--nav-link); +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +a { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #c59704; +} +:root { + --button-default: #deab04; + --button-primary: #ac8403; + --button-hover: #937103; + --header: #FBCC34; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #5b585c; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #747176; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #b4b1b5; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #222; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #FBCC34; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.navbar-nav > li > a:visited { + color: var(--nav-link); +} +:hover { + color: var(--nav-link); +} +.skin-yellow .main-header .navbar .nav > li > a { + color: #413F42; +} +.far fa-life-ring { + color: var(--link); +} +/* + * Skin: yellow dark + * ---------- + */ +.skin-yellow-dark .main-header .navbar .sidebar-toggle { + color: #000 !important; +} +.skin-yellow-dark .main-header .navbar .sidebar-toggle:hover { + background-color: var(--link); +} +@media (max-width: 767px) { + .skin-yellow-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-yellow-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-yellow-dark .main-header .navbar .dropdown-menu li a:hover { + background: #e08e0b; + color: #fff; + } +} +.skin-yellow-dark .main-header li.user-header { + background-color: #f39c12; +} +.skin-yellow-dark .content-header { + background: transparent; +} +.skin-yellow-dark .wrapper, +.skin-yellow-dark .main-sidebar, +.skin-yellow-dark .left-side { + background-color: #222d32; +} +.skin-yellow-dark .user-panel > .info, +.skin-yellow-dark .user-panel > .info > a { + color: #fff; +} +.skin-yellow-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-yellow-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-yellow-dark .sidebar-menu > li:hover > a, +.skin-yellow-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #f39c12; +} +.skin-yellow-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-yellow-dark .sidebar a { + color: #b8c7ce; +} +.skin-yellow-dark .sidebar a:hover { + text-decoration: none; +} +.skin-yellow-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-yellow-dark .treeview-menu > li.active > a, +.skin-yellow-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-yellow-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-yellow-dark .sidebar-form input[type="text"], +.skin-yellow-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-yellow-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-yellow-dark .sidebar-form input[type="text"]:focus, +.skin-yellow-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-yellow-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-yellow-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: var(--button-default); + border-color: #000000; + color: #545454; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: var(--button-hover); + border-color: var(--button-hover); + color: #545454; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: var(--button-default); + color: #545454; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: var(--hover-link); + color: #545454; +} +a.actions { + color: #fff !important; +} +a:visited.label-default, +a:link.label-default { + color: #444; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #3d4144; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #FFCC32; + --button-primary: #b28500; + --button-hover: #987200; + --header: #FFCC32; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #F0E68C; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #f9f5d0; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #ffffff; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #222; + /* Use same as Header picker */ + --light-link: #545454; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +.pagination > li > a { + color: var(--text-main); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--button-default); + border-color: var(--light-link); + color: var(--nav-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +a.btn.btn-default { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--text-main); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.navbar-nav > li > a:visited { + color: var(--nav-link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link.btn-default { + color: var(--nav-link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: #fff !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +.far fa-life-ring { + color: var(--link); +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--nav-link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-yellow { + color: var(--nav-link) !important; +} +.btn-primary:hover { + background-color: var(--button-primary); + color: var(--nav-link) !important; +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-yellow-dark .main-header .navbar .dropdown-menu li a { + color: var(--header); +} +.skin-yellow-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #000000; +} +tr th div.th-inner { + color: #FFFFFF; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); +} +.th-inner { + color: var(--nav-link); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} + diff --git a/public/css/dist/skins/_all-skins.css.map b/public/css/dist/skins/_all-skins.css.map new file mode 100644 index 0000000000..d6c9525c71 --- /dev/null +++ b/public/css/dist/skins/_all-skins.css.map @@ -0,0 +1 @@ +{"version":3,"file":"css/dist/skins/_all-skins.css","mappings":"AAAA;;;EAGE;ACMF;ECHE;AFAF;ACGA;ECAI;AFAJ;ACAA;;;;;;;ECUI;EACA;AFDJ;ACVA;ECgBI;AFHJ;AEII;EACE;EACA;AFFN;ACjBA;EAMQ;ADcR;ACbQ;EACE;ADeV;ACZM;EAGM;IACE;EDYZ;EChBI;IAOQ;EDYZ;ECXY;IACE;EDad;AACF;AClCA;EA8BM;ADON;ACrCA;EAoCI;ADIJ;ACxCA;;;ECsGI;AFzDJ;AC7CA;;EC2GM;AF1DN;AEgEI;EACE;EACA;AF9DN;ACrDA;ECuHM;AF/DN;AEkEI;;EACE;EACA;EACA;AF/DN;AC9DA;ECiIM;EACA;AFhEN;AClEA;ECuII;AFlEJ;AEmEI;EACE;AFjEN;ACxEA;ECgJQ;AFrER;AEuEM;;EACE;AFpER;AC/EA;ECgEE;EA0FE;EACA;AFvEJ;ACpFA;;EC6JM;EACA;EACA;EACA;ECuBI;AHzFV;AC9FA;ECoKM;EAhGJ;EACA;EACA;EACA;AF8BF;AEiEM;;EACE;EACA;AF9DR;AEgEM;EACE;AF9DR;AC7GA;EC+KM;EA3GJ;EACA;EACA;EACA;AF6CF;ACzEA;EAEI;AD0EJ;ACpEA;;EACE;ADuEF;ACrEE;;;;EACE;EACA;EACA;AD0EJ;ACtEE;;EACE;EACA;EACA;ADyEJ;ACtEE;;EACE;ADyEJ;ACtEE;;EACE;ADyEJ;ACtEE;;EACE;ADyEJ;ACpEA;EACE;ADsEF;ACpEE;EACE;ADsEJ;ACpEE;EACE;ADsEJ;AClEA;EACE;ADoEF;AClEA;EACE;EACA;EACA;EACA;EDoEA,8BAA8B;ECnE9B;EACA;EACA;EDqEA,8CAA8C;ECpE9C;EDsEA,8CAA8C;ECrE9C;EDuEA,8CAA8C;ECtE9C;EDwEA,8BAA8B;ECvE9B;EDyEA,8BAA8B;AAChC;ACxEA;;;EACE;AD4EF;AC1EC;;;EACC;AD8EF;AC5EA;EACE;AD8EF;AC5EA;EACE;AD8EF;AC3EA;EACE;AD6EF;AC1EA;;EACE;AD6EF;AC3EA;EACE;AD6EF;AC1EA;;EACE;AD6EF;AACA;;;EAGE;AItNF;EFHE;AF4NF;AIzNA;EFAI;AF4NJ;AI5NA;;;;;;;EFUI;EACA;AF2NJ;AItOA;EFgBI;AFyNJ;AExNI;EACE;EACA;AF0NN;AI7OA;EAMQ;AJ0OR;AIzOQ;EACE;AJ2OV;AIxOM;EAGM;IACE;IACA;EJwOZ;EI7OI;IAQQ;EJwOZ;EIvOY;IACE;IACA;EJyOd;AACF;AInOI;EAAA;IAEI;EJqON;AACF;AIrQA;EAqCM;AJmON;AIxQA;EA2CI;AJgOJ;AI3QA;;;EFsGI;AF0KJ;AIhRA;;EF2GM;AFyKN;AEnKI;EACE;EACA;AFqKN;AIxRA;EFuHM;AFoKN;AEjKI;;EACE;EACA;EACA;AFoKN;AIjSA;EFiIM;EACA;AFmKN;AIrSA;EFuII;AFiKJ;AEhKI;EACE;AFkKN;AI3SA;EFgJQ;AF8JR;AE5JM;;EACE;AF+JR;AIlTA;EFgEE;EA0FE;EACA;AF4JJ;AIvTA;;EF6JM;EACA;EACA;EACA;ECuBI;AH0IV;AIjUA;EFoKM;EAhGJ;EACA;EACA;EACA;AFiQF;AElKM;;EACE;EACA;AFqKR;AEnKM;EACE;AFqKR;AIhVA;EF+KM;EA3GJ;EACA;EACA;EACA;AFgRF;AIrSA;EAEI;AJsSJ;AIhSA;;EACE;AJmSF;AIjSE;;;;EACE;EACA;EACA;AJsSJ;AIlSE;;EACE;EACA;EACA;AJqSJ;AIlSE;;EACE;EACA;AJqSJ;AIlSE;;EACE;EACA;AJqSJ;AACA;;EAEE;AIjSF;EACE;AJmSF;AI/RE;EACE;AJiSJ;AI/RE;EACE;AJiSJ;AI7RA;EACE;AJ+RF;AIzRA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EJ2RA,8BAA8B;EI1R9B;EACA;EACA;EJ4RA,8CAA8C;EI3R9C;EJ6RA,8CAA8C;EI5R9C;EJ8RA,8CAA8C;EI7R9C;EJ+RA,8BAA8B;EI9R9B;EJgSA,8BAA8B;AAChC;AI/RA;EACE;AJiSF;AI/RA;EACE;AJiSF;AI9RA;EACE;EACA;AJgSF;AI9RA;EACE;EACA;AJgSF;AI9RA;EACE;AJgSF;AI9RA;EACE;AJgSF;AI9RA;EACE;AJgSF;AI9RA;EACE;AJgSF;AI9RA;EACE;EACA;AJgSF;AI9RA;EACE;EACA;AJgSF;AI7RA;;EACE;EAEA;EACA;AJgSF;AI7RA;EACE;AJ+RF;AI7RA;EACE;AJ+RF;AI7RA;;EACE;AJgSF;AI9RA;EACE;AJgSF;AI7RA;EACE;EACA;AJ+RF;AI5RA;EACE;AJ8RF;AI3RA;;EACE;AJ8RF;AI/RA;;EAGI;EACA;AJgSJ;AI7RA;EACE;AJ+RF;AACA;;;;;;;;;;EAUE;AI5RF;EACE;AJ8RF;AI5RA;EACE;AJ8RF;AI3RA;EACE;AJ6RF;AI3RA;EACE;AJ6RF;AI3RA;;EACE;EACA;AJ8RF;AI5RA;;;EACE;EACA;AJgSF;AI9RA;EACE;EACA;AJgSF;AI7RA;EACE;EACA;AJ+RF;AI5RA;;;;EACE;EACA;AJiSF;AI/RA;;;;EACE;AJoSF;AIlSA;EACE;EACA;AJoSF;AIlSA;EACE;AJoSF;AIlSA;EACE;AJoSF;AIlSA;EACE;AJoSF;AIlSA;EACE;EACA;AJoSF;AIlSA;;;;;;EACE;AJySF;AIvSA;EACE;EACA;AJySF;AIvSA;EACE;AJySF;AIvSA;EACE;EACA;AJySF;AIvSA;EACE;EACA;AJySF;AIvSA;;;EACE;EACA;AJ2SF;AIzSA;;;;;;;EACE;AJiTF;AI/SA;EACE;AJiTF;AI/SA;;EACE;EACA;AJkTF;AIhTA;;EACE;AJmTF;AIjTA;;EACE;EACA;AJoTF;AIlTA;EACE;AJoTF;AIlTA;EACE;EACA;AJoTF;AIlTA;;EACE;AJqTF;AInTA;EACE;AJqTF;AInTA;EACE;AJqTF;AInTA;EACE;EACA;AJqTF;AInTA;;EACE;AJsTF;AIpTA;EACE;EACA;AJsTF;AIpTA;EACE;EACA;AJsTF;AIpTA;EACE;EACA;AJsTF;AIpTA;EACE;EACA;AJsTF;AIpTA;;EACE;EACA;AJuTF;AIrTA;EACE;EACA;AJuTF;AIrTA;EACE;AJuTF;AIrTA;EACE;AJuTF;AIrTA;EACE;EACA;AJuTF;AIrTA;EACE;EACA;AJuTF;AIrTA;EACE;AJuTF;AIrTA;EACE;AJuTF;AIrTA;;;;EACE;AJ0TF;AIxTA;;EACE;EACA;AJ2TF;AIzTA;EACE;EACA;AJ2TF;AIzTA;EACE;EACA;AJ2TF;AIzTA;EACE;EACA;AJ2TF;AIzTA;EACE;EACA;AJ2TF;AIzTA;;;;EACE;AJ8TF;AI3TA;EACE;AJ6TF;AI1TA;EACE;AJ4TF;AI1TE;EACE;AJ4TJ;AI3TE;EACE;EACA;AJ6TJ;AIzTA;EACE;EACA;EACA;EACA;EACA;EAGA;AJyTF;AIrTA;EACE;EACA;EACA;EACA;AJuTF;AInTA;EACE;EACA;EACA;EACA;AJqTF;AIlTA;;EACE;AJqTF;AInTA;EACE;AJqTF;AInTA;;;;;;;;;;;;EACE;AJgUF;AI9TA;EACE;AJgUF;AACA;;;EAGE;AKxwBF;EHHE;AF8wBF;AK3wBA;EHAI;AF8wBJ;AK9wBA;;;;;;;EHUI;EACA;AF6wBJ;AKxxBA;EHgBI;AF2wBJ;AE1wBI;EACE;EACA;AF4wBN;AK/xBA;EAMQ;AL4xBR;AK3xBQ;EACE;AL6xBV;AK1xBM;EAGM;IACE;EL0xBZ;EK9xBI;IAOQ;EL0xBZ;EKzxBY;IACE;EL2xBd;AACF;AKhzBA;EA8BM;ALqxBN;AKnzBA;EAoCI;ALkxBJ;AKtzBA;;;EHsGI;AFqtBJ;AK3zBA;;EH2GM;AFotBN;AE9sBI;EACE;EACA;AFgtBN;AKn0BA;EHuHM;AF+sBN;AE5sBI;;EACE;EACA;EACA;AF+sBN;AK50BA;EHiIM;EACA;AF8sBN;AKh1BA;EHuII;AF4sBJ;AE3sBI;EACE;AF6sBN;AKt1BA;EHgJQ;AFysBR;AEvsBM;;EACE;AF0sBR;AK71BA;EHgEE;EA0FE;EACA;AFusBJ;AKl2BA;;EH6JM;EACA;EACA;EACA;ECuBI;AHqrBV;AK52BA;EHoKM;EAhGJ;EACA;EACA;EACA;AF4yBF;AE7sBM;;EACE;EACA;AFgtBR;AE9sBM;EACE;AFgtBR;AK33BA;EH+KM;EA3GJ;EACA;EACA;EACA;AF2zBF;AKv1BA;EAEI;ALw1BJ;AK/0BE;;;;;;EACE;EACA;EACA;ALs1BJ;AKl1BE;;;EACE;EACA;EACA;ALs1BJ;AKn1BE;;;EACE;EACA;ALu1BJ;AKp1BE;;;EACE;EACA;ALw1BJ;AKr1BE;;;EACE;EACA;ALy1BJ;AKn1BA;;;;;;;;;;EAGI;AL41BJ;AKz1BA;;;EAEE;AL41BF;AKx1BA;EACE;EACA;EACA;EACA;EL01BA,8BAA8B;EKz1B9B;EACA;EACA;EL21BA,8CAA8C;EK11B9C;EL41BA,8CAA8C;EK31B9C;EL61BA,8CAA8C;EK51B9C;EL81BA,8BAA8B;EK71B9B;EL+1BA,8BAA8B;AAChC;AK91BA;;;EACE;ALk2BF;AKh2BC;;;EACC;ALo2BF;AKl2BA;EACE;ALo2BF;AKj2BA;EACE;ALm2BF;AKh2BA;EACE;ALk2BF;AK/1BA;EACE;ALi2BF;AK/1BA;EACE;ALi2BF;AK91BA;EACE;ALg2BF;AK91BA;EACE;ALg2BF;AK71BA;;EACE;ALg2BF;AK51BA;;;EACE;ALg2BF;AACA;;;EAGE;AMx/BF;EJHE;AF8/BF;AM3/BA;EJAI;AF8/BJ;AM9/BA;;;;;;;EJUI;EACA;AF6/BJ;AMxgCA;EJgBI;AF2/BJ;AE1/BI;EACE;EACA;AF4/BN;AM/gCA;EAMQ;AN4gCR;AM3gCQ;EACE;AN6gCV;AM1gCM;EAGM;IACE;IACA;EN0gCZ;EM/gCI;IAQQ;EN0gCZ;EMzgCY;IACE;IACA;EN2gCd;AACF;AMliCA;EAgCM;ANqgCN;AMriCA;EAsCI;ANkgCJ;AMxiCA;;;EJsGI;AFu8BJ;AM7iCA;;EJ2GM;AFs8BN;AEh8BI;EACE;EACA;AFk8BN;AMrjCA;EJuHM;AFi8BN;AE97BI;;EACE;EACA;EACA;AFi8BN;AM9jCA;EJiIM;EACA;AFg8BN;AMlkCA;EJuII;AF87BJ;AE77BI;EACE;AF+7BN;AMxkCA;EJgJQ;AF27BR;AEz7BM;;EACE;AF47BR;AM/kCA;EJgEE;EA0FE;EACA;AFy7BJ;AMplCA;;EJ6JM;EACA;EACA;EACA;ECuBI;AHu6BV;AM9lCA;EJoKM;EAhGJ;EACA;EACA;EACA;AF8hCF;AE/7BM;;EACE;EACA;AFk8BR;AEh8BM;EACE;AFk8BR;AM7mCA;EJ+KM;EA3GJ;EACA;EACA;EACA;AF6iCF;AMvkCA;EAEI;ANwkCJ;AMlkCA;;EACE;ANqkCF;AMnkCE;;;;EACE;EACA;EACA;ANwkCJ;AMpkCE;;EACE;EACA;EACA;ANukCJ;AMpkCE;;EACE;EACA;ANukCJ;AMpkCE;;EACE;EACA;ANukCJ;AACA;;EAEE;AMnkCF;EACE;ANqkCF;AMhkCE;EACE;ANkkCJ;AMhkCE;EACE;ANkkCJ;AM9jCA;EACE;ANgkCF;AM1jCA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EN4jCA,8BAA8B;EM3jC9B;EACA;EACA;EN6jCA,8CAA8C;EM5jC9C;EN8jCA,8CAA8C;EM7jC9C;EN+jCA,8CAA8C;EM9jC9C;ENgkCA,8BAA8B;EM/jC9B;ENikCA,8BAA8B;AAChC;AM/jCA;EACE;ANikCF;AM/jCA;EACE;ANikCF;AM9jCA;EACE;EACA;ANgkCF;AM9jCA;EACE;EACA;ANgkCF;AM9jCA;EACE;ANgkCF;AM9jCA;EACE;ANgkCF;AM7jCA;EACE;AN+jCF;AM7jCA;EACE;AN+jCF;AM7jCA;EACE;EACA;AN+jCF;AM5jCA;;EACE;EAEA;EACA;AN+jCF;AM5jCA;EACE;AN8jCF;AM5jCA;EACE;AN8jCF;AM5jCA;;EACE;AN+jCF;AM7jCA;EACE;AN+jCF;AM7jCA;EACE;EACA;AN+jCF;AM5jCA;EACE;AN8jCF;AM3jCA;EACE;AN6jCF;AM3jCA;EACE;AN6jCF;AM3jCA;;EACE;AN8jCF;AM/jCA;;EAGI;EACA;ANgkCJ;AM3jCA;EACE;AN6jCF;AM3jCA;EACE;AN6jCF;AM1jCA;EACE;AN4jCF;AM1jCA;EACE;AN4jCF;AM1jCA;;EACE;EACA;AN6jCF;AM3jCA;;;EACE;EACA;AN+jCF;AM7jCA;EACE;EACA;AN+jCF;AM5jCA;EACE;EACA;AN8jCF;AM3jCA;;;;EACE;EACA;ANgkCF;AM9jCA;;;;EACE;ANmkCF;AMjkCA;EACE;ANmkCF;AMjkCA;EACE;ANmkCF;AMjkCA;EACE;ANmkCF;AMjkCA;EACE;ANmkCF;AMjkCA;EACE;EACA;ANmkCF;AMjkCA;;;;;;EACE;ANwkCF;AMtkCA;EACE;EACA;ANwkCF;AMtkCA;EACE;ANwkCF;AMtkCA;EACE;EACA;ANwkCF;AMtkCA;EACE;ANwkCF;AMtkCA;EACE;EACA;ANwkCF;AMtkCA;;;EACE;EACA;AN0kCF;AMxkCA;;;;;;;EACE;ANglCF;AM9kCA;EACE;ANglCF;AM9kCA;;EACE;EACA;ANilCF;AM/kCA;;EACE;EACA;ANklCF;AMhlCA;EACE;ANklCF;AMhlCA;EACE;EACA;ANklCF;AMhlCA;;EACE;ANmlCF;AMjlCA;EACE;ANmlCF;AMjlCA;EACE;ANmlCF;AMjlCA;EACE;EACA;ANmlCF;AMjlCA;;EACE;ANolCF;AMllCA;EACE;EACA;ANolCF;AMllCA;EACE;EACA;ANolCF;AMllCA;EACE;EACA;ANolCF;AMllCA;EACE;EACA;ANolCF;AMllCA;;EACE;EACA;ANqlCF;AMnlCA;EACE;EACA;ANqlCF;AMnlCA;EACE;ANqlCF;AMnlCA;EACE;ANqlCF;AMnlCA;EACE;EACA;ANqlCF;AMnlCA;EACE;EACA;ANqlCF;AMnlCA;EACE;ANqlCF;AMnlCA;EACE;ANqlCF;AMnlCA;;;;EACE;ANwlCF;AMtlCA;;EACE;EACA;ANylCF;AMvlCA;EACE;ANylCF;AMvlCA;EACE;ANylCF;AMvlCA;;;;EACE;AN4lCF;AMzlCA;EACE;AN2lCF;AMxlCA;EACE;AN0lCF;AMxlCE;EACE;AN0lCJ;AMzlCE;EACE;EACA;AN2lCJ;AMzlCE;EACE;AN2lCJ;AMxlCA;EACE;AN0lCF;AMxlCA;EACE;AN0lCF;AMvlCA;EACE;EACA;EACA;EACA;EACA;EAGA;ANulCF;AMnlCA;EACE;EACA;EACA;EACA;ANqlCF;AMjlCA;EACE;EACA;EACA;EACA;ANmlCF;AMjlCA;EACE;EACA;ANmlCF;AMjlCA;EACE;EACA;ANmlCF;AMhlCA;;EACE;ANmlCF;AMjlCA;EACE;ANmlCF;AACA;;;EAGE;AO7gDF;ELHE;AFmhDF;AOhhDA;ELAI;AFmhDJ;AOnhDA;;;;;;;ELUI;EACA;AFkhDJ;AO7hDA;ELgBI;AFghDJ;AE/gDI;EACE;EACA;AFihDN;AOpiDA;EAMQ;APiiDR;AOhiDQ;EACE;APkiDV;AO/hDM;EAGM;IACE;EP+hDZ;EOniDI;IAOQ;EP+hDZ;EO9hDY;IACE;EPgiDd;AACF;AOrjDA;EA8BM;AP0hDN;AOxjDA;EAoCI;APuhDJ;AO3jDA;;;ELsGI;AF09CJ;AOhkDA;;EL2GM;AFy9CN;AEn9CI;EACE;EACA;AFq9CN;AOxkDA;ELuHM;AFo9CN;AEj9CI;;EACE;EACA;EACA;AFo9CN;AOjlDA;ELiIM;EACA;AFm9CN;AOrlDA;ELuII;AFi9CJ;AEh9CI;EACE;AFk9CN;AO3lDA;ELgJQ;AF88CR;AE58CM;;EACE;AF+8CR;AOlmDA;ELgEE;EA0FE;EACA;AF48CJ;AOvmDA;;EL6JM;EACA;EACA;EACA;ECuBI;AH07CV;AOjnDA;ELoKM;EAhGJ;EACA;EACA;EACA;AFijDF;AEl9CM;;EACE;EACA;AFq9CR;AEn9CM;EACE;AFq9CR;AOhoDA;EL+KM;EA3GJ;EACA;EACA;EACA;AFgkDF;AO5lDA;EAEI;AP6lDJ;AOplDE;;;;EACE;EACA;EACA;APylDJ;AOrlDE;;EACE;EACA;EACA;APwlDJ;AOrlDE;;EACE;EACA;APwlDJ;AOrlDE;;EACE;EACA;APwlDJ;AOrlDA;EACE;EACA;APulDF;AOrlDA;EACE;APulDF;AOplDA;EACE;APslDF;AOrlDE;EACE;EACA;APulDJ;AOplDE;EACE;APslDJ;AOjlDE;EACE;EACA;APmlDJ;AO/kDA;EACE;APilDF;AO9kDA;EACE;APglDF;AO7kDA;EACE;AP+kDF;AO5kDA;EACE;AP8kDF;AO3kDA;EACE;AP6kDF;AO1kDA;EACE;AP4kDF;AOzkDA;EACE;AP2kDF;AOxkDA;EACE;EACA;AP0kDF;AOvkDA;EACE;APykDF;AOpkDA;EPskDE,wBAAwB;EOrkDxB;APukDF;AOrkDA;EPukDE,gBAAgB;EOtkDhB;APwkDF;AOtkDA;EPwkDE,WAAW;EOvkDX;APykDF;AOtkDA;;EACE;APykDF;AOtkDA;EACE;APwkDF;AOrkDA;EACE;APukDF;AOpkDA;;EACE;APukDF;AACA;;;EAGE;AQlvDF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;ERovDA,8BAA8B;EQnvD9B;EACA;EACA;ERqvDA,8CAA8C;EQpvD9C;ERsvDA,8CAA8C;EQrvD9C;ARuvDF;AQrvDA;ENjBE;AFywDF;AQxvDA;ENdI;AFywDJ;AQ3vDA;;;;;;;ENJI;EACA;AFwwDJ;AQrwDA;ENEI;AFswDJ;AErwDI;EACE;EACA;AFuwDN;AQ5wDA;EAMQ;ARywDR;AQxwDQ;EACE;AR0wDV;AQvwDM;EAGM;IACE;ERuwDZ;EQ3wDI;IAOQ;ERuwDZ;EQtwDY;IACE;ERwwDd;AACF;AQ7xDA;EA8BM;ARkwDN;AQhyDA;EAoCI;AR+vDJ;AQnyDA;;;ENwFI;AFgtDJ;AQxyDA;;EN6FM;AF+sDN;AEzsDI;EACE;EACA;AF2sDN;AQhzDA;ENyGM;AF0sDN;AEvsDI;;EACE;EACA;EACA;AF0sDN;AQzzDA;ENmHM;EACA;AFysDN;AQ7zDA;ENyHI;AFusDJ;AEtsDI;EACE;AFwsDN;AQn0DA;ENkIQ;AFosDR;AElsDM;;EACE;AFqsDR;AQ10DA;ENkDE;EA0FE;EACA;AFksDJ;AQ/0DA;;EN+IM;EACA;EACA;EACA;ECuBI;AHgrDV;AQz1DA;ENsJM;EAhGJ;EACA;EACA;EACA;AFuyDF;AExsDM;;EACE;EACA;AF2sDR;AEzsDM;EACE;AF2sDR;AQx2DA;ENiKM;EA3GJ;EACA;EACA;EACA;AFszDF;AQp0DA;EAEI;ARq0DJ;AQ5zDE;;;;EACE;EACA;EACA;ARi0DJ;AQ7zDE;;EACE;EACA;EACA;ARg0DJ;AQ7zDE;;EACE;EACA;ARg0DJ;AQ7zDE;;EACE;EACA;ARg0DJ;AQ7zDA;;;EACE;ARi0DF;AQ/zDC;;;EACC;ARm0DF;AQj0DA;EACE;ARm0DF;AQh0DA;EACE;ARk0DF;AQ/zDA;EACE;ARi0DF;AQh0DE;EACE;ARk0DJ;AQh0DE;EACE;ARk0DJ;AQ9zDA;EACE;ARg0DF;AQ9zDA;EACE;ARg0DF;AQ9zDA;EACE;ARg0DF;AQ9zDA;EACE;ARg0DF;AQ7zDA;;EACE;ARg0DF;AACA;;;EAGE;ASr8DF;EPHE;AF28DF;ASx8DA;EPAI;AF28DJ;AS38DA;;;;;;;EPUI;EACA;AF08DJ;ASr9DA;EPgBI;AFw8DJ;AEv8DI;EACE;EACA;AFy8DN;AS59DA;EAMQ;ATy9DR;ASx9DQ;EACE;AT09DV;ASv9DM;EAGM;IACE;IACA;ETu9DZ;ES59DI;IAQQ;ETu9DZ;ESt9DY;IACE;IACA;ETw9Dd;AACF;AS/+DA;EAgCM;ATk9DN;ASl/DA;EAsCI;AT+8DJ;ASr/DA;;;EPsGI;AFo5DJ;AS1/DA;;EP2GM;AFm5DN;AE74DI;EACE;EACA;AF+4DN;ASlgEA;EPuHM;AF84DN;AE34DI;;EACE;EACA;EACA;AF84DN;AS3gEA;EPiIM;EACA;AF64DN;AS/gEA;EPuII;AF24DJ;AE14DI;EACE;AF44DN;ASrhEA;EPgJQ;AFw4DR;AEt4DM;;EACE;AFy4DR;AS5hEA;EPgEE;EA0FE;EACA;AFs4DJ;ASjiEA;;EP6JM;EACA;EACA;EACA;ECuBI;AHo3DV;AS3iEA;EPoKM;EAhGJ;EACA;EACA;EACA;AF2+DF;AE54DM;;EACE;EACA;AF+4DR;AE74DM;EACE;AF+4DR;AS1jEA;EP+KM;EA3GJ;EACA;EACA;EACA;AF0/DF;ASphEA;EAEI;ATqhEJ;AS/gEA;;EACE;ATkhEF;AShhEE;;;;EACE;EACA;EACA;ATqhEJ;ASjhEE;;EACE;EACA;EACA;ATohEJ;ASjhEE;;EACE;EACA;ATohEJ;ASjhEE;;EACE;EACA;ATohEJ;AACA;;EAEE;AShhEF;EACE;ATkhEF;AS9gEE;EACE;ATghEJ;AS9gEE;EACE;ATghEJ;AS5gEA;EACE;AT8gEF;ASxgEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;ET0gEA,8BAA8B;ESzgE9B;EACA;EACA;ET2gEA,8CAA8C;ES1gE9C;ET4gEA,8CAA8C;ES3gE9C;ET6gEA,8CAA8C;ES5gE9C;ET8gEA,8BAA8B;ES7gE9B;ET+gEA,8BAA8B;AAChC;AS7gEA;EACE;AT+gEF;AS7gEA;EACE;AT+gEF;AS5gEA;EACE;EACA;AT8gEF;AS5gEA;EACE;EACA;AT8gEF;AS5gEA;EACE;AT8gEF;AS5gEA;EACE;AT8gEF;AS3gEA;EACE;AT6gEF;AS3gEA;EACE;AT6gEF;AS3gEA;EACE;EACA;AT6gEF;AS1gEA;;EACE;EAEA;EACA;AT6gEF;AS1gEA;EACE;AT4gEF;AS1gEA;EACE;AT4gEF;AS1gEA;;EACE;AT6gEF;AS1gEA;EACE;EACA;AT4gEF;ASzgEA;EACE;AT2gEF;ASxgEA;;EACE;AT2gEF;AS5gEA;;EAGI;EACA;AT6gEJ;ASxgEA;EACE;AT0gEF;ASxgEA;EACE;AT0gEF;ASvgEA;EACE;ATygEF;ASvgEA;EACE;ATygEF;ASvgEA;;EACE;EACA;AT0gEF;ASxgEA;;;EACE;EACA;AT4gEF;AS1gEA;EACE;EACA;AT4gEF;ASzgEA;EACE;EACA;AT2gEF;ASxgEA;;;;EACE;EACA;AT6gEF;AS3gEA;;;;EACE;ATghEF;AS9gEA;EACE;ATghEF;AS9gEA;EACE;ATghEF;AS9gEA;EACE;ATghEF;AS9gEA;EACE;ATghEF;AS9gEA;EACE;EACA;ATghEF;AS9gEA;;;;;;EACE;ATqhEF;ASnhEA;EACE;EACA;ATqhEF;ASnhEA;EACE;ATqhEF;ASnhEA;EACE;EACA;ATqhEF;ASnhEA;EACE;ATqhEF;ASnhEA;EACE;EACA;ATqhEF;ASnhEA;;;EACE;EACA;ATuhEF;ASrhEA;;;;;;;EACE;AT6hEF;AS3hEA;EACE;AT6hEF;AS3hEA;;EACE;EACA;AT8hEF;AS5hEA;;EACE;EACA;AT+hEF;AS7hEA;EACE;AT+hEF;AS7hEA;EACE;EACA;AT+hEF;AS7hEA;;EACE;ATgiEF;AS9hEA;EACE;ATgiEF;AS9hEA;EACE;ATgiEF;AS9hEA;EACE;EACA;ATgiEF;AS9hEA;;EACE;ATiiEF;AS/hEA;EACE;EACA;ATiiEF;AS/hEA;EACE;EACA;ATiiEF;AS/hEA;EACE;EACA;ATiiEF;AS/hEA;;EACE;EACA;ATkiEF;AShiEA;EACE;EACA;ATkiEF;AShiEA;EACE;ATkiEF;AShiEA;EACE;ATkiEF;AShiEA;EACE;EACA;ATkiEF;AShiEA;EACE;EACA;ATkiEF;AShiEA;EACE;ATkiEF;AShiEA;EACE;ATkiEF;AShiEA;;;;EACE;ATqiEF;ASniEA;;EACE;EACA;ATsiEF;ASpiEA;EACE;ATsiEF;ASpiEA;EACE;ATsiEF;ASpiEA;;;;EACE;ATyiEF;AStiEA;EACE;ATwiEF;ASriEA;EACE;ATuiEF;ASriEE;EACE;ATuiEJ;AStiEE;EACE;EACA;ATwiEJ;AStiEE;EACE;ATwiEJ;ASriEA;EACE;ATuiEF;ASriEA;EACE;ATuiEF;ASpiEA;EACE;EACA;EACA;EACA;EACA;EAGA;AToiEF;AShiEA;EACE;EACA;EACA;EACA;ATkiEF;AS9hEA;EACE;EACA;EACA;EACA;ATgiEF;AS9hEA;EACE;EACA;ATgiEF;AS9hEA;EACE;EACA;ATgiEF;AS5hEA;;EACE;AT+hEF;AS7hEA;EACE;AT+hEF;AACA;;;EAGE;AU78EF;ERHE;AFm9EF;AUh9EA;ERAI;AFm9EJ;AUn9EA;;;;;;;ERUI;EACA;AFk9EJ;AU79EA;ERgBI;AFg9EJ;AE/8EI;EACE;EACA;AFi9EN;AUp+EA;EAMQ;AVi+ER;AUh+EQ;EACE;AVk+EV;AU/9EM;EAGM;IACE;IACA;EV+9EZ;EUp+EI;IAQQ;EV+9EZ;EU99EY;IACE;IACA;EVg+Ed;AACF;AUv/EA;EAgCM;AV09EN;AU1/EA;EAsCI;AVu9EJ;AU7/EA;;;ERsGI;AF45EJ;AUlgFA;;ER2GM;AF25EN;AEr5EI;EACE;EACA;AFu5EN;AU1gFA;ERuHM;AFs5EN;AEn5EI;;EACE;EACA;EACA;AFs5EN;AUnhFA;ERiIM;EACA;AFq5EN;AUvhFA;ERuII;AFm5EJ;AEl5EI;EACE;AFo5EN;AU7hFA;ERgJQ;AFg5ER;AE94EM;;EACE;AFi5ER;AUpiFA;ERgEE;EA0FE;EACA;AF84EJ;AUziFA;;ER6JM;EACA;EACA;EACA;ECuBI;AH43EV;AUnjFA;ERoKM;EAhGJ;EACA;EACA;EACA;AFm/EF;AEp5EM;;EACE;EACA;AFu5ER;AEr5EM;EACE;AFu5ER;AUlkFA;ER+KM;EA3GJ;EACA;EACA;EACA;AFkgFF;AU5hFA;EAEI;AV6hFJ;AUvhFA;;EACE;AV0hFF;AUxhFE;;;;EACE;EACA;EACA;AV6hFJ;AUzhFE;;EACE;EACA;EACA;AV4hFJ;AUzhFE;;EACE;EACA;AV4hFJ;AUzhFE;;EACE;EACA;AV4hFJ;AACA;;EAEE;AUxhFF;EACE;AV0hFF;AUvhFA;EACE;AVyhFF;AUthFA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EVwhFA,8BAA8B;EUvhF9B;EACA;EACA;EVyhFA,8CAA8C;EUxhF9C;EV0hFA,8CAA8C;EUzhF9C;EV2hFA,8CAA8C;EU1hF9C;EV4hFA,8BAA8B;EU3hF9B;EV6hFA,8BAA8B;AAChC;AU3hFA;EACE;AV6hFF;AU3hFA;EACE;AV6hFF;AU1hFA;EACE;EACA;AV4hFF;AU1hFA;EACE;EACA;AV4hFF;AU1hFA;EACE;AV4hFF;AU1hFA;EACE;AV4hFF;AUzhFA;EACE;AV2hFF;AUzhFA;EACE;AV2hFF;AUzhFA;EACE;EACA;AV2hFF;AUxhFA;;EACE;EAEA;EACA;AV2hFF;AUzhFA;EACE;AV2hFF;AUxhFA;EACE;AV0hFF;AUxhFA;EACE;AV0hFF;AUvhFA;;EACE;AV0hFF;AUvhFA;EACE;EACA;AVyhFF;AUthFA;EACE;AVwhFF;AUrhFA;EACE;AVuhFF;AUphFA;EACE;AVshFF;AUnhFA;EACE;AVqhFF;AUlhFA;;EACE;AVqhFF;AUthFA;;EAGI;EACA;AVuhFJ;AUlhFA;EACE;AVohFF;AUlhFA;EACE;AVohFF;AUjhFA;EACE;AVmhFF;AUjhFA;EACE;AVmhFF;AUjhFA;;EACE;EACA;AVohFF;AUlhFA;;;EACE;EACA;AVshFF;AUphFA;EACE;EACA;AVshFF;AUphFA;EACE;AVshFF;AUphFA;EACE;EACA;AVshFF;AUnhFA;;;;EACE;EACA;AVwhFF;AUthFA;;;;EACE;EACA;AV2hFF;AUzhFA;EACE;AV2hFF;AUzhFA;EACE;AV2hFF;AUzhFA;EACE;AV2hFF;AUzhFA;EACE;AV2hFF;AUzhFA;EACE;EACA;AV2hFF;AUzhFA;;;;;;EACE;AVgiFF;AU9hFA;EACE;EACA;AVgiFF;AU9hFA;EACE;AVgiFF;AU9hFA;EACE;EACA;AVgiFF;AU9hFA;EACE;AVgiFF;AU9hFA;EACE;EACA;AVgiFF;AU9hFA;;;EACE;EACA;AVkiFF;AUhiFA;;;;;;;EACE;AVwiFF;AUtiFA;EACE;AVwiFF;AUtiFA;;EACE;EACA;AVyiFF;AUviFA;;EACE;EACA;AV0iFF;AUxiFA;EACE;AV0iFF;AUxiFA;EACE;EACA;AV0iFF;AUxiFA;;EACE;AV2iFF;AUziFA;EACE;AV2iFF;AUziFA;EACE;AV2iFF;AUziFA;EACE;EACA;AV2iFF;AUziFA;;EACE;AV4iFF;AU1iFA;EACE;EACA;AV4iFF;AU1iFA;EACE;EACA;AV4iFF;AU1iFA;EACE;EACA;AV4iFF;AU1iFA;EACE;EACA;AV4iFF;AU1iFA;;EACE;EACA;AV6iFF;AU3iFA;EACE;EACA;AV6iFF;AU3iFA;EACE;AV6iFF;AU3iFA;EACE;AV6iFF;AU3iFA;EACE;EACA;AV6iFF;AU3iFA;EACE;EACA;AV6iFF;AU3iFA;EACE;AV6iFF;AU3iFA;EACE;AV6iFF;AU3iFA;;;;EACE;AVgjFF;AU9iFA;;EACE;EACA;AVijFF;AU/iFA;EACE;AVijFF;AU/iFA;EACE;AVijFF;AU/iFA;;;;EACE;AVojFF;AUjjFA;EACE;AVmjFF;AUjjFA;EACE;AVmjFF;AUjjFA;EACE;AVmjFF;AUhjFA;EACE;EACA;EACA;EACA;EACA;EAGA;AVgjFF;AU5iFA;EACE;EACA;EACA;EACA;AV8iFF;AU1iFA;EACE;EACA;EACA;EACA;AV4iFF;AU1iFA;EACE;EACA;AV4iFF;AU1iFA;EACE;EACA;AV4iFF;AU1iFA;;EACE;AV6iFF;AU3iFA;EACE;AV6iFF;AACA;;;EAGE;AWt9FF;ETHE;AF49FF;AWz9FA;ETAI;AF49FJ;AW59FA;;;;;;;ETUI;EACA;AF29FJ;AWt+FA;ETgBI;AFy9FJ;AEx9FI;EACE;EACA;AF09FN;AW7+FA;EAMQ;AX0+FR;AWz+FQ;EACE;AX2+FV;AWx+FM;EAGM;IACE;EXw+FZ;EW5+FI;IAOQ;EXw+FZ;EWv+FY;IACE;EXy+Fd;AACF;AW9/FA;EA8BM;AXm+FN;AWjgGA;EAoCI;AXg+FJ;AWpgGA;;;ETsGI;AFm6FJ;AWzgGA;;ET2GM;AFk6FN;AE55FI;EACE;EACA;AF85FN;AWjhGA;ETuHM;AF65FN;AE15FI;;EACE;EACA;EACA;AF65FN;AW1hGA;ETiIM;EACA;AF45FN;AW9hGA;ETuII;AF05FJ;AEz5FI;EACE;AF25FN;AWpiGA;ETgJQ;AFu5FR;AEr5FM;;EACE;AFw5FR;AW3iGA;ETgEE;EA0FE;EACA;AFq5FJ;AWhjGA;;ET6JM;EACA;EACA;EACA;ECuBI;AHm4FV;AW1jGA;EToKM;EAhGJ;EACA;EACA;EACA;AF0/FF;AE35FM;;EACE;EACA;AF85FR;AE55FM;EACE;AF85FR;AWzkGA;ET+KM;EA3GJ;EACA;EACA;EACA;AFygGF;AWriGA;EAEI;AXsiGJ;AW7hGE;;;;EACE;EACA;EACA;AXkiGJ;AW9hGE;;EACE;EACA;EACA;AXiiGJ;AW9hGE;;EACE;EACA;AXiiGJ;AW9hGE;;EACE;EACA;AXiiGJ;AW5hGA;EACE;AX8hGF;AW7hGE;EACE;AX+hGJ;AW7hGE;EACE;AX+hGJ;AW3hGA;EACE;AX6hGF;AW1hGA;EACE;EACA;EACA;EACA;EX4hGA,8BAA8B;EW3hG9B;EACA;EACA;EX6hGA,8CAA8C;EW5hG9C;EX8hGA,8CAA8C;EW7hG9C;EX+hGA,8CAA8C;EW9hG9C;EXgiGA,8BAA8B;EW/hG9B;EXiiGA,8BAA8B;AAChC;AW/hGA;;;EACE;AXmiGF;AWjiGC;;;EACC;AXqiGF;AWliGA;EACE;AXoiGF;AWjiGA;EACE;AXmiGF;AWjiGA;EACE;AXmiGF;AWjiGA;;EACE;AXoiGF;AACA;;;EAGE;AYrqGF;EVHE;AF2qGF;AYxqGA;EVAI;AF2qGJ;AY3qGA;;;;;;;EVUI;EACA;AF0qGJ;AYrrGA;EVgBI;AFwqGJ;AEvqGI;EACE;EACA;AFyqGN;AY5rGA;EAMQ;AZyrGR;AYxrGQ;EACE;AZ0rGV;AYvrGM;EAGM;IACE;IACA;EZurGZ;EY5rGI;IAQQ;EZurGZ;EYtrGY;IACE;IACA;EZwrGd;AACF;AY/sGA;EAgCM;AZkrGN;AYltGA;EAsCI;AZ+qGJ;AYrtGA;;;EVsGI;AFonGJ;AY1tGA;;EV2GM;AFmnGN;AE7mGI;EACE;EACA;AF+mGN;AYluGA;EVuHM;AF8mGN;AE3mGI;;EACE;EACA;EACA;AF8mGN;AY3uGA;EViIM;EACA;AF6mGN;AY/uGA;EVuII;AF2mGJ;AE1mGI;EACE;AF4mGN;AYrvGA;EVgJQ;AFwmGR;AEtmGM;;EACE;AFymGR;AY5vGA;EVgEE;EA0FE;EACA;AFsmGJ;AYjwGA;;EV6JM;EACA;EACA;EACA;ECuBI;AHolGV;AY3wGA;EVoKM;EAhGJ;EACA;EACA;EACA;AF2sGF;AE5mGM;;EACE;EACA;AF+mGR;AE7mGM;EACE;AF+mGR;AY1xGA;EV+KM;EA3GJ;EACA;EACA;EACA;AF0tGF;AYpvGA;EAEI;AZqvGJ;AY/uGA;;EACE;AZkvGF;AYhvGE;;;;EACE;EACA;EACA;AZqvGJ;AYjvGE;;EACE;EACA;EACA;AZovGJ;AYjvGE;;EACE;EACA;AZovGJ;AYjvGE;;EACE;EACA;AZovGJ;AACA;;EAEE;AY/uGF;EACE;AZivGF;AY7uGE;EACE;AZ+uGJ;AY7uGE;EACE;AZ+uGJ;AY3uGA;EACE;AZ6uGF;AYvuGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EZyuGA,8BAA8B;EYxuG9B;EACA;EACA;EZ0uGA,8CAA8C;EYzuG9C;EZ2uGA,8CAA8C;EY1uG9C;EZ4uGA,8CAA8C;EY3uG9C;EZ6uGA,8BAA8B;EY5uG9B;EZ8uGA,8BAA8B;AAChC;AY5uGA;EACE;AZ8uGF;AY3uGA;EACE;EACA;AZ6uGF;AY3uGA;EACE;EACA;AZ6uGF;AY3uGA;EACE;AZ6uGF;AY3uGA;EACE;AZ6uGF;AY1uGA;EACE;AZ4uGF;AY1uGA;EACE;AZ4uGF;AY1uGA;EACE;EACA;AZ4uGF;AYzuGA;;EACE;EAEA;EACA;AZ4uGF;AYzuGA;EACE;AZ2uGF;AYzuGA;EACE;AZ2uGF;AYzuGA;;EACE;AZ4uGF;AY1uGA;EACE;EACA;AZ4uGF;AYzuGA;EACE;EACA;AZ2uGF;AYxuGA;EACE;AZ0uGF;AYxuGA;EACE;AZ0uGF;AYxuGA;EACE;AZ0uGF;AYvuGA;;EACE;AZ0uGF;AY3uGA;;EAGI;EACA;AZ4uGJ;AYxuGA;EACE;AZ0uGF;AYxuGA;EACE;AZ0uGF;AYxuGA;EACE;AZ0uGF;AYvuGA;EACE;AZyuGF;AYvuGA;EACE;AZyuGF;AYvuGA;;EACE;EACA;AZ0uGF;AYxuGA;;;EACE;EACA;AZ4uGF;AY1uGA;EACE;EACA;AZ4uGF;AYzuGA;EACE;EACA;AZ2uGF;AYxuGA;;;;EACE;EACA;AZ6uGF;AY3uGA;;;;EACE;AZgvGF;AY9uGA;EACE;AZgvGF;AY9uGA;EACE;AZgvGF;AY9uGA;EACE;AZgvGF;AY9uGA;EACE;AZgvGF;AY9uGA;EACE;EACA;AZgvGF;AY9uGA;;;;;;EACE;AZqvGF;AYnvGA;EACE;EACA;AZqvGF;AYnvGA;EACE;AZqvGF;AYnvGA;EACE;EACA;AZqvGF;AYnvGA;EACE;AZqvGF;AYnvGA;EACE;EACA;AZqvGF;AYnvGA;;;EACE;EACA;AZuvGF;AYrvGA;;;;;;;EACE;AZ6vGF;AY3vGA;EACE;AZ6vGF;AY3vGA;;EACE;EACA;AZ8vGF;AY5vGA;;EACE;EACA;AZ+vGF;AY7vGA;EACE;AZ+vGF;AY7vGA;EACE;EACA;AZ+vGF;AY7vGA;;EACE;AZgwGF;AY9vGA;EACE;AZgwGF;AY9vGA;EACE;AZgwGF;AY9vGA;EACE;EACA;AZgwGF;AY9vGA;;EACE;AZiwGF;AY/vGA;EACE;EACA;AZiwGF;AY/vGA;EACE;EACA;AZiwGF;AY/vGA;EACE;EACA;AZiwGF;AY/vGA;EACE;EACA;AZiwGF;AY/vGA;;EACE;EACA;AZkwGF;AYhwGA;EACE;EACA;AZkwGF;AYhwGA;EACE;AZkwGF;AYhwGA;EACE;AZkwGF;AYhwGA;EACE;EACA;AZkwGF;AYhwGA;EACE;EACA;AZkwGF;AYhwGA;EACE;AZkwGF;AYhwGA;EACE;AZkwGF;AYhwGA;;;;EACE;AZqwGF;AYnwGA;;EACE;EACA;AZswGF;AYpwGA;EACE;AZswGF;AYpwGA;EACE;AZswGF;AYpwGA;;;;EACE;AZywGF;AYtwGA;EACE;AZwwGF;AYrwGA;EACE;AZuwGF;AYrwGE;EACE;AZuwGJ;AYtwGE;EACE;EACA;AZwwGJ;AYtwGE;EACE;AZwwGJ;AYpwGA;EACE;EACA;EACA;EACA;EACA;EAGA;AZowGF;AYhwGA;EACE;EACA;EACA;EACA;AZkwGF;AY9vGA;EACE;EACA;EACA;EACA;AZgwGF;AY9vGA;EACE;EACA;AZgwGF;AY9vGA;EACE;EACA;AZgwGF;AY7vGA;;EACE;AZgwGF;AY9vGA;EACE;AZgwGF;AACA;;;EAGE;AarrHF;EXHE;AF2rHF;AaxrHA;EXAI;AF2rHJ;Aa3rHA;;;;;;;EXUI;EACA;AF0rHJ;AarsHA;EXgBI;AFwrHJ;AEvrHI;EACE;EACA;AFyrHN;Aa5sHA;EAMQ;AbysHR;AaxsHQ;EACE;Ab0sHV;AavsHM;EAGM;IACE;EbusHZ;Ea3sHI;IAOQ;EbusHZ;EatsHY;IACE;EbwsHd;AACF;Aa7tHA;EA8BM;AbksHN;AahuHA;EAoCI;Ab+rHJ;AanuHA;;;EXsGI;AFkoHJ;AaxuHA;;EX2GM;AFioHN;AE3nHI;EACE;EACA;AF6nHN;AahvHA;EXuHM;AF4nHN;AEznHI;;EACE;EACA;EACA;AF4nHN;AazvHA;EXiIM;EACA;AF2nHN;Aa7vHA;EXuII;AFynHJ;AExnHI;EACE;AF0nHN;AanwHA;EXgJQ;AFsnHR;AEpnHM;;EACE;AFunHR;Aa1wHA;EXgEE;EA0FE;EACA;AFonHJ;Aa/wHA;;EX6JM;EACA;EACA;EACA;ECuBI;AHkmHV;AazxHA;EXoKM;EAhGJ;EACA;EACA;EACA;AFytHF;AE1nHM;;EACE;EACA;AF6nHR;AE3nHM;EACE;AF6nHR;AaxyHA;EX+KM;EA3GJ;EACA;EACA;EACA;AFwuHF;AapwHA;EAEI;AbqwHJ;Aa5vHE;;;;EACE;EACA;EACA;AbiwHJ;Aa7vHE;;EACE;EACA;EACA;AbgwHJ;Aa7vHE;;EACE;EACA;AbgwHJ;Aa7vHE;;EACE;EACA;AbgwHJ;Aa3vHA;EACE;Ab6vHF;Aa5vHE;EACE;Ab8vHJ;Aa5vHE;EACE;Ab8vHJ;Aa1vHA;EACE;Ab4vHF;AazvHA;EACE;EACA;EACA;EACA;Eb2vHA,8BAA8B;Ea1vH9B;EACA;EACA;EACA;Eb4vHA,8CAA8C;Ea3vH9C;Eb6vHA,8CAA8C;Ea5vH9C;Eb8vHA,8CAA8C;Ea7vH9C;Eb+vHA,8BAA8B;Ea9vH9B;EbgwHA,8BAA8B;AAChC;Aa/vHA;EACE;AbiwHF;Aa9vHA;EACE;AbgwHF;Aa9vHA;;;EACE;AbkwHF;AahwHC;;;EACC;AbowHF;AajwHA;EACE;AbmwHF;AajwHA;EACE;AbmwHF;AahwHA;;EACE;AbmwHF;AajwHA;EACE;AbmwHF;AACA;;;EAGE;Ac34HF;EZHE;AFi5HF;Ac94HA;EZAI;AFi5HJ;Acj5HA;;;;;;;EZUI;EACA;AFg5HJ;Ac35HA;EZgBI;AF84HJ;AE74HI;EACE;EACA;AF+4HN;Acl6HA;EAMQ;Ad+5HR;Ac95HQ;EACE;Adg6HV;Ac75HM;EAGM;IACE;IACA;Ed65HZ;Ecl6HI;IAQQ;Ed65HZ;Ec55HY;IACE;IACA;Ed85Hd;AACF;Acr7HA;EAgCM;Adw5HN;Acx7HA;EAsCI;Adq5HJ;Ac37HA;;;EZsGI;AF01HJ;Ach8HA;;EZ2GM;AFy1HN;AEn1HI;EACE;EACA;AFq1HN;Acx8HA;EZuHM;AFo1HN;AEj1HI;;EACE;EACA;EACA;AFo1HN;Acj9HA;EZiIM;EACA;AFm1HN;Acr9HA;EZuII;AFi1HJ;AEh1HI;EACE;AFk1HN;Ac39HA;EZgJQ;AF80HR;AE50HM;;EACE;AF+0HR;Acl+HA;EZgEE;EA0FE;EACA;AF40HJ;Acv+HA;;EZ6JM;EACA;EACA;EACA;ECuBI;AH0zHV;Acj/HA;EZoKM;EAhGJ;EACA;EACA;EACA;AFi7HF;AEl1HM;;EACE;EACA;AFq1HR;AEn1HM;EACE;AFq1HR;AchgIA;EZ+KM;EA3GJ;EACA;EACA;EACA;AFg8HF;Ac19HA;EAEI;Ad29HJ;Acv9HA;;EACE;Ad09HF;Acx9HE;;;;EACE;EACA;EACA;Ad69HJ;Acz9HE;;EACE;EACA;EACA;Ad49HJ;Acz9HE;;EACE;EACA;Ad49HJ;Acz9HE;;EACE;EACA;Ad49HJ;AACA;;EAEE;Acv9HF;EACE;Ady9HF;Acp9HE;EACE;Ads9HJ;Acp9HE;EACE;Ads9HJ;Acl9HA;EACE;Ado9HF;Ac98HA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;Edg9HA,8BAA8B;Ec/8H9B;EACA;EACA;EACA;Edi9HA,8CAA8C;Ech9H9C;Edk9HA,8CAA8C;Ecj9H9C;Edm9HA,8CAA8C;Ecl9H9C;Edo9HA,8BAA8B;Ecn9H9B;Edq9HA,8BAA8B;AAChC;Acn9HA;EACE;Adq9HF;Acj9HA;EACE;EACA;Adm9HF;Acj9HA;EACE;EACA;Adm9HF;Acj9HA;EACE;Adm9HF;Acj9HA;EACE;Adm9HF;Ach9HA;EACE;Adk9HF;Ach9HA;EACE;Adk9HF;Ach9HA;EACE;EACA;Adk9HF;Ach9HA;;EACE;EAEA;EACA;Adm9HF;Ach9HA;EACE;Adk9HF;Ach9HA;EACE;Adk9HF;Ac/8HA;;EACE;Adk9HF;Ac/8HA;EACE;EACA;Adi9HF;Ac98HA;EACE;Adg9HF;Ac98HA;EACE;Adg9HF;Ac98HA;EACE;Adg9HF;Ac98HA;EACE;Adg9HF;Ac78HA;EACE;Ad+8HF;Ac58HA;;EACE;Ad+8HF;Ach9HA;;EAGI;EACA;Adi9HJ;Ac58HA;EACE;Ad88HF;Ac58HA;EACE;Ad88HF;Ac38HA;EACE;Ad68HF;Ac38HA;EACE;Ad68HF;Ac38HA;;EACE;EACA;Ad88HF;Ac58HA;;;EACE;EACA;Adg9HF;Ac98HA;EACE;Adg9HF;Ac78HA;EACE;EACA;Ad+8HF;Ac58HA;;;;EACE;EACA;Adi9HF;Ac/8HA;;;;EACE;Ado9HF;Acl9HA;EACE;Ado9HF;Acl9HA;EACE;Ado9HF;Acl9HA;EACE;Ado9HF;Acl9HA;EACE;Ado9HF;Acl9HA;EACE;EACA;Ado9HF;Acl9HA;;;;;;EACE;Ady9HF;Acv9HA;EACE;EACA;Ady9HF;Acv9HA;EACE;Ady9HF;Acv9HA;EACE;EACA;Ady9HF;Acv9HA;EACE;Ady9HF;Acv9HA;EACE;EACA;Ady9HF;Acv9HA;;;EACE;EACA;Ad29HF;Acz9HA;;;;;;;EACE;Adi+HF;Ac/9HA;EACE;Adi+HF;Ac/9HA;;EACE;EACA;Adk+HF;Ach+HA;;EACE;EACA;Adm+HF;Acj+HA;EACE;Adm+HF;Acj+HA;EACE;EACA;Adm+HF;Acj+HA;;EACE;Ado+HF;Acl+HA;EACE;Ado+HF;Acl+HA;EACE;Ado+HF;Acl+HA;EACE;EACA;Ado+HF;Acl+HA;;EACE;Adq+HF;Acn+HA;EACE;EACA;Adq+HF;Acn+HA;EACE;EACA;Adq+HF;Acn+HA;EACE;EACA;Adq+HF;Acn+HA;EACE;EACA;Adq+HF;Acn+HA;;EACE;EACA;Ads+HF;Acp+HA;EACE;EACA;Ads+HF;Acp+HA;EACE;Ads+HF;Acp+HA;EACE;Ads+HF;Acp+HA;EACE;EACA;Ads+HF;Acp+HA;EACE;EACA;Ads+HF;Acp+HA;EACE;Ads+HF;Acp+HA;EACE;Ads+HF;Acp+HA;;;;EACE;Ady+HF;Acv+HA;;EACE;EACA;Ad0+HF;Acx+HA;EACE;Ad0+HF;Acx+HA;EACE;Ad0+HF;Acx+HA;;;;EACE;Ad6+HF;Ac1+HA;EACE;Ad4+HF;Ac1+HA;;;EACE;EACA;Ad8+HF;Ac3+HA;EACE;Ad6+HF;Ac3+HE;EACE;Ad6+HJ;Ac3+HE;EACE;EACA;Ad6+HJ;Ac3+HE;EACE;Ad6+HJ;Ac1+HA;EACE;Ad4+HF;Ac1+HA;EACE;Ad4+HF;Ac1+HA;EACE;EACA;EACA;EACA;EACA;EAGA;Ad0+HF;Act+HA;EACE;EACA;EACA;EACA;Adw+HF;Acp+HA;EACE;EACA;EACA;EACA;Ads+HF;Acn+HA;EACE;EACA;Adq+HF;Acn+HA;EACE;EACA;Adq+HF;Acl+HA;;EACE;Adq+HF;Acn+HA;EACE;Adq+HF;AACA;;;EAGE;Aet6IF;EbHE;AF46IF;Aez6IA;EbAI;AF46IJ;Ae56IA;;;;;;;EbUI;EACA;AF26IJ;Aet7IA;EbgBI;AFy6IJ;AEx6II;EACE;EACA;AF06IN;Ae77IA;EAMQ;Af07IR;Aez7IQ;EACE;Af27IV;Aex7IM;EAGM;IACE;Efw7IZ;Ee57II;IAOQ;Efw7IZ;Eev7IY;IACE;Efy7Id;AACF;Ae98IA;EA8BM;Afm7IN;Aej9IA;EAoCI;Afg7IJ;Aep9IA;;;EbsGI;AFm3IJ;Aez9IA;;Eb2GM;AFk3IN;AE52II;EACE;EACA;AF82IN;Aej+IA;EbuHM;AF62IN;AE12II;;EACE;EACA;EACA;AF62IN;Ae1+IA;EbiIM;EACA;AF42IN;Ae9+IA;EbuII;AF02IJ;AEz2II;EACE;AF22IN;Aep/IA;EbgJQ;AFu2IR;AEr2IM;;EACE;AFw2IR;Ae3/IA;EbgEE;EA0FE;EACA;AFq2IJ;AehgJA;;Eb6JM;EACA;EACA;EACA;ECuBI;AHm1IV;Ae1gJA;EboKM;EAhGJ;EACA;EACA;EACA;AF08IF;AE32IM;;EACE;EACA;AF82IR;AE52IM;EACE;AF82IR;AezhJA;Eb+KM;EA3GJ;EACA;EACA;EACA;AFy9IF;Aer/IA;EAEI;Afs/IJ;Ae7+IE;;;;EACE;EACA;EACA;Afk/IJ;Ae9+IE;;EACE;EACA;EACA;Afi/IJ;Ae9+IE;;EACE;EACA;Afi/IJ;Ae9+IE;;EACE;EACA;Afi/IJ;Ae5+IA;EACE;Af8+IF;Ae7+IE;EACE;Af++IJ;Ae7+IE;EACE;Af++IJ;Ae3+IA;EACE;Af6+IF;Ae3+IA;EACE;EACA;EACA;EACA;Ef6+IA,8BAA8B;Ee5+I9B;EACA;EACA;Ef8+IA,8CAA8C;Ee7+I9C;Ef++IA,8CAA8C;Ee9+I9C;Efg/IA,8CAA8C;Ee/+I9C;Efi/IA,8BAA8B;Eeh/I9B;Efk/IA,8BAA8B;AAChC;Aeh/IA;;;EACE;Afo/IF;Ael/IC;;;EACC;Afs/IF;Aep/IA;EACE;Afs/IF;Aen/IA;EACE;Afq/IF;Aen/IA;EACE;Afq/IF;Ael/IA;;EACE;Afq/IF;AACA;;;EAGE;AgBrnJF;EdHE;AF2nJF;AgBxnJA;EdAI;AF2nJJ;AgB3nJA;;;;;;;EdUI;EACA;AF0nJJ;AgBroJA;EdgBI;AFwnJJ;AEvnJI;EACE;EACA;AFynJN;AgB5oJA;EAMQ;AhByoJR;AgBxoJQ;EACE;AhB0oJV;AgBvoJM;EAGM;IACE;EhBuoJZ;EgB3oJI;IAOQ;EhBuoJZ;EgBtoJY;IACE;EhBwoJd;AACF;AgB7pJA;EA8BM;AhBkoJN;AgBhqJA;EAoCI;AhB+nJJ;AgBnqJA;;;EdsGI;AFkkJJ;AgBxqJA;;Ed2GM;AFikJN;AE3jJI;EACE;EACA;AF6jJN;AgBhrJA;EduHM;AF4jJN;AEzjJI;;EACE;EACA;EACA;AF4jJN;AgBzrJA;EdiIM;EACA;AF2jJN;AgB7rJA;EduII;AFyjJJ;AExjJI;EACE;AF0jJN;AgBnsJA;EdgJQ;AFsjJR;AEpjJM;;EACE;AFujJR;AgB1sJA;EdgEE;EA0FE;EACA;AFojJJ;AgB/sJA;;Ed6JM;EACA;EACA;EACA;ECuBI;AHkiJV;AgBztJA;EdoKM;EAhGJ;EACA;EACA;EACA;AFypJF;AE1jJM;;EACE;EACA;AF6jJR;AE3jJM;EACE;AF6jJR;AgBxuJA;Ed+KM;EA3GJ;EACA;EACA;EACA;AFwqJF;AgBpsJA;EAEI;EACA;AhBqsJJ;AgB5rJE;;;;EACE;EACA;EACA;AhBisJJ;AgB7rJE;;EACE;EACA;EACA;AhBgsJJ;AgB7rJE;;EACE;EACA;AhBgsJJ;AgB7rJE;;EACE;EACA;AhBgsJJ;AgB7rJA;;;EACE;AhBisJF;AgB/rJC;;;EACC;AhBmsJF;AgBhsJA;EACE;AhBksJF;AgBjsJE;EACE;AhBmsJJ;AgBjsJE;EACE;AhBmsJJ;AgB/rJA;EACE;AhBisJF;AgB9rJA;EACE;EACA;EACA;EACA;EhBgsJA,8BAA8B;EgB/rJ9B;EACA;EACA;EhBisJA,8CAA8C;EgBhsJ9C;EhBksJA,8CAA8C;EgBjsJ9C;EhBmsJA,8CAA8C;EgBlsJ9C;EhBosJA,8BAA8B;EgBnsJ9B;EhBqsJA,8BAA8B;AAChC;AgBnsJA;EACE;AhBqsJF;AgBnsJA;EACE;AhBqsJF;AgBlsJA;;EACE;AhBqsJF;AgBnsJA;;EACE;AhBssJF;AgBpsJA;EACE;AhBssJF;AgBpsJC;EACC;AhBssJF;AgBpsJA;EACE;AhBssJF;AgBpsJA;EACE;AhBssJF;AACA;;;EAGE;AiBl1JF;EAKQ;AjBg1JR;AiB/0JQ;EACE;AjBi1JV;AiB90JM;EAGM;IACE;IACA;EjB80JZ;EiBn1JI;IAQQ;EjB80JZ;EiB70JY;IACE;IACA;EjB+0Jd;AACF;AiBr2JA;EA+BM;AjBy0JN;AiBx2JA;EAqCI;AjBs0JJ;AiB32JA;;;EfsGI;AF0wJJ;AiBh3JA;;Ef2GM;AFywJN;AEnwJI;EACE;EACA;AFqwJN;AiBx3JA;EfuHM;AFowJN;AEjwJI;;EACE;EACA;EACA;AFowJN;AiBj4JA;EfiIM;EACA;AFmwJN;AiBr4JA;EfuII;AFiwJJ;AEhwJI;EACE;AFkwJN;AiB34JA;EfgJQ;AF8vJR;AE5vJM;;EACE;AF+vJR;AiBl5JA;EfgEE;EA0FE;EACA;AF4vJJ;AiBv5JA;;Ef6JM;EACA;EACA;EACA;ECuBI;AH0uJV;AiBj6JA;EfoKM;EAhGJ;EACA;EACA;EACA;AFi2JF;AElwJM;;EACE;EACA;AFqwJR;AEnwJM;EACE;AFqwJR;AiBh7JA;Ef+KM;EA3GJ;EACA;EACA;EACA;AFg3JF;AiBz4JE;;;;EACE;EACA;EACA;AjB84JJ;AiB34JE;;EACE;EACA;EACA;AjB84JJ;AiB34JE;;EACE;EACA;AjB84JJ;AiB34JE;;EACE;EACA;AjB84JJ;AiB34JA;EACE;AjB64JF;AiB34JA;;EACE;AjB84JF;AACA;;EAEE;AiB54JF;EACE;AjB84JF;AiB14JE;EACE;AjB44JJ;AiB14JE;EACE;AjB44JJ;AiB14JE;EACE;AjB44JJ;AiBx4JA;EACE;AjB04JF;AiBp4JA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EjBs4JA,8BAA8B;EiBr4J9B;EACA;EACA;EjBu4JA,8CAA8C;EiBt4J9C;EjBw4JA,8CAA8C;EiBv4J9C;EjBy4JA,8CAA8C;EiBx4J9C;EjB04JA,8BAA8B;EiBz4J9B;EjB24JA,8BAA8B;AAChC;AiBz4JA;EACE;AjB24JF;AiBx4JA;EACE;EACA;AjB04JF;AiBx4JA;EACE;EACA;EACA;AjB04JF;AiBx4JA;EACE;AjB04JF;AiBx4JA;EACE;AjB04JF;AiBv4JA;EACE;AjBy4JF;AiBv4JA;EACE;AjBy4JF;AiBv4JA;EACE;EACA;AjBy4JF;AiBv4JA;;EACE;EAEA;EACA;AjB04JF;AiBx4JA;EACE;AjB04JF;AiBx4JA;EACE;AjB04JF;AiBx4JA;EACE;AjB04JF;AiBx4JA;;EACE;AjB24JF;AiBz4JA;EACE;AjB24JF;AiBx4JA;EACE;EACA;AjB04JF;AiBv4JA;EACE;AjBy4JF;AiBv4JA;EACE;AjBy4JF;AiBt4JA;;EACE;AjBy4JF;AiB14JA;;EAGI;EACA;AjB24JJ;AiBx4JA;EACE;AjB04JF;AiBv4JA;EACE;AjBy4JF;AiBv4JA;EACE;AjBy4JF;AiBt4JA;EACE;AjBw4JF;AiBt4JA;EACE;AjBw4JF;AiBt4JA;;EACE;EACA;AjBy4JF;AiBv4JA;;;EACE;EACA;AjB24JF;AiBz4JA;EACE;AjB24JF;AiBv4JA;EACE;EACA;AjBy4JF;AiBt4JA;;;;EACE;EACA;AjB24JF;AiBz4JA;;;;EACE;AjB84JF;AiB54JA;EACE;EACA;AjB84JF;AiB54JA;EACE;AjB84JF;AiB54JA;EACE;AjB84JF;AiB54JA;EACE;AjB84JF;AiB54JA;EACE;EACA;AjB84JF;AiB54JA;;;;;;EACE;AjBm5JF;AiBj5JA;EACE;EACA;AjBm5JF;AiBj5JA;EACE;AjBm5JF;AiBj5JA;EACE;EACA;AjBm5JF;AiBj5JA;EACE;EACA;AjBm5JF;AiBj5JA;;;EACE;EACA;AjBq5JF;AiBn5JA;;;;;;;EACE;AjB25JF;AiBz5JA;EACE;AjB25JF;AiBz5JA;;EACE;EACA;AjB45JF;AiB15JA;;EACE;EACA;AjB65JF;AiB35JA;EACE;AjB65JF;AiB35JA;EACE;EACA;AjB65JF;AiB35JA;;EACE;AjB85JF;AiB55JA;EACE;AjB85JF;AiB55JA;EACE;AjB85JF;AiB55JA;EACE;EACA;AjB85JF;AiB55JA;;EACE;AjB+5JF;AiB75JA;EACE;EACA;AjB+5JF;AiB75JA;EACE;EACA;AjB+5JF;AiB75JA;EACE;EACA;AjB+5JF;AiB75JA;EACE;EACA;AjB+5JF;AiB75JA;;EACE;EACA;AjBg6JF;AiB95JA;EACE;EACA;AjBg6JF;AiB95JA;EACE;AjBg6JF;AiB95JA;EACE;AjBg6JF;AiB95JA;EACE;EACA;AjBg6JF;AiB95JA;EACE;EACA;AjBg6JF;AiB95JA;EACE;AjBg6JF;AiB95JA;EACE;AjBg6JF;AiB95JA;EACE;AjBg6JF;AiB95JA;;EACE;EACA;AjBi6JF;AiB/5JA;EACE;AjBi6JF;AiB/5JA;EACE;AjBi6JF;AiB/5JA;EACE;AjBi6JF;AiB/5JA;;;;EACE;AjBo6JF;AiBj6JA;EACE;AjBm6JF;AiBh6JA;EACE;EACA;EACA;EACA;EACA;EAGA;AjBg6JF;AiB75JA;EACE;EACA;EACA;EACA;AjB+5JF;AiB55JA;EACE;EACA;EACA;EACA;AjB85JF;AiB35JA;EACE;EACA;AjB65JF;AiB35JA;EACE;EACA;AjB65JF;AiB15JA;;EACE;AjB65JF;AiB35JA;EACE;AjB65JF","sources":["webpack:///./resources/assets/less/skins/_all-skins.less","webpack:///./resources/assets/less/skins/skin-black.less","webpack:///./resources/assets/less/mixins.less","webpack:///./resources/assets/less/bootstrap-less/mixins/vendor-prefixes.less","webpack:///./resources/assets/less/skins/skin-black-dark.less","webpack:///./resources/assets/less/skins/skin-blue.less","webpack:///./resources/assets/less/skins/skin-blue-dark.less","webpack:///./resources/assets/less/skins/skin-contrast.less","webpack:///./resources/assets/less/skins/skin-green.less","webpack:///./resources/assets/less/skins/skin-green-dark.less","webpack:///./resources/assets/less/skins/skin-orange-dark.less","webpack:///./resources/assets/less/skins/skin-purple.less","webpack:///./resources/assets/less/skins/skin-purple-dark.less","webpack:///./resources/assets/less/skins/skin-red.less","webpack:///./resources/assets/less/skins/skin-red-dark.less","webpack:///./resources/assets/less/skins/skin-orange.less","webpack:///./resources/assets/less/skins/skin-yellow.less","webpack:///./resources/assets/less/skins/skin-yellow-dark.less"],"sourcesContent":["/*\n * Skin: Black\n * ----------\n */\n.skin-black .main-header .navbar {\n background-color: #111;\n}\n.skin-black .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-black .main-header .navbar .nav > li > a:hover,\n.skin-black .main-header .navbar .nav > li > a:active,\n.skin-black .main-header .navbar .nav > li > a:focus,\n.skin-black .main-header .navbar .nav .open > a,\n.skin-black .main-header .navbar .nav .open > a:hover,\n.skin-black .main-header .navbar .nav .open > a:focus,\n.skin-black .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-black .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-black .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-black .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-black .main-header .navbar .sidebar-toggle:hover {\n background-color: #040404;\n}\n@media (max-width: 767px) {\n .skin-black .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n .skin-black .main-header .navbar .dropdown-menu li a {\n color: #333;\n }\n .skin-black .main-header .navbar .dropdown-menu li a:hover {\n background: #040404;\n }\n}\n.skin-black .main-header li.user-header {\n background-color: #111;\n}\n.skin-black .content-header {\n background: transparent;\n}\n.skin-black .wrapper,\n.skin-black .main-sidebar,\n.skin-black .left-side {\n background-color: #222d32;\n}\n.skin-black .user-panel > .info,\n.skin-black .user-panel > .info > a {\n color: #fff;\n}\n.skin-black .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-black .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-black .sidebar-menu > li:hover > a,\n.skin-black .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #111;\n}\n.skin-black .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-black .sidebar a {\n color: #b8c7ce;\n}\n.skin-black .sidebar a:hover {\n text-decoration: none;\n}\n.skin-black .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-black .treeview-menu > li.active > a,\n.skin-black .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-black .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-black .sidebar-form input[type=\"text\"],\n.skin-black .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-black .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-black .sidebar-form input[type=\"text\"]:focus,\n.skin-black .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-black .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-black .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-black.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn,\n.btn:hover {\n color: #000;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #505156;\n border-color: #FFF;\n color: #FFF;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #111;\n border-color: #1f1f21;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n color: #fff;\n}\n.btn.btn-white:visited,\n.btn:hover.btn-white:visited {\n color: #fff;\n}\na {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--visited-link);\n}\n.text-primary {\n color: #000000;\n}\n:root {\n --button-default: #000000;\n --button-primary: #000000;\n --button-hover: #000000;\n --header: #111;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #black;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #111;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #999999;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\na.btn-info:link,\na.btn-warning:link,\na.btn-danger:link {\n color: #FFF;\n}\na.btn-info:visited,\na.btn-warning:visited,\na.btn-danger:visited {\n color: #FFF;\n}\n.btn-danger.btn-sm.disabled {\n color: #FFF;\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.sidebar-toggle-mobile {\n color: #FFF !important;\n}\n.skin-black .main-header .navbar .nav > li > a,\n.skin-black .main-header .navbar .nav > li > a {\n text-decoration: none;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #111;\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n/*\n * Skin: black dark\n * ----------\n */\n.skin-black-dark .main-header .navbar {\n background-color: #111;\n}\n.skin-black-dark .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-black-dark .main-header .navbar .nav > li > a:hover,\n.skin-black-dark .main-header .navbar .nav > li > a:active,\n.skin-black-dark .main-header .navbar .nav > li > a:focus,\n.skin-black-dark .main-header .navbar .nav .open > a,\n.skin-black-dark .main-header .navbar .nav .open > a:hover,\n.skin-black-dark .main-header .navbar .nav .open > a:focus,\n.skin-black-dark .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-black-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-black-dark .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-black-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-black-dark .main-header .navbar .sidebar-toggle:hover {\n background-color: #040404;\n}\n@media (max-width: 767px) {\n .skin-black-dark .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .skin-black-dark .main-header .navbar .dropdown-menu li a {\n color: #333;\n }\n .skin-black-dark .main-header .navbar .dropdown-menu li a:hover {\n background: #040404;\n color: #fff;\n }\n}\n@media (max-width: 991px) {\n .skin-black-dark .main-header .navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-sub);\n }\n}\n.skin-black-dark .main-header li.user-header {\n background-color: #111;\n}\n.skin-black-dark .content-header {\n background: transparent;\n}\n.skin-black-dark .wrapper,\n.skin-black-dark .main-sidebar,\n.skin-black-dark .left-side {\n background-color: #222d32;\n}\n.skin-black-dark .user-panel > .info,\n.skin-black-dark .user-panel > .info > a {\n color: #fff;\n}\n.skin-black-dark .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-black-dark .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-black-dark .sidebar-menu > li:hover > a,\n.skin-black-dark .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #111;\n}\n.skin-black-dark .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-black-dark .sidebar a {\n color: #b8c7ce;\n}\n.skin-black-dark .sidebar a:hover {\n text-decoration: none;\n}\n.skin-black-dark .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-black-dark .treeview-menu > li.active > a,\n.skin-black-dark .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-black-dark .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-black-dark .sidebar-form input[type=\"text\"],\n.skin-black-dark .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-black-dark .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-black-dark .sidebar-form input[type=\"text\"]:focus,\n.skin-black-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-black-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-black-dark .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-black-dark.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn,\n.btn:hover {\n color: #fff;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #000000;\n border-color: #FFF;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #000000;\n border-color: #000000;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #000000;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #000000;\n color: #fff;\n}\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\na:hover {\n color: #fff;\n}\na:visited {\n color: #fff;\n}\n.text-primary {\n color: #fff;\n}\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #3d4144;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #000000;\n --button-primary: #000000;\n --button-hover: #000000;\n --header: #111;\n /* Use same as Header picker */\n --text-main: #fff;\n --text-sub: #9b9b9b;\n --link: #fff;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #fff;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #949494;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\n#ldapad_test_results.well.well-sm {\n color: var(--back-main);\n}\na.actions {\n color: #fff !important;\n}\n.pagination > li > a {\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover {\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress {\n background-color: var(--background);\n}\na:hover > h2.task_menu {\n color: var(--header);\n}\nh2.task_menu {\n color: var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-main);\n color: var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu > li.header {\n background-color: var(--header);\n color: var(--link);\n}\n.main-header .navbar,\n.main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%, var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);\n border-color: var(--header);\n}\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color: #000;\n}\n.thead,\n.navbar-nav > li > a:link {\n color: var(--nav-link);\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.small-box h3,\n.small-box p {\n color: var(--nav-link) !important;\n}\n.small-box h3 a:hover,\n.small-box p a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n}\na:link.btn-default {\n color: var(--nav-link);\n}\n/* Trying to figure out a way of having links different shades based on what row they are on (even/odd).\na unique challenge for this dark mode skin.\nProbably writing the specificity for this incorrectly. --Godfrey Martinez 02/04/21\n\n.table-striped>tbody>tr:nth-of-type(even)>a:link{\n color:#000d07;\n}\n.table-striped>tbody>tr:nth-of-type(odd)>a:link{\n color:#bfbfbf;\n\n}*/\n#accessoriesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr.selected > td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box,\n.box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body,\n.box-footer,\n.box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n.btn-default:active,\n.btn-default:focus,\n.btn-default:hover,\n.btn-default.active {\n background-color: var(--button-hover);\n color: var(--nav-link);\n}\n.btn-primary,\n.btn-primary.hover,\n.btn-primary:active,\n.text-black {\n color: var(--nav-link) !important;\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n color: var(--link) !important;\n}\n#componentsTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#consumablesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form > div > div > div > span > i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover,\n.datepicker .next:hover,\n.datepicker .prev:hover,\n.datepicker tfoot tr th:hover,\n.datepicker table tr td.day.focused,\n.datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details > div > div > div > table {\n background-color: transparent;\n}\n.dropdown-menu > li > a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text],\ninput[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: var(--back-sub) !important;\n}\n.input-group,\n.input-group-addon {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n#licensesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li > a,\n.nav-tabs-custom > .nav-tabs > li.active > a {\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom,\n.nav-tabs-custom > .tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open > .dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default > .panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true],\n.select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--nav-link);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--back-sub);\n color: var(--visited-link);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub) !important;\n color: var(--text-main) !important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-black-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-black-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #000000;\n}\n.fixed-table-body thead th .th-inner,\n.skin-black-dark .sidebar-menu > li.active > a,\n.skin-black .sidebar-menu > li:hover > a,\n.sidebar-toggle:hover {\n background-color: var(--header) !important;\n}\n.tab-content,\n.tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.table-striped > tbody > tr:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa,\n.box-body,\n.box-footer,\n.box-header {\n color: var(--text-main);\n}\n.box-header.with-border {\n border-bottom: #000;\n}\na {\n color: var(--link);\n}\na:link {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n text-decoration: underline;\n}\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n display: table;\n}\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\ndiv.container.row-new-striped {\n background-color: var(--back-sub);\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: var(--back-sub);\n}\n.table > tbody > tr.warning > td {\n background-color: var(--back-sub);\n}\n/*\n * Skin: Blue\n * ----------\n */\n.skin-blue .main-header .navbar {\n background-color: #3c8dbc;\n}\n.skin-blue .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-blue .main-header .navbar .nav > li > a:hover,\n.skin-blue .main-header .navbar .nav > li > a:active,\n.skin-blue .main-header .navbar .nav > li > a:focus,\n.skin-blue .main-header .navbar .nav .open > a,\n.skin-blue .main-header .navbar .nav .open > a:hover,\n.skin-blue .main-header .navbar .nav .open > a:focus,\n.skin-blue .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-blue .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-blue .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-blue .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-blue .main-header .navbar .sidebar-toggle:hover {\n background-color: #367fa9;\n}\n@media (max-width: 767px) {\n .skin-blue .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n .skin-blue .main-header .navbar .dropdown-menu li a {\n color: #333;\n }\n .skin-blue .main-header .navbar .dropdown-menu li a:hover {\n background: #367fa9;\n }\n}\n.skin-blue .main-header li.user-header {\n background-color: #3c8dbc;\n}\n.skin-blue .content-header {\n background: transparent;\n}\n.skin-blue .wrapper,\n.skin-blue .main-sidebar,\n.skin-blue .left-side {\n background-color: #222d32;\n}\n.skin-blue .user-panel > .info,\n.skin-blue .user-panel > .info > a {\n color: #fff;\n}\n.skin-blue .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-blue .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-blue .sidebar-menu > li:hover > a,\n.skin-blue .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #3c8dbc;\n}\n.skin-blue .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-blue .sidebar a {\n color: #b8c7ce;\n}\n.skin-blue .sidebar a:hover {\n text-decoration: none;\n}\n.skin-blue .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-blue .treeview-menu > li.active > a,\n.skin-blue .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-blue .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-blue .sidebar-form input[type=\"text\"],\n.skin-blue .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-blue .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-blue .sidebar-form input[type=\"text\"]:focus,\n.skin-blue .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-blue .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-blue .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-blue.layout-top-nav .main-header > .logo .logo-variant {\n background-color: unset;\n}\n.btn.btn-primary,\nbtn-sm.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\nbtn-sm .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #307095;\n border-color: #23536f;\n color: #fff !important;\n}\n.btna.btn-primary:hover,\nbtn-sma.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #23536f;\n border-color: #23536f;\n color: #fff;\n}\n.btn.btn-white:link,\nbtn-sm.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #307095;\n color: #fff;\n}\n.btn.btn-white:hover,\nbtn-sm.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #173648;\n color: #fff;\n}\n.btn.btn-white:visited,\nbtn-sm.btn-white:visited,\n.btn:hover.btn-white:visited {\n background-color: #173648;\n color: #fff;\n}\n.btn-warning,\n.btn-warning:link,\na.btn-warning:hover,\n.btn-warning:link,\n.btn-warning:visited,\n.btn-danger,\n.btn-danger:link,\na.btn-danger:hover,\n.btn-danger:link,\n.btn-danger:visited {\n color: #fff;\n}\n.btn-default:link,\na.btn-default:hover,\n.btn-default:visited {\n color: #505156;\n}\n:root {\n --button-default: #505156;\n --button-primary: #1d455b;\n --button-hover: #173648;\n --header: #3c8dbc;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #296282;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #5fa4cc;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #86bad8;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\na.btn-info:link,\na.btn-warning:link,\na.btn-danger:link {\n color: #FFF;\n}\na.btn-info:visited,\na.btn-warning:visited,\na.btn-danger:visited {\n color: #FFF;\n}\na:link {\n color: var(--link);\n}\na:visited {\n color: var(--visited-link);\n}\na:hover {\n color: var(--hover-link);\n}\n.text-primary {\n color: #23536f;\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #3c8dbc;\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\na.settings_button:link,\na.settings_button:visited,\na.settings_button:hover {\n color: #3c8dbc;\n}\n/*\n * Skin: blue dark\n * ----------\n */\n.skin-blue-dark .main-header .navbar {\n background-color: #3c8dbc;\n}\n.skin-blue-dark .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-blue-dark .main-header .navbar .nav > li > a:hover,\n.skin-blue-dark .main-header .navbar .nav > li > a:active,\n.skin-blue-dark .main-header .navbar .nav > li > a:focus,\n.skin-blue-dark .main-header .navbar .nav .open > a,\n.skin-blue-dark .main-header .navbar .nav .open > a:hover,\n.skin-blue-dark .main-header .navbar .nav .open > a:focus,\n.skin-blue-dark .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-blue-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-blue-dark .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-blue-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-blue-dark .main-header .navbar .sidebar-toggle:hover {\n background-color: #367fa9;\n}\n@media (max-width: 767px) {\n .skin-blue-dark .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .skin-blue-dark .main-header .navbar .dropdown-menu li a {\n color: #fff;\n }\n .skin-blue-dark .main-header .navbar .dropdown-menu li a:hover {\n background: #367fa9;\n color: #fff;\n }\n}\n.skin-blue-dark .main-header li.user-header {\n background-color: #3c8dbc;\n}\n.skin-blue-dark .content-header {\n background: transparent;\n}\n.skin-blue-dark .wrapper,\n.skin-blue-dark .main-sidebar,\n.skin-blue-dark .left-side {\n background-color: #222d32;\n}\n.skin-blue-dark .user-panel > .info,\n.skin-blue-dark .user-panel > .info > a {\n color: #fff;\n}\n.skin-blue-dark .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-blue-dark .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-blue-dark .sidebar-menu > li:hover > a,\n.skin-blue-dark .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #3c8dbc;\n}\n.skin-blue-dark .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-blue-dark .sidebar a {\n color: #b8c7ce;\n}\n.skin-blue-dark .sidebar a:hover {\n text-decoration: none;\n}\n.skin-blue-dark .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-blue-dark .treeview-menu > li.active > a,\n.skin-blue-dark .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-blue-dark .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-blue-dark .sidebar-form input[type=\"text\"],\n.skin-blue-dark .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-blue-dark .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-blue-dark .sidebar-form input[type=\"text\"]:focus,\n.skin-blue-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-blue-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-blue-dark .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-blue-dark.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn,\n.btn:hover {\n color: #fff !important;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #307095;\n border-color: #FFF;\n color: #FFF;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #23536f;\n border-color: #23536f;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #307095;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #173648;\n color: #fff;\n}\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--visited-link);\n}\n.text-primary {\n color: #fff;\n}\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #272727;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #296282;\n --button-primary: #1d455b;\n --button-hover: #173648;\n --header: #3c8dbc;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #00BFFF;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #4dd2ff;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #99e5ff;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\n#ldapad_test_results.well.well-sm {\n color: var(--back-main);\n}\na.actions {\n color: #fff !important;\n}\n.pagination > li > a {\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover {\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu {\n background-color: var(--back-main);\n}\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress {\n background-color: var(--background);\n}\nh2.task_menu {\n color: var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-main);\n color: var(--link);\n}\n.main-header .navbar,\n.main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%, var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);\n border-color: var(--header);\n}\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color: #000;\n}\n.thead,\n.navbar-nav > li > a:link {\n color: var(--nav-link);\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\na:link {\n color: var(--link);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color: #000;\n}\n.small-box h3,\n.small-box p {\n color: var(--nav-link) !important;\n}\n.small-box h3 a:hover,\n.small-box p a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n}\n#accessoriesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr.selected > td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box,\n.box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body,\n.box-footer,\n.box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default {\n background-color: var(--button-default);\n color: var(--link);\n}\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--link);\n}\n.btn-default:active,\n.btn-default:focus,\n.btn-default:hover,\n.btn-default.active {\n background-color: var(--button-hover);\n color: var(--link);\n}\n.btn-primary,\n.btn-primary.hover,\n.btn-primary:active,\n.text-blue {\n color: var(--text-main);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#consumablesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form > div > div > div > span > i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover,\n.datepicker .next:hover,\n.datepicker .prev:hover,\n.datepicker tfoot tr th:hover,\n.datepicker table tr td.day.focused,\n.datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details > div > div > div > table {\n background-color: transparent;\n}\n.dropdown-menu > li > a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner {\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text],\ninput[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group,\n.input-group-addon {\n background-color: var(--back-sub) !important;\n color: var(--text-main);\n}\n#licensesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li > a,\n.nav-tabs-custom > .nav-tabs > li.active > a {\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom,\n.nav-tabs-custom > .tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open > .dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default > .panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true],\n.select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub) !important;\n color: var(--text-main) !important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-blue-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-blue-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #3c8dbc;\n}\n.fixed-table-body thead th .th-inner,\n.skin-blue-dark .sidebar-menu > li.active > a,\n.skin-blue .sidebar-menu > li:hover > a,\n.sidebar-toggle:hover {\n background-color: var(--header) !important;\n}\n.tab-content,\n.tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped > tbody > tr:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n}\n#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa,\n.box-body,\n.box-footer,\n.box-header {\n color: var(--text-main);\n}\n.box-header.with-border {\n border-bottom: #000;\n}\na {\n color: var(--link);\n}\na:link {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n text-decoration: underline;\n}\na:visited {\n color: var(--visited-link);\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets'] {\n background-color: transparent;\n}\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n display: table;\n}\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td {\n background-color: var(--back-sub);\n}\n/*\n * Skin: Contrast\n * ----------\n */\n.skin-contrast .main-header .navbar {\n background-color: #001F3F;\n}\n.skin-contrast .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-contrast .main-header .navbar .nav > li > a:hover,\n.skin-contrast .main-header .navbar .nav > li > a:active,\n.skin-contrast .main-header .navbar .nav > li > a:focus,\n.skin-contrast .main-header .navbar .nav .open > a,\n.skin-contrast .main-header .navbar .nav .open > a:hover,\n.skin-contrast .main-header .navbar .nav .open > a:focus,\n.skin-contrast .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-contrast .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-contrast .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-contrast .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-contrast .main-header .navbar .sidebar-toggle:hover {\n background-color: #001226;\n}\n@media (max-width: 767px) {\n .skin-contrast .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n .skin-contrast .main-header .navbar .dropdown-menu li a {\n color: #fff;\n }\n .skin-contrast .main-header .navbar .dropdown-menu li a:hover {\n background: #001226;\n }\n}\n.skin-contrast .main-header li.user-header {\n background-color: #001F3F;\n}\n.skin-contrast .content-header {\n background: transparent;\n}\n.skin-contrast .wrapper,\n.skin-contrast .main-sidebar,\n.skin-contrast .left-side {\n background-color: #222d32;\n}\n.skin-contrast .user-panel > .info,\n.skin-contrast .user-panel > .info > a {\n color: #fff;\n}\n.skin-contrast .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-contrast .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-contrast .sidebar-menu > li:hover > a,\n.skin-contrast .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #001F3F;\n}\n.skin-contrast .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-contrast .sidebar a {\n color: #b8c7ce;\n}\n.skin-contrast .sidebar a:hover {\n text-decoration: none;\n}\n.skin-contrast .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-contrast .treeview-menu > li.active > a,\n.skin-contrast .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-contrast .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-contrast .sidebar-form input[type=\"text\"],\n.skin-contrast .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-contrast .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-contrast .sidebar-form input[type=\"text\"]:focus,\n.skin-contrast .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-contrast .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-contrast .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-contrast.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #00060c;\n border-color: #000000;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #000000;\n border-color: #000000;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #00060c;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #000000;\n color: #fff;\n}\na.btn.btn-link.text-left {\n color: #001F3F;\n border: 1px solid #000;\n}\na.btn.btn-link.text-left:hover {\n color: #001F3F;\n}\na {\n color: #001F3F;\n}\na:hover {\n color: #001F3F;\n text-decoration: underline;\n}\na:visited {\n color: #001F3F;\n}\na.btn:hover {\n color: #fff;\n text-decoration: underline;\n}\n.text-primary {\n color: #000000;\n}\n.skin-contrast .treeview-menu > li > a {\n color: #FFF;\n}\n.bg-teal {\n background-color: #1b6a6a !important;\n}\n.bg-orange {\n background-color: #995400 !important;\n}\n.bg-purple {\n background-color: #3a3767 !important;\n}\n.bg-maroon {\n background-color: #7d1038 !important;\n}\n.pagination > li > a {\n color: #00060c !important;\n}\n.pagination > .active > a {\n background-color: #001F3F;\n color: #FFF !important;\n}\nbtn-success {\n background-color: #000d07;\n}\ninput::-webkit-input-placeholder {\n /* Chrome/Opera/Safari */\n color: #b5bbc8 !important;\n}\ninput::-moz-placeholder {\n /* Firefox 19+ */\n color: #b5bbc8 !important;\n}\ninput::-ms-input-placeholder {\n /* IE 10+ */\n color: #b5bbc8 !important;\n}\n.select2-default,\n.select2-selection__placeholder {\n color: #37383c !important;\n}\n.callout.callout-info {\n background-color: #23536f !important;\n}\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n/*\n * Skin: Green\n * ----------\n */\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #3d4144;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #005a31;\n --button-primary: #002715;\n --button-hover: #000d07;\n --header: #00a65a;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #00a65a;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #00f383;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #0dff90;\n}\n.skin-green .main-header .navbar {\n background-color: #00a65a;\n}\n.skin-green .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-green .main-header .navbar .nav > li > a:hover,\n.skin-green .main-header .navbar .nav > li > a:active,\n.skin-green .main-header .navbar .nav > li > a:focus,\n.skin-green .main-header .navbar .nav .open > a,\n.skin-green .main-header .navbar .nav .open > a:hover,\n.skin-green .main-header .navbar .nav .open > a:focus,\n.skin-green .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-green .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-green .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-green .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-green .main-header .navbar .sidebar-toggle:hover {\n background-color: #008d4c;\n}\n@media (max-width: 767px) {\n .skin-green .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n .skin-green .main-header .navbar .dropdown-menu li a {\n color: #333;\n }\n .skin-green .main-header .navbar .dropdown-menu li a:hover {\n background: #008d4c;\n }\n}\n.skin-green .main-header li.user-header {\n background-color: #00a65a;\n}\n.skin-green .content-header {\n background: transparent;\n}\n.skin-green .wrapper,\n.skin-green .main-sidebar,\n.skin-green .left-side {\n background-color: #222d32;\n}\n.skin-green .user-panel > .info,\n.skin-green .user-panel > .info > a {\n color: #fff;\n}\n.skin-green .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-green .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-green .sidebar-menu > li:hover > a,\n.skin-green .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #00a65a;\n}\n.skin-green .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-green .sidebar a {\n color: #b8c7ce;\n}\n.skin-green .sidebar a:hover {\n text-decoration: none;\n}\n.skin-green .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-green .treeview-menu > li.active > a,\n.skin-green .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-green .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-green .sidebar-form input[type=\"text\"],\n.skin-green .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-green .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-green .sidebar-form input[type=\"text\"]:focus,\n.skin-green .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-green .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-green .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-green.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #00733e;\n border-color: #004023;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #004023;\n border-color: #004023;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #00733e;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #000d07;\n color: #fff;\n}\na.btn-info:link,\na.btn-warning:link,\na.btn-danger:link {\n color: #FFF;\n}\na.btn-info:visited,\na.btn-warning:visited,\na.btn-danger:visited {\n color: #FFF;\n}\na:link {\n color: var(--link);\n}\na:visited {\n color: var(--visited-link);\n}\na {\n color: #00a65a;\n}\na:hover {\n color: #004023;\n}\na:visited {\n color: #00a65a;\n}\n.text-primary {\n color: #004023;\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #00a65a;\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n/*\n * Skin: green dark\n * ----------\n */\n.skin-green-dark .main-header .navbar {\n background-color: #00a65a;\n}\n.skin-green-dark .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-green-dark .main-header .navbar .nav > li > a:hover,\n.skin-green-dark .main-header .navbar .nav > li > a:active,\n.skin-green-dark .main-header .navbar .nav > li > a:focus,\n.skin-green-dark .main-header .navbar .nav .open > a,\n.skin-green-dark .main-header .navbar .nav .open > a:hover,\n.skin-green-dark .main-header .navbar .nav .open > a:focus,\n.skin-green-dark .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-green-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-green-dark .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-green-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-green-dark .main-header .navbar .sidebar-toggle:hover {\n background-color: #008d4c;\n}\n@media (max-width: 767px) {\n .skin-green-dark .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .skin-green-dark .main-header .navbar .dropdown-menu li a {\n color: #fff;\n }\n .skin-green-dark .main-header .navbar .dropdown-menu li a:hover {\n background: #008d4c;\n color: #fff;\n }\n}\n.skin-green-dark .main-header li.user-header {\n background-color: #00a65a;\n}\n.skin-green-dark .content-header {\n background: transparent;\n}\n.skin-green-dark .wrapper,\n.skin-green-dark .main-sidebar,\n.skin-green-dark .left-side {\n background-color: #222d32;\n}\n.skin-green-dark .user-panel > .info,\n.skin-green-dark .user-panel > .info > a {\n color: #fff;\n}\n.skin-green-dark .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-green-dark .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-green-dark .sidebar-menu > li:hover > a,\n.skin-green-dark .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #00a65a;\n}\n.skin-green-dark .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-green-dark .sidebar a {\n color: #b8c7ce;\n}\n.skin-green-dark .sidebar a:hover {\n text-decoration: none;\n}\n.skin-green-dark .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-green-dark .treeview-menu > li.active > a,\n.skin-green-dark .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-green-dark .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-green-dark .sidebar-form input[type=\"text\"],\n.skin-green-dark .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-green-dark .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-green-dark .sidebar-form input[type=\"text\"]:focus,\n.skin-green-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-green-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-green-dark .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-green-dark.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn,\n.btn:hover {\n color: #FFF !important;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #00733e;\n border-color: #FFF;\n color: #FFF;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #004023;\n border-color: #004023;\n color: #FFF;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #00733e;\n color: #FFF;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #000d07;\n color: #FFF;\n}\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--visited-link);\n}\n.text-primary {\n color: #fff;\n}\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #312F2F;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #006400;\n --button-primary: #003100;\n --button-hover: #000000;\n --header: #006400;\n /* Use same as Header picker */\n --text-main: #FFF;\n --text-sub: #9b9b9b;\n --link: #9ACD32;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #b8dc70;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #d7ebad;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\n#ldapad_test_results.well.well-sm {\n color: var(--back-main);\n}\na.actions {\n color: #fff !important;\n}\n.pagination > li > a {\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover {\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu {\n background-color: var(--back-main);\n}\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress {\n background-color: var(--background);\n}\nh2.task_menu {\n color: var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-main);\n color: var(--link);\n}\n.main-header .navbar,\n.main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%, var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);\n border-color: var(--header);\n}\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.thead,\n.navbar-nav > li > a:link {\n color: var(--nav-link);\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.small-box h3,\n.small-box p {\n color: var(--nav-link) !important;\n}\n.small-box h3 a:hover,\n.small-box p a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n}\n#accessoriesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr.selected > td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box,\n.box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body,\n.box-footer,\n.box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default {\n background-color: var(--button-default);\n color: var(--link);\n}\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--link);\n}\n.btn-default:active,\n.btn-default:focus,\n.btn-default:hover,\n.btn-default.active {\n background-color: var(--button-hover);\n color: var(--link);\n}\n.btn-primary,\n.btn-primary.hover,\n.btn-primary:active,\n.text-green {\n color: var(--text-main);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#consumablesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form > div > div > div > span > i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover,\n.datepicker .next:hover,\n.datepicker .prev:hover,\n.datepicker tfoot tr th:hover,\n.datepicker table tr td.day.focused,\n.datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details > div > div > div > table {\n background-color: transparent;\n}\n.dropdown-menu > li > a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner {\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text],\ninput[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group,\n.input-group-addon {\n background-color: var(--back-sub) !important;\n color: var(--text-main);\n}\n#licensesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li > a,\n.nav-tabs-custom > .nav-tabs > li.active > a {\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom,\n.nav-tabs-custom > .tab-content {\n background-color: var(--back-main);\n}\n.open > .dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default > .panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true],\n.select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub) !important;\n color: var(--text-main) !important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-green-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-green-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #006300;\n}\n.fixed-table-body thead th .th-inner,\n.skin-green-dark .sidebar-menu > li.active > a,\n.skin-green .sidebar-menu > li:hover > a,\n.sidebar-toggle:hover {\n background-color: var(--header) !important;\n}\n.tab-content,\n.tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped > tbody > tr:nth-of-type(even) {\n background-color: var(--back-sub);\n}\n#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa,\n.box-body,\n.box-footer,\n.box-header {\n color: var(--text-main);\n}\n.box-header.with-border {\n border-bottom: #000;\n}\na {\n color: var(--link);\n}\na:link {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n text-decoration: underline;\n}\na:visited {\n color: var(--visited-link);\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets'] {\n background-color: transparent;\n}\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n display: table;\n}\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td {\n background-color: var(--back-sub);\n}\n/*\n * Skin: orange dark\n * ----------\n */\n.skin-orange-dark .main-header .navbar {\n background-color: #FF8C00;\n}\n.skin-orange-dark .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-orange-dark .main-header .navbar .nav > li > a:hover,\n.skin-orange-dark .main-header .navbar .nav > li > a:active,\n.skin-orange-dark .main-header .navbar .nav > li > a:focus,\n.skin-orange-dark .main-header .navbar .nav .open > a,\n.skin-orange-dark .main-header .navbar .nav .open > a:hover,\n.skin-orange-dark .main-header .navbar .nav .open > a:focus,\n.skin-orange-dark .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-orange-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-orange-dark .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-orange-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-orange-dark .main-header .navbar .sidebar-toggle:hover {\n background-color: #e67e00;\n}\n@media (max-width: 767px) {\n .skin-orange-dark .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .skin-orange-dark .main-header .navbar .dropdown-menu li a {\n color: #fff;\n }\n .skin-orange-dark .main-header .navbar .dropdown-menu li a:hover {\n background: #e67e00;\n color: #fff;\n }\n}\n.skin-orange-dark .main-header li.user-header {\n background-color: #FF8C00;\n}\n.skin-orange-dark .content-header {\n background: transparent;\n}\n.skin-orange-dark .wrapper,\n.skin-orange-dark .main-sidebar,\n.skin-orange-dark .left-side {\n background-color: #222d32;\n}\n.skin-orange-dark .user-panel > .info,\n.skin-orange-dark .user-panel > .info > a {\n color: #fff;\n}\n.skin-orange-dark .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-orange-dark .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-orange-dark .sidebar-menu > li:hover > a,\n.skin-orange-dark .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #FF8C00;\n}\n.skin-orange-dark .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-orange-dark .sidebar a {\n color: #b8c7ce;\n}\n.skin-orange-dark .sidebar a:hover {\n text-decoration: none;\n}\n.skin-orange-dark .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-orange-dark .treeview-menu > li.active > a,\n.skin-orange-dark .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-orange-dark .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-orange-dark .sidebar-form input[type=\"text\"],\n.skin-orange-dark .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-orange-dark .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-orange-dark .sidebar-form input[type=\"text\"]:focus,\n.skin-orange-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-orange-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-orange-dark .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-orange-dark.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn,\n.btn:hover {\n color: #fff !important;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #cc7000;\n border-color: #fff;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #995400;\n border-color: #995400;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #cc7000;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #663800;\n color: #fff;\n}\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n.text-primary {\n color: #fff;\n}\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #2E2C2C;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #b36200;\n --button-primary: #804600;\n --button-hover: #663800;\n --header: #FF8C00;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #FFA500;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #ffc04d;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #ffdb99;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\n#ldapad_test_results.well.well-sm {\n color: var(--back-main);\n}\na.actions {\n color: #fff !important;\n}\n.pagination > li > a {\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover {\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu {\n background-color: var(--back-main);\n}\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress {\n background-color: var(--background);\n}\nh2.task_menu {\n color: var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-main);\n color: var(--link);\n}\n.main-header .navbar,\n.main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%, var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);\n border-color: var(--header);\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color: #000;\n}\n.thead,\n.navbar-nav > li > a:link {\n color: var(--nav-link);\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\na:link {\n color: var(--link);\n}\na:visited {\n color: var(--visited-link);\n}\na:hover {\n color: var(--hover-link);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.small-box h3,\n.small-box p {\n color: var(--nav-link) !important;\n}\n.small-box h3 a:hover,\n.small-box p a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n}\n#accessoriesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr.selected > td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box,\n.box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body,\n.box-footer,\n.box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\na:link.btn-default {\n color: var(--nav-link);\n}\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n.btn-default:active,\n.btn-default:focus,\n.btn-default:hover,\n.btn-default.active {\n background-color: var(--button-hover);\n color: var(--background);\n}\n.btn-primary,\n.btn-primary.hover,\n.btn-primary:active,\n.text-orange {\n color: var(--nav-link) !important;\n border-color: var(--button-primary);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#consumablesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form > div > div > div > span > i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover,\n.datepicker .next:hover,\n.datepicker .prev:hover,\n.datepicker tfoot tr th:hover,\n.datepicker table tr td.day.focused,\n.datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details > div > div > div > table {\n background-color: transparent;\n}\n.dropdown-menu > li > a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner {\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text],\ninput[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group,\n.input-group-addon {\n background-color: var(--back-sub) !important;\n color: var(--text-main);\n}\n#licensesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li > a,\n.nav-tabs-custom > .nav-tabs > li.active > a {\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom,\n.nav-tabs-custom > .tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open > .dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default > .panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true],\n.select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub) !important;\n color: var(--text-main) !important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-orange-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-orange-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #ff8c00;\n}\n.fixed-table-body thead th .th-inner,\n.skin-orange-dark .sidebar-menu > li.active > a,\n.skin-orange .sidebar-menu > li:hover > a,\n.sidebar-toggle:hover {\n background-color: var(--header) !important;\n}\n.tab-content,\n.tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped > tbody > tr:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n}\n#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa,\n.box-body,\n.box-footer,\n.box-header {\n color: var(--text-main);\n}\n.box-header.with-border {\n border-bottom: #000;\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets'] {\n background-color: transparent;\n}\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n display: table;\n}\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td {\n background-color: var(--back-sub);\n}\n/*\n * Skin: Purple\n * ----------\n */\n.skin-purple .main-header .navbar {\n background-color: #605ca8;\n}\n.skin-purple .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-purple .main-header .navbar .nav > li > a:hover,\n.skin-purple .main-header .navbar .nav > li > a:active,\n.skin-purple .main-header .navbar .nav > li > a:focus,\n.skin-purple .main-header .navbar .nav .open > a,\n.skin-purple .main-header .navbar .nav .open > a:hover,\n.skin-purple .main-header .navbar .nav .open > a:focus,\n.skin-purple .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-purple .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-purple .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-purple .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-purple .main-header .navbar .sidebar-toggle:hover {\n background-color: #555299;\n}\n@media (max-width: 767px) {\n .skin-purple .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n .skin-purple .main-header .navbar .dropdown-menu li a {\n color: #333;\n }\n .skin-purple .main-header .navbar .dropdown-menu li a:hover {\n background: #555299;\n }\n}\n.skin-purple .main-header li.user-header {\n background-color: #605ca8;\n}\n.skin-purple .content-header {\n background: transparent;\n}\n.skin-purple .wrapper,\n.skin-purple .main-sidebar,\n.skin-purple .left-side {\n background-color: #222d32;\n}\n.skin-purple .user-panel > .info,\n.skin-purple .user-panel > .info > a {\n color: #fff;\n}\n.skin-purple .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-purple .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-purple .sidebar-menu > li:hover > a,\n.skin-purple .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #605ca8;\n}\n.skin-purple .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-purple .sidebar a {\n color: #b8c7ce;\n}\n.skin-purple .sidebar a:hover {\n text-decoration: none;\n}\n.skin-purple .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-purple .treeview-menu > li.active > a,\n.skin-purple .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-purple .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-purple .sidebar-form input[type=\"text\"],\n.skin-purple .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-purple .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-purple .sidebar-form input[type=\"text\"]:focus,\n.skin-purple .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-purple .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-purple .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-purple.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #4c4988;\n border-color: #3a3767;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #3a3767;\n border-color: #3a3767;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #4c4988;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #272546;\n color: #fff;\n}\na {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--visited-link);\n}\n.text-primary {\n color: #3a3767;\n}\n:root {\n --button-default: #434078;\n --button-primary: #302e56;\n --button-hover: #272546;\n --header: #605ca8;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #605ca8;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #918ec3;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #c1c0dd;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\na.btn-info:link,\na.btn-warning:link,\na.btn-danger:link {\n color: #FFF;\n}\na.btn-info:visited,\na.btn-warning:visited,\na.btn-danger:visited {\n color: #FFF;\n}\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #605ca8;\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n/*\n * Skin: purple dark\n * ----------\n */\n.skin-purple-dark .main-header .navbar {\n background-color: #605ca8;\n}\n.skin-purple-dark .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-purple-dark .main-header .navbar .nav > li > a:hover,\n.skin-purple-dark .main-header .navbar .nav > li > a:active,\n.skin-purple-dark .main-header .navbar .nav > li > a:focus,\n.skin-purple-dark .main-header .navbar .nav .open > a,\n.skin-purple-dark .main-header .navbar .nav .open > a:hover,\n.skin-purple-dark .main-header .navbar .nav .open > a:focus,\n.skin-purple-dark .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-purple-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-purple-dark .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-purple-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-purple-dark .main-header .navbar .sidebar-toggle:hover {\n background-color: #555299;\n}\n@media (max-width: 767px) {\n .skin-purple-dark .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .skin-purple-dark .main-header .navbar .dropdown-menu li a {\n color: #fff;\n }\n .skin-purple-dark .main-header .navbar .dropdown-menu li a:hover {\n background: #555299;\n color: #fff;\n }\n}\n.skin-purple-dark .main-header li.user-header {\n background-color: #605ca8;\n}\n.skin-purple-dark .content-header {\n background: transparent;\n}\n.skin-purple-dark .wrapper,\n.skin-purple-dark .main-sidebar,\n.skin-purple-dark .left-side {\n background-color: #222d32;\n}\n.skin-purple-dark .user-panel > .info,\n.skin-purple-dark .user-panel > .info > a {\n color: #fff;\n}\n.skin-purple-dark .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-purple-dark .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-purple-dark .sidebar-menu > li:hover > a,\n.skin-purple-dark .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #605ca8;\n}\n.skin-purple-dark .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-purple-dark .sidebar a {\n color: #b8c7ce;\n}\n.skin-purple-dark .sidebar a:hover {\n text-decoration: none;\n}\n.skin-purple-dark .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-purple-dark .treeview-menu > li.active > a,\n.skin-purple-dark .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-purple-dark .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-purple-dark .sidebar-form input[type=\"text\"],\n.skin-purple-dark .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-purple-dark .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-purple-dark .sidebar-form input[type=\"text\"]:focus,\n.skin-purple-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-purple-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-purple-dark .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-purple-dark.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn,\n.btn:hover {\n color: #fff !important;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #4c4988;\n border-color: #FFF;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #3a3767;\n border-color: #3a3767;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #4c4988;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #272546;\n color: #fff;\n}\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--visited-link);\n}\n.text-primary {\n color: #fff;\n}\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #080808;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #434078;\n --button-primary: #302e56;\n --button-hover: #272546;\n --header: #605ca8;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #AC83FF;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #dfd0ff;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #ffffff;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\n#ldapad_test_results.well.well-sm {\n color: var(--back-main);\n}\n.pagination > li > a {\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--button-default);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover {\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu {\n background-color: var(--back-main);\n}\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress {\n background-color: var(--background);\n}\nh2.task_menu {\n color: var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-main);\n color: var(--link);\n}\n.main-header .navbar,\n.main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%, var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);\n border-color: var(--header);\n}\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color: #000;\n}\n.thead,\n.navbar-nav > li > a:link {\n color: var(--nav-link);\n}\n.label-default {\n background-color: var(--back-sub);\n color: #fff;\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\na:link {\n color: var(--link);\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.small-box h3,\n.small-box p {\n color: var(--nav-link) !important;\n}\n.small-box h3 a:hover,\n.small-box p a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n}\na:link.btn-default {\n color: var(--nav-link);\n}\n#accessoriesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr.selected > td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box,\n.box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body,\n.box-footer,\n.box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n.btn-default:active,\n.btn-default:focus,\n.btn-default:hover,\n.btn-default.active {\n background-color: var(--button-hover);\n color: var(--nav-link);\n}\n.btn-primary,\n.btn-primary.hover,\n.btn-primary:active,\n.text-purple {\n color: var(--nav-link) !important;\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#consumablesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form > div > div > div > span > i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover,\n.datepicker .next:hover,\n.datepicker .prev:hover,\n.datepicker tfoot tr th:hover,\n.datepicker table tr td.day.focused,\n.datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details > div > div > div > table {\n background-color: transparent;\n}\n.dropdown-menu > li > a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner {\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text],\ninput[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group,\n.input-group-addon {\n background-color: var(--back-sub) !important;\n color: var(--text-main);\n}\n#licensesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li > a,\n.nav-tabs-custom > .nav-tabs > li.active > a {\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom,\n.nav-tabs-custom > .tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li.header {\n background-color: var(--back-main);\n color: var(--link);\n}\n.open > .dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default > .panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true],\n.select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub) !important;\n color: var(--text-main) !important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-purple-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-purple-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #5f5ca8;\n}\n.fixed-table-body thead th .th-inner,\n.skin-purple-dark .sidebar-menu > li.active > a,\n.skin-purple .sidebar-menu > li:hover > a,\n.sidebar-toggle:hover {\n background-color: var(--header) !important;\n}\n.tab-content,\n.tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped > tbody > tr:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n}\n#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa,\n.box-body,\n.box-footer,\n.box-header {\n color: var(--text-main);\n}\n.box-header.with-border {\n border-bottom: #000;\n}\na {\n color: var(--link);\n}\na:link {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n text-decoration: underline;\n}\na:visited {\n color: var(--visited-link);\n}\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n display: table;\n}\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td {\n background-color: var(--back-sub);\n}\n/*\n * Skin: Red\n * ----------\n */\n.skin-red .main-header .navbar {\n background-color: #dd4b39;\n}\n.skin-red .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-red .main-header .navbar .nav > li > a:hover,\n.skin-red .main-header .navbar .nav > li > a:active,\n.skin-red .main-header .navbar .nav > li > a:focus,\n.skin-red .main-header .navbar .nav .open > a,\n.skin-red .main-header .navbar .nav .open > a:hover,\n.skin-red .main-header .navbar .nav .open > a:focus,\n.skin-red .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-red .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-red .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-red .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-red .main-header .navbar .sidebar-toggle:hover {\n background-color: #d73925;\n}\n@media (max-width: 767px) {\n .skin-red .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n .skin-red .main-header .navbar .dropdown-menu li a {\n color: #333;\n }\n .skin-red .main-header .navbar .dropdown-menu li a:hover {\n background: #d73925;\n }\n}\n.skin-red .main-header li.user-header {\n background-color: #dd4b39;\n}\n.skin-red .content-header {\n background: transparent;\n}\n.skin-red .wrapper,\n.skin-red .main-sidebar,\n.skin-red .left-side {\n background-color: #222d32;\n}\n.skin-red .user-panel > .info,\n.skin-red .user-panel > .info > a {\n color: #fff;\n}\n.skin-red .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-red .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-red .sidebar-menu > li:hover > a,\n.skin-red .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #dd4b39;\n}\n.skin-red .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-red .sidebar a {\n color: #b8c7ce;\n}\n.skin-red .sidebar a:hover {\n text-decoration: none;\n}\n.skin-red .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-red .treeview-menu > li.active > a,\n.skin-red .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-red .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-red .sidebar-form input[type=\"text\"],\n.skin-red .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-red .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-red .sidebar-form input[type=\"text\"]:focus,\n.skin-red .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-red .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-red .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-red.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #c23321;\n border-color: #96271a;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #96271a;\n border-color: #96271a;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #c23321;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #6b1c12;\n color: #fff;\n}\na {\n color: #dd4b39;\n}\na:hover {\n color: #96271a;\n}\na:visited {\n color: #dd4b39;\n}\n.text-primary {\n color: #96271a;\n}\n:root {\n --button-default: #ac2d1e;\n --button-primary: #802216;\n --button-hover: #6b1c12;\n --header: #dd4b39;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-alt: #222;\n --text-sub: #9b9b9b;\n --link: #dd4b39;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #e8867a;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #f3c2bc;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\na:link {\n color: var(--link);\n}\na:visited {\n color: var(--visited-link);\n}\na.btn-info:link,\na.btn-warning:link,\na.btn-danger:link {\n color: #FFF;\n}\na.btn-info:visited,\na.btn-warning:visited,\na.btn-danger:visited {\n color: #FFF;\n}\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #dd4b39;\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n.far fa-life-ring {\n color: var(--link);\n}\n/*\n * Skin: red dark\n * ----------\n */\n.skin-red-dark .main-header .navbar {\n background-color: #dd4b39;\n}\n.skin-red-dark .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-red-dark .main-header .navbar .nav > li > a:hover,\n.skin-red-dark .main-header .navbar .nav > li > a:active,\n.skin-red-dark .main-header .navbar .nav > li > a:focus,\n.skin-red-dark .main-header .navbar .nav .open > a,\n.skin-red-dark .main-header .navbar .nav .open > a:hover,\n.skin-red-dark .main-header .navbar .nav .open > a:focus,\n.skin-red-dark .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-red-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-red-dark .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-red-dark .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-red-dark .main-header .navbar .sidebar-toggle:hover {\n background-color: #d73925;\n}\n@media (max-width: 767px) {\n .skin-red-dark .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .skin-red-dark .main-header .navbar .dropdown-menu li a {\n color: #fff;\n }\n .skin-red-dark .main-header .navbar .dropdown-menu li a:hover {\n background: #d73925;\n color: #fff;\n }\n}\n.skin-red-dark .main-header li.user-header {\n background-color: #dd4b39;\n}\n.skin-red-dark .content-header {\n background: transparent;\n}\n.skin-red-dark .wrapper,\n.skin-red-dark .main-sidebar,\n.skin-red-dark .left-side {\n background-color: #222d32;\n}\n.skin-red-dark .user-panel > .info,\n.skin-red-dark .user-panel > .info > a {\n color: #fff;\n}\n.skin-red-dark .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-red-dark .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-red-dark .sidebar-menu > li:hover > a,\n.skin-red-dark .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #dd4b39;\n}\n.skin-red-dark .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-red-dark .sidebar a {\n color: #b8c7ce;\n}\n.skin-red-dark .sidebar a:hover {\n text-decoration: none;\n}\n.skin-red-dark .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-red-dark .treeview-menu > li.active > a,\n.skin-red-dark .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-red-dark .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-red-dark .sidebar-form input[type=\"text\"],\n.skin-red-dark .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-red-dark .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-red-dark .sidebar-form input[type=\"text\"]:focus,\n.skin-red-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-red-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-red-dark .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-red-dark.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn,\n.btn:hover {\n color: #fff;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #c23321;\n border-color: #FFF;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #96271a;\n border-color: #96271a;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #c23321;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #6b1c12;\n color: #fff;\n}\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\na:hover {\n color: #fff;\n}\na:visited {\n color: #fff;\n}\n.text-primary {\n color: #fff;\n}\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #3d4144;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #ac2d1e;\n --button-primary: #802216;\n --button-hover: #6b1c12;\n --header: #dd4b39;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-alt: #222;\n --text-sub: #9b9b9b;\n --link: #e00000;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #D24D57;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #D24D57;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\n#ldapad_test_results.well.well-sm {\n color: var(--back-main);\n}\n.pagination > li > a {\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover {\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu {\n background-color: var(--back-main);\n}\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress {\n background-color: var(--background);\n}\nh2.task_menu {\n color: var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-main);\n color: var(--link);\n}\n.main-header .navbar,\n.main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%, var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);\n border-color: var(--header);\n}\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color: #000;\n}\n.thead,\n.navbar-nav > li > a:link {\n color: var(--nav-link);\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\na:link {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--nav-link);\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.small-box h3,\n.small-box p {\n color: var(--nav-link) !important;\n}\n.small-box h3 a:hover,\n.small-box p a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n}\n#accessoriesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr.selected > td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box,\n.box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body,\n.box-footer,\n.box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default {\n background-color: var(--back-sub);\n}\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--link);\n}\n.btn-default:active,\n.btn-default:focus,\n.btn-default:hover,\n.btn-default.active {\n background-color: var(--button-hover);\n color: var(--link);\n}\n.btn-primary,\n.btn-primary.hover,\n.btn-primary:active,\n.text-red {\n color: var(--button-primary);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#consumablesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form > div > div > div > span > i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover,\n.datepicker .next:hover,\n.datepicker .prev:hover,\n.datepicker tfoot tr th:hover,\n.datepicker table tr td.day.focused,\n.datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details > div > div > div > table {\n background-color: transparent;\n}\n.dropdown-menu > li > a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner {\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text],\ninput[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group,\n.input-group-addon {\n background-color: var(--back-sub) !important;\n color: var(--text-main);\n}\n#licensesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li > a,\n.nav-tabs-custom > .nav-tabs > li.active > a {\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom,\n.nav-tabs-custom > .tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open > .dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default > .panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true],\n.select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub) !important;\n color: var(--text-main) !important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-red-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-red-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #c23320;\n}\n.fixed-table-body thead th .th-inner,\n.skin-red-dark .sidebar-menu > li.active > a,\n.skin-red .sidebar-menu > li:hover > a,\n.sidebar-toggle:hover {\n background-color: var(--header) !important;\n}\n.tab-content,\n.tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped > tbody > tr:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n}\n#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa,\n.box-body,\n.box-footer,\n.box-header {\n color: var(--text-main);\n}\n.box-header.with-border {\n border-bottom: #000;\n}\n#upload-table tbody > tr.warning > td,\n#upload-table h3,\n#upload-table p {\n background-color: #fcf8e3;\n color: #000;\n}\na {\n color: var(--link);\n}\na:link {\n color: var(--nav-link);\n}\na:hover {\n color: var(--nav-link);\n text-decoration: underline;\n}\na:visited {\n color: var(--nav-link);\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets'] {\n background-color: transparent;\n}\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n display: table;\n}\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td {\n background-color: var(--back-sub);\n}\n/*\n * Skin: Orange\n * ----------\n */\n.skin-orange .main-header .navbar {\n background-color: #FF8C00;\n}\n.skin-orange .main-header .navbar .nav > li > a {\n color: #fff;\n}\n.skin-orange .main-header .navbar .nav > li > a:hover,\n.skin-orange .main-header .navbar .nav > li > a:active,\n.skin-orange .main-header .navbar .nav > li > a:focus,\n.skin-orange .main-header .navbar .nav .open > a,\n.skin-orange .main-header .navbar .nav .open > a:hover,\n.skin-orange .main-header .navbar .nav .open > a:focus,\n.skin-orange .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-orange .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-orange .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-orange .main-header .navbar .sidebar-toggle {\n color: #fff;\n}\n.skin-orange .main-header .navbar .sidebar-toggle:hover {\n background-color: #e67e00;\n}\n@media (max-width: 767px) {\n .skin-orange .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n .skin-orange .main-header .navbar .dropdown-menu li a {\n color: #333;\n }\n .skin-orange .main-header .navbar .dropdown-menu li a:hover {\n background: #e67e00;\n }\n}\n.skin-orange .main-header li.user-header {\n background-color: #FF8C00;\n}\n.skin-orange .content-header {\n background: transparent;\n}\n.skin-orange .wrapper,\n.skin-orange .main-sidebar,\n.skin-orange .left-side {\n background-color: #222d32;\n}\n.skin-orange .user-panel > .info,\n.skin-orange .user-panel > .info > a {\n color: #fff;\n}\n.skin-orange .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-orange .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-orange .sidebar-menu > li:hover > a,\n.skin-orange .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #FF8C00;\n}\n.skin-orange .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-orange .sidebar a {\n color: #b8c7ce;\n}\n.skin-orange .sidebar a:hover {\n text-decoration: none;\n}\n.skin-orange .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-orange .treeview-menu > li.active > a,\n.skin-orange .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-orange .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-orange .sidebar-form input[type=\"text\"],\n.skin-orange .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-orange .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-orange .sidebar-form input[type=\"text\"]:focus,\n.skin-orange .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-orange .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-orange .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-orange.layout-top-nav .main-header > .logo .logo-variant {\n background-color: none;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #cc7000;\n border-color: #995400;\n color: #fff;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #995400;\n border-color: #995400;\n color: #fff;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #cc7000;\n color: #fff;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #663800;\n color: #fff;\n}\na {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--visited-link);\n}\n.text-primary {\n color: #995400;\n}\n:root {\n --button-default: #b36200;\n --button-primary: #804600;\n --button-hover: #663800;\n --header: #FF8C00;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #FF8C00;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #ffa333;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #ffaf4d;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\na.btn-info:link,\na.btn-warning:link,\na.btn-danger:link {\n color: #FFF;\n}\na.btn-info:visited,\na.btn-warning:visited,\na.btn-danger:visited {\n color: #FFF;\n}\n.far fa-life-ring {\n color: var(--link);\n}\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #FF8C00;\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n/*\n * Skin: Yellow\n * ----------\n */\n.skin-yellow .main-header .navbar {\n background-color: #FBCC34;\n}\n.skin-yellow .main-header .navbar .nav > li > a {\n color: rgba(255, 255, 255, 0.8);\n}\n.skin-yellow .main-header .navbar .nav > li > a:hover,\n.skin-yellow .main-header .navbar .nav > li > a:active,\n.skin-yellow .main-header .navbar .nav > li > a:focus,\n.skin-yellow .main-header .navbar .nav .open > a,\n.skin-yellow .main-header .navbar .nav .open > a:hover,\n.skin-yellow .main-header .navbar .nav .open > a:focus,\n.skin-yellow .main-header .navbar .nav > .active > a {\n background: rgba(0, 0, 0, 0.1);\n color: #f6f6f6;\n}\n.skin-yellow .main-header .navbar .sidebar-toggle {\n color: rgba(255, 255, 255, 0.8);\n}\n.skin-yellow .main-header .navbar .sidebar-toggle:hover {\n color: #f6f6f6;\n background: rgba(0, 0, 0, 0.1);\n}\n.skin-yellow .main-header .navbar .sidebar-toggle {\n color: #000;\n}\n.skin-yellow .main-header .navbar .sidebar-toggle:hover {\n background-color: #fbc61b;\n}\n@media (max-width: 767px) {\n .skin-yellow .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n .skin-yellow .main-header .navbar .dropdown-menu li a {\n color: #333;\n }\n .skin-yellow .main-header .navbar .dropdown-menu li a:hover {\n background: #fbc61b;\n }\n}\n.skin-yellow .main-header li.user-header {\n background-color: #FBCC34;\n}\n.skin-yellow .content-header {\n background: transparent;\n}\n.skin-yellow .wrapper,\n.skin-yellow .main-sidebar,\n.skin-yellow .left-side {\n background-color: #222d32;\n}\n.skin-yellow .user-panel > .info,\n.skin-yellow .user-panel > .info > a {\n color: #fff;\n}\n.skin-yellow .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-yellow .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-yellow .sidebar-menu > li:hover > a,\n.skin-yellow .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #FBCC34;\n}\n.skin-yellow .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-yellow .sidebar a {\n color: #b8c7ce;\n}\n.skin-yellow .sidebar a:hover {\n text-decoration: none;\n}\n.skin-yellow .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-yellow .treeview-menu > li.active > a,\n.skin-yellow .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-yellow .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-yellow .sidebar-form input[type=\"text\"],\n.skin-yellow .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-yellow .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-yellow .sidebar-form input[type=\"text\"]:focus,\n.skin-yellow .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-yellow .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-yellow .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.skin-yellow.layout-top-nav .main-header > .logo .logo-variant {\n color: #413F42;\n background-color: none;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: #f7be05;\n border-color: #c59704;\n color: var(--nav-link);\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: #c59704;\n border-color: #c59704;\n color: var(--nav-link);\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: #f7be05;\n color: var(--nav-link);\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: #937103;\n color: var(--nav-link);\n}\na.btn-info:link,\na.btn-warning:link,\na.btn-danger:link {\n color: #FFF;\n}\na.btn-info:visited,\na.btn-warning:visited,\na.btn-danger:visited {\n color: #FFF;\n}\na {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--visited-link);\n}\n.text-primary {\n color: #c59704;\n}\n:root {\n --button-default: #deab04;\n --button-primary: #ac8403;\n --button-hover: #937103;\n --header: #FBCC34;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #5b585c;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #747176;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #b4b1b5;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #222;\n /* Use same as Header picker */\n --light-link: #fff;\n /* Use same as Header picker */\n}\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #FBCC34;\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n.thead,\n.navbar-nav > li > a:link {\n color: var(--nav-link);\n}\n.navbar-nav > li > a:visited {\n color: var(--nav-link);\n}\n:hover {\n color: var(--nav-link);\n}\n.skin-yellow .main-header .navbar .nav > li > a {\n color: #413F42;\n}\n.far fa-life-ring {\n color: var(--link);\n}\n/*\n * Skin: yellow dark\n * ----------\n */\n.skin-yellow-dark .main-header .navbar .sidebar-toggle {\n color: #000 !important;\n}\n.skin-yellow-dark .main-header .navbar .sidebar-toggle:hover {\n background-color: var(--link);\n}\n@media (max-width: 767px) {\n .skin-yellow-dark .main-header .navbar .dropdown-menu li.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n .skin-yellow-dark .main-header .navbar .dropdown-menu li a {\n color: #fff;\n }\n .skin-yellow-dark .main-header .navbar .dropdown-menu li a:hover {\n background: #e08e0b;\n color: #fff;\n }\n}\n.skin-yellow-dark .main-header li.user-header {\n background-color: #f39c12;\n}\n.skin-yellow-dark .content-header {\n background: transparent;\n}\n.skin-yellow-dark .wrapper,\n.skin-yellow-dark .main-sidebar,\n.skin-yellow-dark .left-side {\n background-color: #222d32;\n}\n.skin-yellow-dark .user-panel > .info,\n.skin-yellow-dark .user-panel > .info > a {\n color: #fff;\n}\n.skin-yellow-dark .sidebar-menu > li.header {\n color: #4b646f;\n background: #1a2226;\n}\n.skin-yellow-dark .sidebar-menu > li > a {\n border-left: 3px solid transparent;\n}\n.skin-yellow-dark .sidebar-menu > li:hover > a,\n.skin-yellow-dark .sidebar-menu > li.active > a {\n color: #fff;\n background: #1e282c;\n border-left-color: #f39c12;\n}\n.skin-yellow-dark .sidebar-menu > li > .treeview-menu {\n margin: 0 1px;\n background: #2c3b41;\n}\n.skin-yellow-dark .sidebar a {\n color: #b8c7ce;\n}\n.skin-yellow-dark .sidebar a:hover {\n text-decoration: none;\n}\n.skin-yellow-dark .treeview-menu > li > a {\n color: #8aa4af;\n}\n.skin-yellow-dark .treeview-menu > li.active > a,\n.skin-yellow-dark .treeview-menu > li > a:hover {\n color: #fff;\n}\n.skin-yellow-dark .sidebar-form {\n border-radius: 3px;\n border: 1px solid #374850;\n margin: 10px 10px;\n}\n.skin-yellow-dark .sidebar-form input[type=\"text\"],\n.skin-yellow-dark .sidebar-form .btn {\n box-shadow: none;\n background-color: #374850;\n border: 1px solid transparent;\n height: 35px;\n -webkit-transition: all 0.3s ease-in-out;\n -o-transition: all 0.3s ease-in-out;\n transition: all 0.3s ease-in-out;\n}\n.skin-yellow-dark .sidebar-form input[type=\"text\"] {\n color: #666;\n border-top-left-radius: 2px;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 2px;\n}\n.skin-yellow-dark .sidebar-form input[type=\"text\"]:focus,\n.skin-yellow-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n}\n.skin-yellow-dark .sidebar-form input[type=\"text\"]:focus + .input-group-btn .btn {\n border-left-color: #fff;\n}\n.skin-yellow-dark .sidebar-form .btn {\n color: #999;\n border-top-left-radius: 0;\n border-top-right-radius: 2px;\n border-bottom-right-radius: 2px;\n border-bottom-left-radius: 0;\n}\n.btn.btn-primary,\n.btn:hover.btn-primary,\n.btn .btn-primary:link,\n.btn:hover .btn-primary:link {\n background-color: var(--button-default);\n border-color: #000000;\n color: #545454;\n}\n.btna.btn-primary:hover,\n.btn:hovera.btn-primary:hover {\n background-color: var(--button-hover);\n border-color: var(--button-hover);\n color: #545454;\n}\n.btn.btn-white:link,\n.btn:hover.btn-white:link {\n background-color: var(--button-default);\n color: #545454;\n}\n.btn.btn-white:hover,\n.btn:hover.btn-white:hover {\n background-color: var(--hover-link);\n color: #545454;\n}\na.actions {\n color: #fff !important;\n}\na:visited.label-default,\na:link.label-default {\n color: #444;\n}\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\na:link {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--visited-link);\n}\n.text-primary {\n color: #fff;\n}\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #3d4144;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #FFCC32;\n --button-primary: #b28500;\n --button-hover: #987200;\n --header: #FFCC32;\n /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #F0E68C;\n /* Use same as Header picker, lighten by 70% */\n --visited-link: #f9f5d0;\n /* Use same as Header picker, lighten by 70% */\n --hover-link: #ffffff;\n /* Use same as Header picker, lighten by 70% */\n --nav-link: #222;\n /* Use same as Header picker */\n --light-link: #545454;\n /* Use same as Header picker */\n}\n#ldapad_test_results.well.well-sm {\n color: var(--back-main);\n}\n.pagination > li > a {\n color: var(--text-main);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--button-default);\n border-color: var(--light-link);\n color: var(--nav-link);\n}\n.pagination > .active > a:hover {\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu {\n background-color: var(--back-main);\n}\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress {\n background-color: var(--background);\n}\nh2.task_menu {\n color: var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-main);\n color: var(--link);\n}\n.main-header .navbar,\n.main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%, var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%);\n border-color: var(--header);\n}\na.btn.btn-default {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--text-main);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color: #000;\n}\n.thead,\n.navbar-nav > li > a:link {\n color: var(--nav-link);\n}\n.navbar-nav > li > a:visited {\n color: var(--nav-link);\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\na:link.btn-default {\n color: var(--nav-link);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.small-box h3,\n.small-box p {\n color: #fff !important;\n}\n.small-box h3 a:hover,\n.small-box p a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n}\n.far fa-life-ring {\n color: var(--link);\n}\n#accessoriesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#assetsListingTable > tbody > tr.selected > td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box,\n.box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body,\n.box-footer,\n.box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default {\n background-color: var(--button-default);\n}\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n.btn-default:active,\n.btn-default:focus,\n.btn-default:hover,\n.btn-default.active {\n background-color: var(--button-hover);\n color: var(--nav-link);\n}\n.btn-primary,\n.btn-primary.hover,\n.btn-primary:active,\n.text-yellow {\n color: var(--nav-link) !important;\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n color: var(--nav-link) !important;\n}\n#componentsTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n#consumablesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form > div > div > div > span > i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover,\n.datepicker .next:hover,\n.datepicker .prev:hover,\n.datepicker tfoot tr th:hover,\n.datepicker table tr td.day.focused,\n.datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details > div > div > div > table {\n background-color: transparent;\n}\n.dropdown-menu > li > a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text],\ninput[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group,\n.input-group-addon {\n background-color: var(--back-sub) !important;\n color: var(--text-main);\n}\n#licensesTable > tbody > tr > td > nobr > a > i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li > a,\n.nav-tabs-custom > .nav-tabs > li.active > a {\n color: var(--text-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom > .nav-tabs > li.active > a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom,\n.nav-tabs-custom > .tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav > .tasks-menu > .dropdown-menu > li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open > .dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default > .panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true],\n.select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub) !important;\n color: var(--text-main) !important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-yellow-dark .main-header .navbar .dropdown-menu li a {\n color: var(--header);\n}\n.skin-yellow-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #000000;\n}\ntr th div.th-inner {\n color: #FFFFFF;\n}\n.tab-content,\n.tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped > tbody > tr:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n}\n.th-inner {\n color: var(--nav-link);\n}\n#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa,\n.box-body,\n.box-footer,\n.box-header {\n color: var(--text-main);\n}\n.box-header.with-border {\n border-bottom: #000;\n}\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n display: table;\n}\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even) {\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.search-highlight,\n.search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td {\n background-color: var(--back-sub);\n}\n","/*\n * Skin: Black\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-black {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@black; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@black, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n a {\n color: #333;\n &:hover {\n background: darken(@black, 5%);\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @black;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@black);\n}\n\n.skin-black.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n color: #000 ;\n\n &.btn-primary, .btn-primary:link {\n background-color: @dark-gray;\n border-color: #FFF;\n color: #FFF;\n }\n\n\n &a.btn-primary:hover {\n background-color: @black;\n border-color: darken(@dark-gray, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n color: #fff;\n }\n\n &.btn-white:hover {\n color: #fff;\n }\n\n &.btn-white:visited {\n color: #fff;\n }\n}\n\n\na {\n color: var(--link);\n\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: darken(@black, 20%);\n}\n:root {\n --button-default: darken(@black, 15%);\n --button-primary: darken(@black, 25%);\n --button-hover: darken(@black, 30%);\n --header: @black; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #black; /* Use same as Header picker, lighten by 70% */\n --visited-link: @black; /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(#000, 60%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\na.btn-info:link, a.btn-warning:link, a.btn-danger:link{\n color: #FFF;\n}\n&a.btn-info:visited, a.btn-warning:visited, a.btn-danger:visited{\n color: #FFF;\n}\n.btn-danger.btn-sm.disabled{\n color: #FFF;\n}\n.far fa-life-ring{\n color:var(--link);\n}\n\n.sidebar-toggle-mobile {\n color: #FFF !important;\n}\n\n.skin-black .main-header .navbar .nav>li>a, .skin-black .main-header .navbar .nav>li>a {\n text-decoration: none;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: @black;\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}","//AdminLTE mixins\n//===============\n\n\n//Changes the color and the hovering properties of the navbar\n.navbar-variant(@color; @font-color: rgba(255, 255, 255, 0.8); @hover-color: #f6f6f6; @hover-bg: rgba(0, 0, 0, 0.1)) {\n background-color: @color;\n //Navbar links\n .nav > li > a {\n color: @font-color;\n }\n\n .nav > li > a:hover,\n .nav > li > a:active,\n .nav > li > a:focus,\n .nav .open > a,\n .nav .open > a:hover,\n .nav .open > a:focus,\n .nav > .active > a {\n background: @hover-bg;\n color: @hover-color;\n }\n\n //Add color to the sidebar toggle button\n .sidebar-toggle {\n color: @font-color;\n &:hover {\n color: @hover-color;\n background: @hover-bg;\n }\n }\n}\n\n//Logo color variation\n.logo-variant(@bg-color; @color: #fff; @border-bottom-color: transparent; @border-bottom-width: 0) {\n background-color: @bg-color;\n color: @color;\n border-bottom: @border-bottom-width solid @border-bottom-color;\n\n &:hover {\n background-color: darken(@bg-color, 1%);\n }\n}\n\n//Box solid color variantion creator\n.box-solid-variant(@color; @text-color: #fff) {\n border: 1px solid @color;\n > .box-header {\n color: @text-color;\n background: @color;\n background-color: @color;\n a,\n .btn {\n color: @text-color;\n }\n }\n}\n\n//Direct Chat Variant\n.direct-chat-variant(@bg-color; @color: #fff) {\n .right > .direct-chat-text {\n background: @bg-color;\n border-color: @bg-color;\n color: @color;\n &:after,\n &:before {\n border-left-color: @bg-color;\n }\n }\n}\n\n//border radius creator\n.border-radius(@radius) {\n border-radius: @radius;\n}\n//Different radius each side\n.border-radius(@top-left; @top-right; @bottom-left; @bottom-right) {\n border-top-left-radius: @top-left;\n border-top-right-radius: @top-right;\n border-bottom-right-radius: @bottom-right;\n border-bottom-left-radius: @bottom-left;\n}\n\n//Gradient background\n.gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) {\n background: @color;\n background: -webkit-gradient(linear,\n left bottom,\n left top,\n color-stop(0, @start),\n color-stop(1, @stop));\n background: -ms-linear-gradient(bottom,\n @start,\n @stop);\n background: -moz-linear-gradient(center bottom,\n @start 0%,\n @stop 100%);\n background: -o-linear-gradient(@stop,\n @start);\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",@stop,@start));\n}\n\n//Added 2.1.0\n//Skins Mixins\n\n//Dark Sidebar Mixin\n.skin-dark-sidebar(@link-hover-border-color) {\n // Sidebar background color (Both .wrapper and .left-side are responsible for sidebar bg color)\n .wrapper,\n .main-sidebar,\n .left-side {\n background-color: @sidebar-dark-bg;\n }\n //User Panel (resides in the sidebar)\n .user-panel {\n > .info, > .info > a {\n color: #fff;\n }\n }\n //Sidebar Menu. First level links\n .sidebar-menu > li {\n //Section Headning\n &.header {\n color: lighten(@sidebar-dark-bg, 20%);\n background: darken(@sidebar-dark-bg, 4%);\n }\n //links\n > a {\n border-left: 3px solid transparent;\n }\n //Hover and active states\n &:hover > a, &.active > a {\n color: @sidebar-dark-hover-color;\n background: @sidebar-dark-hover-bg;\n border-left-color: @link-hover-border-color;\n }\n //First Level Submenu\n > .treeview-menu {\n margin: 0 1px;\n background: @sidebar-dark-submenu-bg;\n }\n }\n //All links within the sidebar menu\n .sidebar a {\n color: @sidebar-dark-color;\n &:hover {\n text-decoration: none;\n }\n }\n //All submenus\n .treeview-menu {\n > li {\n > a {\n color: @sidebar-dark-submenu-color;\n }\n &.active > a, > a:hover {\n color: @sidebar-dark-submenu-hover-color;\n }\n }\n }\n //The sidebar search form\n .sidebar-form {\n .border-radius(3px);\n border: 1px solid lighten(@sidebar-dark-bg, 10%);\n margin: 10px 10px;\n input[type=\"text\"], .btn {\n box-shadow: none;\n background-color: lighten(@sidebar-dark-bg, 10%);\n border: 1px solid transparent;\n height: 35px;\n .transition(all @transition-speed @transition-fn);\n }\n input[type=\"text\"] {\n color: #666;\n .border-radius(2px, 0, 2px, 0);\n &:focus, &:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n }\n &:focus + .input-group-btn .btn {\n border-left-color: #fff;\n }\n }\n .btn {\n color: #999;\n .border-radius(0, 2px, 0, 2px);\n }\n }\n}\n\n//Light Sidebar Mixin\n.skin-light-sidebar(@icon-active-color) {\n // Sidebar background color (Both .wrapper and .left-side are responsible for sidebar bg color)\n .wrapper,\n .main-sidebar,\n .left-side {\n background-color: @sidebar-light-bg;\n }\n .content-wrapper,\n .main-footer {\n border-left: 1px solid @gray;\n }\n //User Panel (resides in the sidebar)\n .user-panel {\n > .info, > .info > a {\n color: @sidebar-light-color;\n }\n }\n //Sidebar Menu. First level links\n .sidebar-menu > li {\n .transition(border-left-color .3s ease);\n //border-left: 3px solid transparent;\n //Section Headning\n &.header {\n color: lighten(@sidebar-light-color, 25%);\n background: @sidebar-light-bg;\n }\n //links\n > a {\n border-left: 3px solid transparent;\n font-weight: 600;\n }\n //Hover and active states\n &:hover > a,\n &.active > a {\n color: @sidebar-light-hover-color;\n background: @sidebar-light-hover-bg;\n }\n &:hover > a {\n\n }\n &.active {\n border-left-color: @icon-active-color;\n > a {\n font-weight: 600;\n }\n }\n //First Level Submenu\n > .treeview-menu {\n background: @sidebar-light-submenu-bg;\n }\n }\n //All links within the sidebar menu\n .sidebar a {\n color: @sidebar-light-color;\n &:hover {\n text-decoration: none;\n }\n }\n //All submenus\n .treeview-menu {\n > li {\n > a {\n color: @sidebar-light-submenu-color;\n }\n &.active > a,\n > a:hover {\n color: @sidebar-light-submenu-hover-color;\n }\n &.active > a {\n font-weight: 600;\n }\n }\n }\n //The sidebar search form\n .sidebar-form {\n .border-radius(3px);\n border: 1px solid @gray;//darken(@sidebar-light-bg, 5%);\n margin: 10px 10px;\n input[type=\"text\"],\n .btn {\n box-shadow: none;\n background-color: #fff;//darken(@sidebar-light-bg, 3%);\n border: 1px solid transparent;\n height: 35px;\n .transition(all @transition-speed @transition-fn);\n }\n input[type=\"text\"] {\n color: #666;\n .border-radius(2px, 0, 2px, 0);\n &:focus,\n &:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n }\n &:focus + .input-group-btn .btn {\n border-left-color: #fff;\n }\n }\n .btn {\n color: #999;\n .border-radius(0, 2px, 0, 2px);\n }\n }\n @media(min-width: @screen-sm-min) {\n &.sidebar-mini.sidebar-collapse {\n .sidebar-menu > li > .treeview-menu {\n border-left: 1px solid @gray;\n }\n }\n }\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","/*\n * Skin: black dark\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-black-dark {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@black; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@black, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n a {\n color: #333;\n &:hover {\n background: darken(@black, 5%);\n color: #fff;\n }\n }\n }\n }\n }\n }\n @media (max-width: 991px) {\n .navbar-custom-menu > .navbar-nav > li > .dropdown-menu {\n background-color: var(--back-sub);\n }\n }\n //Logo\n\n li.user-header {\n background-color: @black;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@black);\n}\n\n.skin-black-dark.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n color: #fff;\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@black, 10%);\n border-color: #FFF;\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@black, 20%);\n border-color: darken(@black, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@black, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@black, 30%);\n color: #fff;\n }\n}\n\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n\na {\n &:hover {\n color: #fff;\n }\n &:visited {\n color: #fff;\n }\n}\n\n.text-primary {\n color: #fff;\n}\n\n\n\n\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #3d4144;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: darken(@black, 15%);\n --button-primary: darken(@black, 25%);\n --button-hover: darken(@black, 30%);\n --header: @black; /* Use same as Header picker */\n --text-main: #fff;\n --text-sub: #9b9b9b;\n --link: #fff; /* Use same as Header picker, lighten by 70% */\n --visited-link: #fff; /* Use same as Header picker, lighten by 70% */\n --hover-link: #949494; /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n#ldapad_test_results.well.well-sm{\n color: var(--back-main);\n}\na.actions {\n color:#fff !important;\n}\n//pagination\n.pagination > li >a{\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover{\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{\n background-color: var(--background);\n}\na:hover > h2.task_menu{\n color:var(--header);\n}\nh2.task_menu{\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{\n background-color:var(--back-main);\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu > li.header{\n background-color:var(--header);\n color:var(--link);\n}\n\n.main-header .navbar, .main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%,var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%,var(--header) 100%);\n border-color: var(--header);\n}\n\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n\n.btn-primary.hover {\n color: var(--nav-link);\n}\n\n.small-box h3, .small-box p {\n color: var(--nav-link) !important;\n a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n }\n}\na:link.btn-default{\n color: var(--nav-link);\n}\n/* Trying to figure out a way of having links different shades based on what row they are on (even/odd).\na unique challenge for this dark mode skin.\nProbably writing the specificity for this incorrectly. --Godfrey Martinez 02/04/21\n\n.table-striped>tbody>tr:nth-of-type(even)>a:link{\n color:#000d07;\n}\n.table-striped>tbody>tr:nth-of-type(odd)>a:link{\n color:#bfbfbf;\n\n}*/\n\n#accessoriesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#assetsListingTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n\n#assetsListingTable>tbody>tr.selected>td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box, .box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body, .box-footer, .box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default{\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n\n.btn-default:active, .btn-default:focus, .btn-default:hover, .btn-default.active {\n background-color: var(--button-hover);\n color: var(--nav-link);\n}\n.btn-primary, .btn-primary.hover, .btn-primary:active, .text-black {\n color: var(--nav-link)!important;\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n color: var(--link)!important;\n}\n#componentsTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#consumablesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form>div>div>div>span>i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover, .datepicker .next:hover, .datepicker .prev:hover, .datepicker tfoot tr th:hover, .datepicker table tr td.day.focused, .datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details>div>div>div>table {\n background-color: transparent;\n}\n.dropdown-menu>li>a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1, h2, h3, h4, h5, h6, p {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text], input[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.search-highlight, .search-highlight:hover{\n background-color: var(--back-sub) !important;\n}\n.input-group, .input-group-addon {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n#licensesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li>a, .nav-tabs-custom>.nav-tabs>li.active>a {\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom, .nav-tabs-custom>.tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav>.tasks-menu>.dropdown-menu>li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open>.dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default>.panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--nav-link);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--back-sub);\n color: var(--visited-link);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub)!important;\n color: var(--text-main)!important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-black-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-black-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #000000;\n}\n.fixed-table-body thead th .th-inner, .skin-black-dark .sidebar-menu>li.active>a, .skin-black .sidebar-menu>li:hover>a, .sidebar-toggle:hover {\n background-color: var(--header)!important;\n}\n.tab-content, .tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(odd){\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {\n color: var(--text-main);\n}\n\n.box-header.with-border {\n border-bottom: #000;\n}\n\na {\n color: var(--link);\n\n &:link {\n color: var(--link) }\n &:hover {\n color: var(--hover-link);\n text-decoration: underline;\n }\n}\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\ndiv.container.row-new-striped{\n background-color: var(--back-sub);\n}\n.table > thead > tr > td.danger, .table > tbody > tr > td.danger, .table > tfoot > tr > td.danger, .table > thead > tr > th.danger, .table > tbody > tr > th.danger, .table > tfoot > tr > th.danger, .table > thead > tr.danger > td, .table > tbody > tr.danger > td, .table > tfoot > tr.danger > td, .table > thead > tr.danger > th, .table > tbody > tr.danger > th, .table > tfoot > tr.danger > th {\n background-color: var(--back-sub);\n}\n.table > tbody > tr.warning > td{\n background-color:var(--back-sub);\n}","/*\n * Skin: Blue\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-blue {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@light-blue; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@light-blue, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n a {\n color: #333;\n &:hover {\n background: darken(@light-blue, 5%);\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @light-blue;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@light-blue);\n}\n\n.skin-blue.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: unset;\n }\n}\n\n\n\n.btn, btn-sm, .btn:hover {\n\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@light-blue, 10%);\n border-color: darken(@light-blue, 20%);\n color: #fff !important;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@light-blue, 20%);\n border-color: darken(@light-blue, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@light-blue, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@light-blue, 30%);\n color: #fff;\n }\n\n &.btn-white:visited {\n background-color: darken(@light-blue, 30%);\n color: #fff;\n }\n}\n\n\n\n.btn-warning, .btn-warning:link, a.btn-warning:hover, .btn-warning:link, .btn-warning:visited,\n.btn-danger, .btn-danger:link, a.btn-danger:hover, .btn-danger:link, .btn-danger:visited\n{\n color: #fff;\n}\n\n.btn-default:link, a.btn-default:hover, .btn-default:visited\n{\n color: @dark-gray;\n}\n\n\n:root {\n --button-default: @dark-gray;\n --button-primary: darken(@blue, 25%);\n --button-hover: darken(@blue, 30%);\n --header: @blue; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link:darken(@blue, 15%); /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(@blue,10%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(@blue,20%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\na.btn-info:link, a.btn-warning:link, a.btn-danger:link{\n color: #FFF;\n}\n&a.btn-info:visited, a.btn-warning:visited, a.btn-danger:visited{\n color: #FFF;\n}\na:link {\n color: var(--link);\n}\n\na:visited {\n color: var(--visited-link);\n}\n\na:hover {\n color: var(--hover-link);\n}\n\n.text-primary {\n color: darken(@light-blue, 20%);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #3c8dbc;\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n\n// Link colors for the settings page\na.settings_button:link, a.settings_button:visited, a.settings_button:hover {\n color: @light-blue;\n}","/*\n * Skin: blue dark\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-blue-dark {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@blue; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@blue, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n a {\n color: #fff;\n &:hover {\n background: darken(@blue, 5%);\n color: #fff;\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @blue;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@blue);\n}\n\n.skin-blue-dark.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n color: #fff !important;\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@blue, 10%);\n border-color: #FFF;\n color: #FFF;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@blue, 20%);\n border-color: darken(@blue, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@blue, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@blue, 30%);\n color: #fff;\n }\n}\n\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n\n\na {\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: #fff;\n}\n\n\n\n\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #272727;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: darken(@blue, 15%);\n --button-primary: darken(@blue, 25%);\n --button-hover: darken(@blue, 30%);\n --header: @blue; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #00BFFF; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(#00BFFF,15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(#00BFFF,30%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\n#ldapad_test_results.well.well-sm{\n color: var(--back-main);\n}\na.actions {\n color:#fff !important;\n}\n//pagination\n.pagination > li >a{\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover{\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu{\n background-color: var(--back-main);\n}\n\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{\n background-color: var(--background);\n}\nh2.task_menu{\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{\n background-color:var(--back-main);\n color:var(--link);\n}\n\n.main-header .navbar, .main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%,var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%,var(--header) 100%);\n border-color: var(--header);\n}\n\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n\na:link {\n color: var(--link);\n}\n\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n.small-box h3, .small-box p {\n color: var(--nav-link) !important;\n a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n }\n}\n\n\n#accessoriesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#assetsListingTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n\n#assetsListingTable>tbody>tr.selected>td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box, .box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body, .box-footer, .box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default{\n background-color: var(--button-default);\n color: var(--link);\n}\n\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--link);\n}\n\n.btn-default:active, .btn-default:focus, .btn-default:hover, .btn-default.active {\n background-color: var(--button-hover);\n color: var(--link);\n}\n.btn-primary, .btn-primary.hover, .btn-primary:active, .text-blue {\n color: var(--text-main);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#consumablesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form>div>div>div>span>i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover, .datepicker .next:hover, .datepicker .prev:hover, .datepicker tfoot tr th:hover, .datepicker table tr td.day.focused, .datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details>div>div>div>table {\n background-color: transparent;\n}\n.dropdown-menu>li>a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner{\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1, h2, h3, h4, h5, h6, p {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text], input[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group, .input-group-addon {\n background-color: var(--back-sub)!important;\n color: var(--text-main);\n}\n#licensesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li>a, .nav-tabs-custom>.nav-tabs>li.active>a {\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom, .nav-tabs-custom>.tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav>.tasks-menu>.dropdown-menu>li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open>.dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default>.panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub)!important;\n color: var(--text-main)!important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-blue-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-blue-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #3c8dbc;\n}\n.fixed-table-body thead th .th-inner, .skin-blue-dark .sidebar-menu>li.active>a, .skin-blue .sidebar-menu>li:hover>a, .sidebar-toggle:hover {\n background-color: var(--header)!important;\n}\n.tab-content, .tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped>tbody>tr:nth-of-type(even){\n background-color: var(--back-sub-alt);\n}\n#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {\n color: var(--text-main);\n}\n\n.box-header.with-border {\n border-bottom: #000;\n}\n\na {\n color: var(--link);\n\n &:link {\n color: var(--link) }\n &:hover {\n color: var(--hover-link);\n text-decoration: underline;\n }\n &:visited {\n color: var(--visited-link)\n }\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets']{\n background-color: transparent;\n}\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd){\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td{\n background-color:var(--back-sub);\n}","/*\n * Skin: Contrast\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-contrast {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@navy; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@navy, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n a {\n color: #fff;\n &:hover {\n background: darken(@navy, 5%);\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @navy;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@navy);\n}\n\n.skin-contrast.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@navy, 10%);\n border-color: darken(@navy, 20%);\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@navy, 20%);\n border-color: darken(@navy, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@navy, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@navy, 30%);\n color: #fff;\n }\n}\na.btn.btn-link.text-left{\n color:@navy;\n border: 1px solid #000;\n}\na.btn.btn-link.text-left:hover{\n color:@navy;\n}\n\na {\n color: @navy;\n &:hover {\n color: @navy;\n text-decoration: underline;\n\n }\n &:visited {\n color: @navy;\n }\n}\n\na.btn {\n &:hover {\n color: #fff;\n text-decoration: underline;\n }\n}\n\n.text-primary {\n color: darken(@navy, 20%);\n}\n\n.skin-contrast .treeview-menu>li>a {\n color: #FFF;\n}\n\n.bg-teal {\n background-color: darken(@teal, 25%)!important;\n}\n\n.bg-orange {\n background-color: darken(@orange, 20%)!important;\n}\n\n.bg-purple {\n background-color: darken(@purple, 20%)!important;\n}\n\n.bg-maroon {\n background-color: darken(@maroon, 20%)!important;\n}\n\n.pagination>li>a {\n color: darken(@navy, 10%)!important;\n}\n\n.pagination>.active>a {\n background-color: @navy;\n color: #FFF!important;\n}\n\nbtn-success {\n background-color: darken(@green, 30%);\n}\n\n\n\ninput::-webkit-input-placeholder { /* Chrome/Opera/Safari */\n color: darken(@gray, 10%) !important;\n}\ninput::-moz-placeholder { /* Firefox 19+ */\n color: darken(@gray, 10%) !important;\n}\ninput::-ms-input-placeholder { /* IE 10+ */\n color: darken(@gray, 10%) !important;\n}\n\n.select2-default, .select2-selection__placeholder {\n color: darken(@dark-gray, 10%) !important;\n}\n\n.callout.callout-info {\n background-color: darken(@blue, 20%)!important;\n}\n\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}","/*\n * Skin: Green\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n:root{\n --background: #222;\n --back-main: #333;\n --back-sub: #3d4144;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: darken(@green, 15%);\n --button-primary: darken(@green, 25%);\n --button-hover: darken(@green, 30%);\n --header: @green; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: @green; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(@green, 15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(@green, 20%);\n}\n.skin-green {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@green; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@green, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n a {\n color: #333;\n &:hover {\n background: darken(@green, 5%);\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @green;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@green);\n}\n\n.skin-green.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@green, 10%);\n border-color: darken(@green, 20%);\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@green, 20%);\n border-color: darken(@green, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@green, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@green, 30%);\n color: #fff;\n }\n}\na.btn-info:link, a.btn-warning:link, a.btn-danger:link{\n color: #FFF;\n}\n&a.btn-info:visited, a.btn-warning:visited, a.btn-danger:visited{\n color: #FFF;\n}\na:link {\n color: var(--link);\n}\n\na:visited {\n color: var(--visited-link);\n}\n\na {\n color: @green;\n &:hover {\n color: darken(@green, 20%);\n }\n &:visited {\n color: @green;\n }\n}\n\n.text-primary {\n color: darken(@green, 20%);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: @green;\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}","/*\n * Skin: green dark\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-green-dark {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@green; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@green, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n a {\n color: #fff;\n &:hover {\n background: darken(@green, 5%);\n color: #fff;\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @green;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@green);\n}\n\n.skin-green-dark.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n color: #FFF !important;\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@green, 10%);\n border-color: #FFF;\n color: #FFF;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@green, 20%);\n border-color: darken(@green, 20%);\n color: #FFF;\n }\n\n &.btn-white:link {\n background-color: darken(@green, 10%);\n color: #FFF;\n }\n\n &.btn-white:hover {\n background-color: darken(@green, 30%);\n color: #FFF;\n }\n}\n\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n\na {\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: #fff;\n}\n\n\n\n\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #312F2F;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #006400;\n --button-primary: darken(#006400, 10%);\n --button-hover: darken(#006400, 30%);\n --header: #006400; /* Use same as Header picker */\n --text-main: #FFF;\n --text-sub: #9b9b9b;\n --link: #9ACD32; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(#9ACD32, 15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(#9ACD32, 30%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\n#ldapad_test_results.well.well-sm{\n color: var(--back-main);\n}\na.actions {\n color:#fff !important;\n}\n//pagination\n.pagination > li >a{\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover{\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu{\n background-color: var(--back-main);\n}\n\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{\n background-color: var(--background);\n}\nh2.task_menu{\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{\n background-color:var(--back-main);\n color:var(--link);\n}\n\n.main-header .navbar, .main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%,var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%,var(--header) 100%);\n border-color: var(--header);\n}\n\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n\n.btn-primary.hover {\n color: var(--nav-link);\n}\n\n.small-box h3, .small-box p {\n color: var(--nav-link) !important;\n a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n }\n}\n\n\n#accessoriesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#assetsListingTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n\n#assetsListingTable>tbody>tr.selected>td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box, .box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body, .box-footer, .box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default{\n background-color: var(--button-default);\n color: var(--link);\n}\n\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--link);\n}\n\n.btn-default:active, .btn-default:focus, .btn-default:hover, .btn-default.active {\n background-color: var(--button-hover);\n color: var(--link);\n}\n.btn-primary, .btn-primary.hover, .btn-primary:active, .text-green {\n color: var(--text-main);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#consumablesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form>div>div>div>span>i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover, .datepicker .next:hover, .datepicker .prev:hover, .datepicker tfoot tr th:hover, .datepicker table tr td.day.focused, .datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details>div>div>div>table {\n background-color: transparent;\n}\n.dropdown-menu>li>a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner{\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1, h2, h3, h4, h5, h6, p {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text], input[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group, .input-group-addon {\n background-color: var(--back-sub)!important;\n color: var(--text-main);\n}\n#licensesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li>a, .nav-tabs-custom>.nav-tabs>li.active>a {\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom, .nav-tabs-custom>.tab-content {\n background-color: var(--back-main);\n}\n.open>.dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default>.panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub)!important;\n color: var(--text-main)!important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-green-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-green-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #006300;\n}\n.fixed-table-body thead th .th-inner, .skin-green-dark .sidebar-menu>li.active>a, .skin-green .sidebar-menu>li:hover>a, .sidebar-toggle:hover {\n background-color: var(--header)!important;\n}\n.tab-content, .tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped>tbody>tr:nth-of-type(even){\n background-color: var(--back-sub);\n}\n#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {\n color: var(--text-main);\n}\n\n.box-header.with-border {\n border-bottom: #000;\n}\n\na {\n color: var(--link);\n\n &:link {\n color: var(--link) }\n &:hover {\n color: var(--hover-link);\n text-decoration: underline;\n }\n &:visited {\n color: var(--visited-link)\n }\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets']{\n background-color: transparent;\n}\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd){\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td{\n background-color:var(--back-sub);\n}","/*\n * Skin: orange dark\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-orange-dark {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@orange; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@orange, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n a {\n color: #fff;\n &:hover {\n background: darken(@orange, 5%);\n color: #fff;\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @orange;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@orange);\n}\n\n.skin-orange-dark.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n color: #fff !important;\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@orange, 10%);\n border-color: #fff;\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@orange, 20%);\n border-color: darken(@orange, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@orange, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@orange, 30%);\n color: #fff;\n }\n}\n\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n\n.text-primary {\n color: #fff;\n}\n\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #2E2C2C;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: darken(@orange, 15%);\n --button-primary: darken(@orange, 25%);\n --button-hover: darken(@orange, 30%);\n --header: @orange; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #FFA500; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(#FFA500, 15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(#FFA500, 30%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\n#ldapad_test_results.well.well-sm{\n color: var(--back-main);\n}\na.actions {\n color:#fff !important;\n}\n//pagination\n.pagination > li >a{\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover{\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu{\n background-color: var(--back-main);\n}\n\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{\n background-color: var(--background);\n}\nh2.task_menu{\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{\n background-color:var(--back-main);\n color:var(--link);\n}\n\n.main-header .navbar, .main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%,var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%,var(--header) 100%);\n border-color: var(--header);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n\na:link {\n color: var(--link);\n}\n\na:visited {\n color: var(--visited-link);\n}\n\na:hover {\n color: var(--hover-link);\n}\n\n.btn-primary.hover {\n color: var(--nav-link);\n}\n\n.small-box h3, .small-box p {\n color: var(--nav-link) !important;\n a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n }\n}\n\n\n#accessoriesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#assetsListingTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n\n#assetsListingTable>tbody>tr.selected>td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box, .box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body, .box-footer, .box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default{\n background-color: var(--button-default);\n color: var(--nav-link);\n}\na:link.btn-default{\n color: var(--nav-link);\n}\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n\n.btn-default:active, .btn-default:focus, .btn-default:hover, .btn-default.active {\n background-color: var(--button-hover);\n color: var(--background);\n}\n.btn-primary, .btn-primary.hover, .btn-primary:active, .text-orange {\n color: var(--nav-link)!important;\n border-color: var(--button-primary);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#consumablesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form>div>div>div>span>i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover, .datepicker .next:hover, .datepicker .prev:hover, .datepicker tfoot tr th:hover, .datepicker table tr td.day.focused, .datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details>div>div>div>table {\n background-color: transparent;\n}\n.dropdown-menu>li>a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner{\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1, h2, h3, h4, h5, h6, p {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text], input[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group, .input-group-addon {\n background-color: var(--back-sub)!important;\n color: var(--text-main);\n}\n#licensesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li>a, .nav-tabs-custom>.nav-tabs>li.active>a {\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom, .nav-tabs-custom>.tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav>.tasks-menu>.dropdown-menu>li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open>.dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default>.panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub)!important;\n color: var(--text-main)!important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-orange-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-orange-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #ff8c00;\n}\n.fixed-table-body thead th .th-inner, .skin-orange-dark .sidebar-menu>li.active>a, .skin-orange .sidebar-menu>li:hover>a, .sidebar-toggle:hover {\n background-color: var(--header)!important;\n}\n.tab-content, .tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped>tbody>tr:nth-of-type(even){\n background-color: var(--back-sub-alt);\n}\n#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {\n color: var(--text-main);\n}\n\n.box-header.with-border {\n border-bottom: #000;\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets']{\n background-color: transparent;\n}\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd){\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td{\n background-color:var(--back-sub);\n}","/*\n * Skin: Purple\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-purple {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@purple; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@purple, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n a {\n color: #333;\n &:hover {\n background: darken(@purple, 5%);\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @purple;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@purple);\n}\n\n.skin-purple.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@purple, 10%);\n border-color: darken(@purple, 20%);\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@purple, 20%);\n border-color: darken(@purple, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@purple, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@purple, 30%);\n color: #fff;\n }\n}\n\n\na {\n color: var(--link);\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: darken(@purple, 20%);\n}\n\n:root {\n --button-default: darken(@purple, 15%);\n --button-primary: darken(@purple, 25%);\n --button-hover: darken(@purple, 30%);\n --header: @purple; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: @purple; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(@purple, 15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(@purple, 30%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\na.btn-info:link, a.btn-warning:link, a.btn-danger:link{\n color: #FFF;\n}\n&a.btn-info:visited, a.btn-warning:visited, a.btn-danger:visited{\n color: #FFF;\n}\n\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: @purple;\n}\n.far fa-life-ring{\n color:var(--link);\n}\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}","/*\n * Skin: purple dark\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-purple-dark {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@purple; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@purple, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n a {\n color: #fff;\n &:hover {\n background: darken(@purple, 5%);\n color: #fff;\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @purple;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@purple);\n}\n\n.skin-purple-dark.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n color: #fff !important;\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@purple, 10%);\n border-color: #FFF;\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@purple, 20%);\n border-color: darken(@purple, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@purple, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@purple, 30%);\n color: #fff;\n }\n}\n\n\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n\na {\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: #fff;\n}\n\n\n\n\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #080808;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: darken(@purple, 15%);\n --button-primary: darken(@purple, 25%);\n --button-hover: darken(@purple, 30%);\n --header: @purple; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #AC83FF; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(#AC83FF, 15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(#AC83FF, 30%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\n#ldapad_test_results.well.well-sm{\n color: var(--back-main);\n}\n//pagination\n.pagination > li >a{\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--button-default);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover{\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu{\n background-color: var(--back-main);\n}\n\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{\n background-color: var(--background);\n}\nh2.task_menu{\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{\n background-color:var(--back-main);\n color:var(--link);\n}\n\n.main-header .navbar, .main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%,var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%,var(--header) 100%);\n border-color: var(--header);\n}\n\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n.label-default{\n background-color:var(--back-sub);\n color:#fff;\n}\n\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n\na:link {\n color: var(--link);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n\n.small-box h3, .small-box p {\n color: var(--nav-link) !important;\n a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n }\n}\n\na:link.btn-default{\n color: var(--nav-link);\n}\n#accessoriesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#assetsListingTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n\n#assetsListingTable>tbody>tr.selected>td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box, .box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body, .box-footer, .box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default{\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n\n.btn-default:active, .btn-default:focus, .btn-default:hover, .btn-default.active {\n background-color: var(--button-hover);\n color: var(--nav-link);\n}\n.btn-primary, .btn-primary.hover, .btn-primary:active, .text-purple {\n color: var(--nav-link)!important;\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#consumablesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form>div>div>div>span>i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover, .datepicker .next:hover, .datepicker .prev:hover, .datepicker tfoot tr th:hover, .datepicker table tr td.day.focused, .datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details>div>div>div>table {\n background-color: transparent;\n}\n.dropdown-menu>li>a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner{\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1, h2, h3, h4, h5, h6, p {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text], input[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group, .input-group-addon {\n background-color: var(--back-sub)!important;\n color: var(--text-main);\n}\n#licensesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li>a, .nav-tabs-custom>.nav-tabs>li.active>a {\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom, .nav-tabs-custom>.tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav>.tasks-menu>.dropdown-menu>li.header {\n background-color: var(--back-main);\n color: var(--link);\n}\n.open>.dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default>.panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub)!important;\n color: var(--text-main)!important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-purple-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-purple-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #5f5ca8;\n}\n.fixed-table-body thead th .th-inner, .skin-purple-dark .sidebar-menu>li.active>a, .skin-purple .sidebar-menu>li:hover>a, .sidebar-toggle:hover {\n background-color: var(--header)!important;\n}\n.tab-content, .tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped>tbody>tr:nth-of-type(even){\n background-color: var(--back-sub-alt);\n}\n#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {\n color: var(--text-main);\n}\n\n.box-header.with-border {\n border-bottom: #000;\n}\n\na {\n color: var(--link);\n\n &:link {\n color: var(--link) }\n &:hover {\n color: var(--hover-link);\n text-decoration: underline;\n }\n &:visited {\n color: var(--visited-link)\n }\n}\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd){\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td{\n background-color:var(--back-sub);\n}","/*\n * Skin: Red\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-red {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@red; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@red, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n a {\n color: #333;\n &:hover {\n background: darken(@red, 5%);\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @red;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@red);\n}\n\n.skin-red.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@red, 10%);\n border-color: darken(@red, 20%);\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@red, 20%);\n border-color: darken(@red, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@red, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@red, 30%);\n color: #fff;\n }\n}\n\n\na {\n color: @red;\n &:hover {\n color: darken(@red, 20%);\n }\n &:visited {\n color: @red;\n }\n}\n\n.text-primary {\n color: darken(@red, 20%);\n}\n\n:root {\n --button-default: darken(@red, 15%);\n --button-primary: darken(@red, 25%);\n --button-hover: darken(@red, 30%);\n --header: @red; /* Use same as Header picker */\n --text-main: #BBB;\n --text-alt: #222;\n --text-sub: #9b9b9b;\n --link: @red; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(@red, 15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(@red, 30%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\na:link {\n color: var(--link);\n}\n\na:visited {\n color: var(--visited-link);\n}\na.btn-info:link, a.btn-warning:link, a.btn-danger:link{\n color: #FFF;\n}\n&a.btn-info:visited, a.btn-warning:visited, a.btn-danger:visited{\n color: #FFF;\n}\n\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: @red;\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.far fa-life-ring{\n color:var(--link);\n}","/*\n * Skin: red dark\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-red-dark {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@red; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@red, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n a {\n color: #fff;\n &:hover {\n background: darken(@red, 5%);\n color: #fff;\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @red;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@red);\n}\n\n.skin-red-dark.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n.btn, .btn:hover {\n color: #fff;\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@red, 10%);\n border-color: #FFF;\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@red, 20%);\n border-color: darken(@red, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@red, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@red, 30%);\n color: #fff;\n }\n}\n\n\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n\n\na {\n &:hover {\n color: #fff;\n }\n &:visited {\n color: #fff;\n }\n}\n\n.text-primary {\n color: #fff;\n}\n\n\n\n\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #3d4144;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: darken(@red, 15%);\n --button-primary: darken(@red, 25%);\n --button-hover: darken(@red, 30%);\n --header: @red; /* Use same as Header picker */\n --text-main: #BBB;\n --text-alt: #222;\n --text-sub: #9b9b9b;\n --link: #e00000; /* Use same as Header picker, lighten by 70% */\n --visited-link: #D24D57; /* Use same as Header picker, lighten by 70% */\n --hover-link: #D24D57; /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\n#ldapad_test_results.well.well-sm{\n color: var(--back-main);\n}\n\n//pagination\n.pagination > li >a{\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover{\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu{\n background-color: var(--back-main);\n}\n\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{\n background-color: var(--background);\n}\nh2.task_menu{\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{\n background-color:var(--back-main);\n color:var(--link);\n}\n.main-header .navbar, .main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%,var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%,var(--header) 100%);\n border-color: var(--header);\n}\n\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n\na:link {\n color: var(--link);\n}\na:hover {\n color: var(--hover-link);\n}\na:visited {\n color: var(--nav-link);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n\n.btn-primary.hover {\n color: var(--nav-link);\n}\n\n.small-box h3, .small-box p {\n color: var(--nav-link) !important;\n a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n }\n}\n\n\n#accessoriesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#assetsListingTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n\n#assetsListingTable>tbody>tr.selected>td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box, .box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body, .box-footer, .box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default{\n background-color: var(--back-sub);\n}\n\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--link);\n}\n\n.btn-default:active, .btn-default:focus, .btn-default:hover, .btn-default.active {\n background-color: var(--button-hover);\n color: var(--link);\n}\n.btn-primary, .btn-primary.hover, .btn-primary:active, .text-red {\n color: var(--button-primary);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#consumablesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form>div>div>div>span>i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover, .datepicker .next:hover, .datepicker .prev:hover, .datepicker tfoot tr th:hover, .datepicker table tr td.day.focused, .datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details>div>div>div>table {\n background-color: transparent;\n}\n.dropdown-menu>li>a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner{\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1, h2, h3, h4, h5, h6, p {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text], input[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group, .input-group-addon {\n background-color: var(--back-sub)!important;\n color: var(--text-main);\n}\n#licensesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li>a, .nav-tabs-custom>.nav-tabs>li.active>a {\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom, .nav-tabs-custom>.tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav>.tasks-menu>.dropdown-menu>li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open>.dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default>.panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub)!important;\n color: var(--text-main)!important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-red-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-red-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #c23320;\n}\n.fixed-table-body thead th .th-inner, .skin-red-dark .sidebar-menu>li.active>a, .skin-red .sidebar-menu>li:hover>a, .sidebar-toggle:hover {\n background-color: var(--header)!important;\n}\n.tab-content, .tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped>tbody>tr:nth-of-type(even){\n background-color: var(--back-sub-alt);\n}\n#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {\n color: var(--text-main);\n}\n\n.box-header.with-border {\n border-bottom: #000;\n}\n#upload-table tbody > tr.warning > td,#upload-table h3,#upload-table p{\n background-color:#fcf8e3;\n color:#000;\n}\n\na {\n color: var(--link);\n\n &:link {\n color: var(--nav-link);\n }\n &:hover {\n color: var(--nav-link);\n text-decoration: underline;\n }\n &:visited {\n color: var(--nav-link);\n }\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets']{\n background-color: transparent;\n}\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n\n.row-new-striped > .row:nth-of-type(odd){\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td{\n background-color:var(--back-sub);\n}","/*\n * Skin: Orange\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-orange {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@orange; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@orange, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n a {\n color: #333;\n &:hover {\n background: darken(@orange, 5%);\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @orange;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@orange);\n}\n\n.skin-orange.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@orange, 10%);\n border-color: darken(@orange, 20%);\n color: #fff;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@orange, 20%);\n border-color: darken(@orange, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@orange, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@orange, 30%);\n color: #fff;\n }\n}\n\n\na {\n color: var(--link);\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: darken(@orange, 20%);\n}\n:root {\n --button-default: darken(@orange, 15%);\n --button-primary: darken(@orange, 25%);\n --button-hover: darken(@orange, 30%);\n --header: @orange; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: @orange; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(@orange, 10%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(@orange, 15%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\na.btn-info:link, a.btn-warning:link, a.btn-danger:link{\n color: #FFF;\n}\n&a.btn-info:visited, a.btn-warning:visited, a.btn-danger:visited{\n color: #FFF;\n}\n.far fa-life-ring{\n color:var(--link);\n}\n\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: @orange;\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}","/*\n * Skin: Yellow\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-yellow {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(#FBCC34);\n .sidebar-toggle {\n color: #000;\n &:hover {\n background-color: darken(#FBCC34, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n }\n a {\n color: #333;\n &:hover {\n background: darken(#FBCC34, 5%);\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: #FBCC34;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(#FBCC34);\n}\n\n.skin-yellow.layout-top-nav .main-header > .logo {\n .logo-variant {\n color: #413F42;\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(#FBCC34, 10%);\n border-color: darken(#FBCC34, 20%);\n color: var(--nav-link);\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(#FBCC34, 20%);\n border-color: darken(#FBCC34, 20%);\n color: var(--nav-link);\n }\n\n &.btn-white:link {\n background-color: darken(#FBCC34, 10%);\n color: var(--nav-link);\n }\n\n &.btn-white:hover {\n background-color: darken(#FBCC34, 30%);\n color: var(--nav-link);\n }\n}\na.btn-info:link, a.btn-warning:link, a.btn-danger:link{\n color: #FFF;\n}\n&a.btn-info:visited, a.btn-warning:visited, a.btn-danger:visited{\n color: #FFF;\n}\n\na {\n color: var(--link);\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: darken(#FBCC34, 20%);\n}\n\n:root {\n --button-default: darken(#FBCC34, 15%);\n --button-primary: darken(#FBCC34, 25%);\n --button-hover: darken(#FBCC34, 30%);\n --header: #FBCC34; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: lighten( #413F42, 10%); /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(#413F42, 20%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(#413F42, 45%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #222; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\n.fixed-table-container tbody .selected td {\n background-color: #fff8af;\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: #FBCC34;\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n.navbar-nav>li>a:visited {\n color: var(--nav-link);\n}\n&:hover{\n color: var(--nav-link);\n}\n.skin-yellow .main-header .navbar .nav > li > a {\n color: #413F42;\n}\n.far fa-life-ring{\n color:var(--link);\n}","/*\n * Skin: yellow dark\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-yellow-dark {\n //Navbar\n .main-header {\n .navbar {\n .sidebar-toggle {\n color: #000 !important;\n &:hover {\n background-color: var(--link);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n a {\n color: #fff;\n &:hover {\n background: darken(@yellow, 5%);\n color: #fff;\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @yellow;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@yellow);\n}\n\n.btn, .btn:hover {\n\n &.btn-primary, .btn-primary:link {\n background-color: var(--button-default);\n border-color: #000000;\n color: #545454;\n }\n\n &a.btn-primary:hover {\n background-color: var(--button-hover);\n border-color: var(--button-hover);\n color: #545454;\n }\n\n &.btn-white:link {\n background-color: var(--button-default);\n color: #545454;\n }\n\n &.btn-white:hover {\n background-color: var(--hover-link);\n color: #545454;\n }\n}\na.actions {\n color:#fff !important;\n}\na:visited.label-default, a:link.label-default{\n color:#444;\n}\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n\na {\n &:link {\n color: var(--link);\n }\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: #fff;\n}\n\n\n\n\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #3d4144;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: #FFCC32;\n --button-primary: darken(#FFCC32, 25%);\n --button-hover: darken(#FFCC32, 30%);\n --header: #FFCC32; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #F0E68C; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(#F0E68C, 15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(#F0E68C, 45%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #222; /* Use same as Header picker */\n --light-link: #545454; /* Use same as Header picker */\n}\n\n#ldapad_test_results.well.well-sm{\n color: var(--back-main);\n}\n//pagination\n.pagination > li >a{\n color: var(--text-main);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--button-default);\n border-color: var(--light-link);\n color:var(--nav-link);\n}\n.pagination > .active > a:hover{\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu{\n background-color: var(--back-main);\n}\n\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{\n background-color: var(--background);\n}\nh2.task_menu{\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{\n background-color:var(--back-main);\n color:var(--link);\n}\n.main-header .navbar, .main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%,var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%,var(--header) 100%);\n border-color: var(--header);\n}\na.btn.btn-default{\n color:var(--nav-link);\n}\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--text-main);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n.navbar-nav>li>a:visited {\n color: var(--nav-link);\n}\n\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n\na:link.btn-default{\n color: var(--nav-link);\n}\n.btn-primary.hover {\n color: var(--nav-link);\n}\n\n.small-box h3, .small-box p {\n color: #fff !important;\n a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n }\n}\n.far fa-life-ring{\n color:var(--link);\n}\n\n#accessoriesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#assetsListingTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n\n#assetsListingTable>tbody>tr.selected>td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box, .box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body, .box-footer, .box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default{\n background-color: var(--button-default);\n\n}\n\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--nav-link);\n}\n\n.btn-default:active, .btn-default:focus, .btn-default:hover, .btn-default.active {\n background-color: var(--button-hover);\n color: var(--nav-link);\n}\n.btn-primary, .btn-primary.hover, .btn-primary:active, .text-yellow {\n color: var(--nav-link)!important;\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n color: var(--nav-link)!important;\n}\n#componentsTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#consumablesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form>div>div>div>span>i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover, .datepicker .next:hover, .datepicker .prev:hover, .datepicker tfoot tr th:hover, .datepicker table tr td.day.focused, .datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details>div>div>div>table {\n background-color: transparent;\n}\n.dropdown-menu>li>a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1, h2, h3, h4, h5, h6, p {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text], input[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group, .input-group-addon {\n background-color: var(--back-sub)!important;\n color: var(--text-main);\n}\n#licensesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li>a, .nav-tabs-custom>.nav-tabs>li.active>a {\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom, .nav-tabs-custom>.tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav>.tasks-menu>.dropdown-menu>li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open>.dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default>.panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub)!important;\n color: var(--text-main)!important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-yellow-dark .main-header .navbar .dropdown-menu li a {\n color: var(--header);\n}\n.skin-yellow-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #000000;\n}\ntr th div.th-inner {\n color: #FFFFFF;\n}\n.tab-content, .tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped>tbody>tr:nth-of-type(even){\n background-color: var(--back-sub-alt);\n}\n.th-inner{\n color: var(--nav-link);\n}\n#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {\n color: var(--text-main);\n}\n\n.box-header.with-border {\n border-bottom: #000;\n}\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n\n.row-new-striped > .row:nth-of-type(odd){\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td{\n background-color:var(--back-sub);\n}"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/public/css/dist/skins/_all-skins.min.css b/public/css/dist/skins/_all-skins.min.css new file mode 100644 index 0000000000..6e7722f34b --- /dev/null +++ b/public/css/dist/skins/_all-skins.min.css @@ -0,0 +1,5473 @@ +/* + * Skin: Black + * ---------- + */ +.skin-black .main-header .navbar { + background-color: #111; +} +.skin-black .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-black .main-header .navbar .nav > li > a:hover, +.skin-black .main-header .navbar .nav > li > a:active, +.skin-black .main-header .navbar .nav > li > a:focus, +.skin-black .main-header .navbar .nav .open > a, +.skin-black .main-header .navbar .nav .open > a:hover, +.skin-black .main-header .navbar .nav .open > a:focus, +.skin-black .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-black .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-black .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-black .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-black .main-header .navbar .sidebar-toggle:hover { + background-color: #040404; +} +@media (max-width: 767px) { + .skin-black .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-black .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-black .main-header .navbar .dropdown-menu li a:hover { + background: #040404; + } +} +.skin-black .main-header li.user-header { + background-color: #111; +} +.skin-black .content-header { + background: transparent; +} +.skin-black .wrapper, +.skin-black .main-sidebar, +.skin-black .left-side { + background-color: #222d32; +} +.skin-black .user-panel > .info, +.skin-black .user-panel > .info > a { + color: #fff; +} +.skin-black .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-black .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-black .sidebar-menu > li:hover > a, +.skin-black .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #111; +} +.skin-black .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-black .sidebar a { + color: #b8c7ce; +} +.skin-black .sidebar a:hover { + text-decoration: none; +} +.skin-black .treeview-menu > li > a { + color: #8aa4af; +} +.skin-black .treeview-menu > li.active > a, +.skin-black .treeview-menu > li > a:hover { + color: #fff; +} +.skin-black .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-black .sidebar-form input[type="text"], +.skin-black .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-black .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-black .sidebar-form input[type="text"]:focus, +.skin-black .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-black .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-black .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-black.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #000; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #505156; + border-color: #FFF; + color: #FFF; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #111; + border-color: #1f1f21; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + color: #fff; +} +.btn.btn-white:visited, +.btn:hover.btn-white:visited { + color: #fff; +} +a { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #000000; +} +:root { + --button-default: #000000; + --button-primary: #000000; + --button-hover: #000000; + --header: #111; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #black; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #111; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #999999; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +.btn-danger.btn-sm.disabled { + color: #FFF; +} +.far fa-life-ring { + color: var(--link); +} +.sidebar-toggle-mobile { + color: #FFF !important; +} +.skin-black .main-header .navbar .nav > li > a, +.skin-black .main-header .navbar .nav > li > a { + text-decoration: none; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #111; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: black dark + * ---------- + */ +.skin-black-dark .main-header .navbar { + background-color: #111; +} +.skin-black-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-black-dark .main-header .navbar .nav > li > a:hover, +.skin-black-dark .main-header .navbar .nav > li > a:active, +.skin-black-dark .main-header .navbar .nav > li > a:focus, +.skin-black-dark .main-header .navbar .nav .open > a, +.skin-black-dark .main-header .navbar .nav .open > a:hover, +.skin-black-dark .main-header .navbar .nav .open > a:focus, +.skin-black-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-black-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-black-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-black-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-black-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #040404; +} +@media (max-width: 767px) { + .skin-black-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-black-dark .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-black-dark .main-header .navbar .dropdown-menu li a:hover { + background: #040404; + color: #fff; + } +} +@media (max-width: 991px) { + .skin-black-dark .main-header .navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-sub); + } +} +.skin-black-dark .main-header li.user-header { + background-color: #111; +} +.skin-black-dark .content-header { + background: transparent; +} +.skin-black-dark .wrapper, +.skin-black-dark .main-sidebar, +.skin-black-dark .left-side { + background-color: #222d32; +} +.skin-black-dark .user-panel > .info, +.skin-black-dark .user-panel > .info > a { + color: #fff; +} +.skin-black-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-black-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-black-dark .sidebar-menu > li:hover > a, +.skin-black-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #111; +} +.skin-black-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-black-dark .sidebar a { + color: #b8c7ce; +} +.skin-black-dark .sidebar a:hover { + text-decoration: none; +} +.skin-black-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-black-dark .treeview-menu > li.active > a, +.skin-black-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-black-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-black-dark .sidebar-form input[type="text"], +.skin-black-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-black-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-black-dark .sidebar-form input[type="text"]:focus, +.skin-black-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-black-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-black-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-black-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #000000; + border-color: #FFF; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #000000; + border-color: #000000; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #000000; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #000000; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: #fff; +} +a:visited { + color: #fff; +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #3d4144; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #000000; + --button-primary: #000000; + --button-hover: #000000; + --header: #111; + /* Use same as Header picker */ + --text-main: #fff; + --text-sub: #9b9b9b; + --link: #fff; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #fff; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #949494; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +a.actions { + color: #fff !important; +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +a:hover > h2.task_menu { + color: var(--header); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu > li.header { + background-color: var(--header); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.far fa-life-ring { + color: var(--link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +a:link.btn-default { + color: var(--nav-link); +} +/* Trying to figure out a way of having links different shades based on what row they are on (even/odd). +a unique challenge for this dark mode skin. +Probably writing the specificity for this incorrectly. --Godfrey Martinez 02/04/21 + +.table-striped>tbody>tr:nth-of-type(even)>a:link{ + color:#000d07; +} +.table-striped>tbody>tr:nth-of-type(odd)>a:link{ + color:#bfbfbf; + +}*/ +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--nav-link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-black { + color: var(--nav-link) !important; +} +.btn-primary:hover { + background-color: var(--button-primary); + color: var(--link) !important; +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: var(--back-sub) !important; +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--nav-link); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--back-sub); + color: var(--visited-link); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-black-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-black-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #000000; +} +.fixed-table-body thead th .th-inner, +.skin-black-dark .sidebar-menu > li.active > a, +.skin-black .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); + text-decoration: underline; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +div.container.row-new-striped { + background-color: var(--back-sub); +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: var(--back-sub); +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Blue + * ---------- + */ +.skin-blue .main-header .navbar { + background-color: #3c8dbc; +} +.skin-blue .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-blue .main-header .navbar .nav > li > a:hover, +.skin-blue .main-header .navbar .nav > li > a:active, +.skin-blue .main-header .navbar .nav > li > a:focus, +.skin-blue .main-header .navbar .nav .open > a, +.skin-blue .main-header .navbar .nav .open > a:hover, +.skin-blue .main-header .navbar .nav .open > a:focus, +.skin-blue .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-blue .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-blue .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-blue .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-blue .main-header .navbar .sidebar-toggle:hover { + background-color: #367fa9; +} +@media (max-width: 767px) { + .skin-blue .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-blue .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-blue .main-header .navbar .dropdown-menu li a:hover { + background: #367fa9; + } +} +.skin-blue .main-header li.user-header { + background-color: #3c8dbc; +} +.skin-blue .content-header { + background: transparent; +} +.skin-blue .wrapper, +.skin-blue .main-sidebar, +.skin-blue .left-side { + background-color: #222d32; +} +.skin-blue .user-panel > .info, +.skin-blue .user-panel > .info > a { + color: #fff; +} +.skin-blue .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-blue .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-blue .sidebar-menu > li:hover > a, +.skin-blue .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #3c8dbc; +} +.skin-blue .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-blue .sidebar a { + color: #b8c7ce; +} +.skin-blue .sidebar a:hover { + text-decoration: none; +} +.skin-blue .treeview-menu > li > a { + color: #8aa4af; +} +.skin-blue .treeview-menu > li.active > a, +.skin-blue .treeview-menu > li > a:hover { + color: #fff; +} +.skin-blue .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-blue .sidebar-form input[type="text"], +.skin-blue .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-blue .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-blue .sidebar-form input[type="text"]:focus, +.skin-blue .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-blue .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-blue .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-blue.layout-top-nav .main-header > .logo .logo-variant { + background-color: unset; +} +.btn.btn-primary, +btn-sm.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +btn-sm .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #307095; + border-color: #23536f; + color: #fff !important; +} +.btna.btn-primary:hover, +btn-sma.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #23536f; + border-color: #23536f; + color: #fff; +} +.btn.btn-white:link, +btn-sm.btn-white:link, +.btn:hover.btn-white:link { + background-color: #307095; + color: #fff; +} +.btn.btn-white:hover, +btn-sm.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #173648; + color: #fff; +} +.btn.btn-white:visited, +btn-sm.btn-white:visited, +.btn:hover.btn-white:visited { + background-color: #173648; + color: #fff; +} +.btn-warning, +.btn-warning:link, +a.btn-warning:hover, +.btn-warning:link, +.btn-warning:visited, +.btn-danger, +.btn-danger:link, +a.btn-danger:hover, +.btn-danger:link, +.btn-danger:visited { + color: #fff; +} +.btn-default:link, +a.btn-default:hover, +.btn-default:visited { + color: #505156; +} +:root { + --button-default: #505156; + --button-primary: #1d455b; + --button-hover: #173648; + --header: #3c8dbc; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #296282; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #5fa4cc; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #86bad8; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +a:link { + color: var(--link); +} +a:visited { + color: var(--visited-link); +} +a:hover { + color: var(--hover-link); +} +.text-primary { + color: #23536f; +} +.far fa-life-ring { + color: var(--link); +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #3c8dbc; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +a.settings_button:link, +a.settings_button:visited, +a.settings_button:hover { + color: #3c8dbc; +} +a.label.label-default:link { + color: #307095; + /* Use same as Header picker, lighten by 70% */ +} +a.label.label-default:visited { + color: #23536f; + /* Use same as Header picker, lighten by 70% */ +} +a.label.label-default:hover { + background-color: #BBB; + color: #296282; +} +/* + * Skin: blue dark + * ---------- + */ +.skin-blue-dark .main-header .navbar { + background-color: #3c8dbc; +} +.skin-blue-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-blue-dark .main-header .navbar .nav > li > a:hover, +.skin-blue-dark .main-header .navbar .nav > li > a:active, +.skin-blue-dark .main-header .navbar .nav > li > a:focus, +.skin-blue-dark .main-header .navbar .nav .open > a, +.skin-blue-dark .main-header .navbar .nav .open > a:hover, +.skin-blue-dark .main-header .navbar .nav .open > a:focus, +.skin-blue-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-blue-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-blue-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-blue-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-blue-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #367fa9; +} +@media (max-width: 767px) { + .skin-blue-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-blue-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-blue-dark .main-header .navbar .dropdown-menu li a:hover { + background: #367fa9; + color: #fff; + } +} +.skin-blue-dark .main-header li.user-header { + background-color: #3c8dbc; +} +.skin-blue-dark .content-header { + background: transparent; +} +.skin-blue-dark .wrapper, +.skin-blue-dark .main-sidebar, +.skin-blue-dark .left-side { + background-color: #222d32; +} +.skin-blue-dark .user-panel > .info, +.skin-blue-dark .user-panel > .info > a { + color: #fff; +} +.skin-blue-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-blue-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-blue-dark .sidebar-menu > li:hover > a, +.skin-blue-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #3c8dbc; +} +.skin-blue-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-blue-dark .sidebar a { + color: #b8c7ce; +} +.skin-blue-dark .sidebar a:hover { + text-decoration: none; +} +.skin-blue-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-blue-dark .treeview-menu > li.active > a, +.skin-blue-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-blue-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-blue-dark .sidebar-form input[type="text"], +.skin-blue-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-blue-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-blue-dark .sidebar-form input[type="text"]:focus, +.skin-blue-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-blue-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-blue-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-blue-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff !important; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #307095; + border-color: #FFF; + color: #FFF; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #23536f; + border-color: #23536f; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #307095; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #173648; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #272727; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #296282; + --button-primary: #1d455b; + --button-hover: #173648; + --header: #3c8dbc; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #00BFFF; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #4dd2ff; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #99e5ff; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +a.actions { + color: #fff !important; +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.far fa-life-ring { + color: var(--link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link { + color: var(--link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-blue { + color: var(--text-main); +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-blue-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-blue-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #3c8dbc; +} +.fixed-table-body thead th .th-inner, +.skin-blue-dark .sidebar-menu > li.active > a, +.skin-blue .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-main); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); + text-decoration: underline; +} +a:visited { + color: var(--visited-link); +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} +#customFieldsTable a[href*='/fieldsets'] { + background-color: transparent; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Contrast + * ---------- + */ +.skin-contrast .main-header .navbar { + background-color: #001F3F; +} +.skin-contrast .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-contrast .main-header .navbar .nav > li > a:hover, +.skin-contrast .main-header .navbar .nav > li > a:active, +.skin-contrast .main-header .navbar .nav > li > a:focus, +.skin-contrast .main-header .navbar .nav .open > a, +.skin-contrast .main-header .navbar .nav .open > a:hover, +.skin-contrast .main-header .navbar .nav .open > a:focus, +.skin-contrast .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-contrast .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-contrast .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-contrast .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-contrast .main-header .navbar .sidebar-toggle:hover { + background-color: #001226; +} +@media (max-width: 767px) { + .skin-contrast .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-contrast .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-contrast .main-header .navbar .dropdown-menu li a:hover { + background: #001226; + } +} +.skin-contrast .main-header li.user-header { + background-color: #001F3F; +} +.skin-contrast .content-header { + background: transparent; +} +.skin-contrast .wrapper, +.skin-contrast .main-sidebar, +.skin-contrast .left-side { + background-color: #222d32; +} +.skin-contrast .user-panel > .info, +.skin-contrast .user-panel > .info > a { + color: #fff; +} +.skin-contrast .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-contrast .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-contrast .sidebar-menu > li:hover > a, +.skin-contrast .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #001F3F; +} +.skin-contrast .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-contrast .sidebar a { + color: #b8c7ce; +} +.skin-contrast .sidebar a:hover { + text-decoration: none; +} +.skin-contrast .treeview-menu > li > a { + color: #8aa4af; +} +.skin-contrast .treeview-menu > li.active > a, +.skin-contrast .treeview-menu > li > a:hover { + color: #fff; +} +.skin-contrast .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-contrast .sidebar-form input[type="text"], +.skin-contrast .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-contrast .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-contrast .sidebar-form input[type="text"]:focus, +.skin-contrast .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-contrast .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-contrast .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-contrast.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #00060c; + border-color: #000000; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #000000; + border-color: #000000; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #00060c; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #000000; + color: #fff; +} +a.btn.btn-link.text-left { + color: #001F3F; + border: 1px solid #000; +} +a.btn.btn-link.text-left:hover { + color: #001F3F; +} +a { + color: #001F3F; +} +a:hover { + color: #001F3F; + text-decoration: underline; +} +a:visited { + color: #001F3F; +} +a.btn:hover { + color: #fff; + text-decoration: underline; +} +.text-primary { + color: #000000; +} +.skin-contrast .treeview-menu > li > a { + color: #FFF; +} +.bg-teal { + background-color: #1b6a6a !important; +} +.bg-orange { + background-color: #995400 !important; +} +.bg-purple { + background-color: #3a3767 !important; +} +.bg-maroon { + background-color: #7d1038 !important; +} +.pagination > li > a { + color: #00060c !important; +} +.pagination > .active > a { + background-color: #001F3F; + color: #FFF !important; +} +btn-success { + background-color: #000d07; +} +input::-webkit-input-placeholder { + /* Chrome/Opera/Safari */ + color: #b5bbc8 !important; +} +input::-moz-placeholder { + /* Firefox 19+ */ + color: #b5bbc8 !important; +} +input::-ms-input-placeholder { + /* IE 10+ */ + color: #b5bbc8 !important; +} +.select2-default, +.select2-selection__placeholder { + color: #37383c !important; +} +.callout.callout-info { + background-color: #23536f !important; +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: Green + * ---------- + */ +:root { + --background: #222; + --back-main: #333; + --back-sub: #3d4144; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #005a31; + --button-primary: #002715; + --button-hover: #000d07; + --header: #00a65a; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #00a65a; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #00f383; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #0dff90; +} +.skin-green .main-header .navbar { + background-color: #00a65a; +} +.skin-green .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-green .main-header .navbar .nav > li > a:hover, +.skin-green .main-header .navbar .nav > li > a:active, +.skin-green .main-header .navbar .nav > li > a:focus, +.skin-green .main-header .navbar .nav .open > a, +.skin-green .main-header .navbar .nav .open > a:hover, +.skin-green .main-header .navbar .nav .open > a:focus, +.skin-green .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-green .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-green .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-green .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-green .main-header .navbar .sidebar-toggle:hover { + background-color: #008d4c; +} +@media (max-width: 767px) { + .skin-green .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-green .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-green .main-header .navbar .dropdown-menu li a:hover { + background: #008d4c; + } +} +.skin-green .main-header li.user-header { + background-color: #00a65a; +} +.skin-green .content-header { + background: transparent; +} +.skin-green .wrapper, +.skin-green .main-sidebar, +.skin-green .left-side { + background-color: #222d32; +} +.skin-green .user-panel > .info, +.skin-green .user-panel > .info > a { + color: #fff; +} +.skin-green .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-green .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-green .sidebar-menu > li:hover > a, +.skin-green .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #00a65a; +} +.skin-green .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-green .sidebar a { + color: #b8c7ce; +} +.skin-green .sidebar a:hover { + text-decoration: none; +} +.skin-green .treeview-menu > li > a { + color: #8aa4af; +} +.skin-green .treeview-menu > li.active > a, +.skin-green .treeview-menu > li > a:hover { + color: #fff; +} +.skin-green .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-green .sidebar-form input[type="text"], +.skin-green .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-green .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-green .sidebar-form input[type="text"]:focus, +.skin-green .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-green .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-green .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-green.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #00733e; + border-color: #004023; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #004023; + border-color: #004023; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #00733e; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #000d07; + color: #fff; +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +a:link { + color: var(--link); +} +a:visited { + color: var(--visited-link); +} +a { + color: #00a65a; +} +a:hover { + color: #004023; +} +a:visited { + color: #00a65a; +} +.text-primary { + color: #004023; +} +.far fa-life-ring { + color: var(--link); +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #00a65a; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: green dark + * ---------- + */ +.skin-green-dark .main-header .navbar { + background-color: #00a65a; +} +.skin-green-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-green-dark .main-header .navbar .nav > li > a:hover, +.skin-green-dark .main-header .navbar .nav > li > a:active, +.skin-green-dark .main-header .navbar .nav > li > a:focus, +.skin-green-dark .main-header .navbar .nav .open > a, +.skin-green-dark .main-header .navbar .nav .open > a:hover, +.skin-green-dark .main-header .navbar .nav .open > a:focus, +.skin-green-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-green-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-green-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-green-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-green-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #008d4c; +} +@media (max-width: 767px) { + .skin-green-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-green-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-green-dark .main-header .navbar .dropdown-menu li a:hover { + background: #008d4c; + color: #fff; + } +} +.skin-green-dark .main-header li.user-header { + background-color: #00a65a; +} +.skin-green-dark .content-header { + background: transparent; +} +.skin-green-dark .wrapper, +.skin-green-dark .main-sidebar, +.skin-green-dark .left-side { + background-color: #222d32; +} +.skin-green-dark .user-panel > .info, +.skin-green-dark .user-panel > .info > a { + color: #fff; +} +.skin-green-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-green-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-green-dark .sidebar-menu > li:hover > a, +.skin-green-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #00a65a; +} +.skin-green-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-green-dark .sidebar a { + color: #b8c7ce; +} +.skin-green-dark .sidebar a:hover { + text-decoration: none; +} +.skin-green-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-green-dark .treeview-menu > li.active > a, +.skin-green-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-green-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-green-dark .sidebar-form input[type="text"], +.skin-green-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-green-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-green-dark .sidebar-form input[type="text"]:focus, +.skin-green-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-green-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-green-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-green-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #FFF !important; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #00733e; + border-color: #FFF; + color: #FFF; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #004023; + border-color: #004023; + color: #FFF; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #00733e; + color: #FFF; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #000d07; + color: #FFF; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #312F2F; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #006400; + --button-primary: #003100; + --button-hover: #000000; + --header: #006400; + /* Use same as Header picker */ + --text-main: #FFF; + --text-sub: #9b9b9b; + --link: #9ACD32; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #b8dc70; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #d7ebad; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +a.actions { + color: #fff !important; +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.far fa-life-ring { + color: var(--link); +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-green { + color: var(--text-main); +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-green-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-green-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #006300; +} +.fixed-table-body thead th .th-inner, +.skin-green-dark .sidebar-menu > li.active > a, +.skin-green .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); + text-decoration: underline; +} +a:visited { + color: var(--visited-link); +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} +#customFieldsTable a[href*='/fieldsets'] { + background-color: transparent; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: orange dark + * ---------- + */ +.skin-orange-dark .main-header .navbar { + background-color: #FF8C00; +} +.skin-orange-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-orange-dark .main-header .navbar .nav > li > a:hover, +.skin-orange-dark .main-header .navbar .nav > li > a:active, +.skin-orange-dark .main-header .navbar .nav > li > a:focus, +.skin-orange-dark .main-header .navbar .nav .open > a, +.skin-orange-dark .main-header .navbar .nav .open > a:hover, +.skin-orange-dark .main-header .navbar .nav .open > a:focus, +.skin-orange-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-orange-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-orange-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-orange-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-orange-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #e67e00; +} +@media (max-width: 767px) { + .skin-orange-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-orange-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-orange-dark .main-header .navbar .dropdown-menu li a:hover { + background: #e67e00; + color: #fff; + } +} +.skin-orange-dark .main-header li.user-header { + background-color: #FF8C00; +} +.skin-orange-dark .content-header { + background: transparent; +} +.skin-orange-dark .wrapper, +.skin-orange-dark .main-sidebar, +.skin-orange-dark .left-side { + background-color: #222d32; +} +.skin-orange-dark .user-panel > .info, +.skin-orange-dark .user-panel > .info > a { + color: #fff; +} +.skin-orange-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-orange-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-orange-dark .sidebar-menu > li:hover > a, +.skin-orange-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #FF8C00; +} +.skin-orange-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-orange-dark .sidebar a { + color: #b8c7ce; +} +.skin-orange-dark .sidebar a:hover { + text-decoration: none; +} +.skin-orange-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-orange-dark .treeview-menu > li.active > a, +.skin-orange-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-orange-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-orange-dark .sidebar-form input[type="text"], +.skin-orange-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-orange-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-orange-dark .sidebar-form input[type="text"]:focus, +.skin-orange-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-orange-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-orange-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-orange-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff !important; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #cc7000; + border-color: #fff; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #995400; + border-color: #995400; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #cc7000; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #663800; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #2E2C2C; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #b36200; + --button-primary: #804600; + --button-hover: #663800; + --header: #FF8C00; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #FFA500; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #ffc04d; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #ffdb99; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +a.actions { + color: #fff !important; +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.far fa-life-ring { + color: var(--link); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link { + color: var(--link); +} +a:visited { + color: var(--visited-link); +} +a:hover { + color: var(--hover-link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--nav-link); +} +a:link.btn-default { + color: var(--nav-link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--background); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-orange { + color: var(--nav-link) !important; + border-color: var(--button-primary); +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-orange-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-orange-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #ff8c00; +} +.fixed-table-body thead th .th-inner, +.skin-orange-dark .sidebar-menu > li.active > a, +.skin-orange .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} +#customFieldsTable a[href*='/fieldsets'] { + background-color: transparent; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Purple + * ---------- + */ +.skin-purple .main-header .navbar { + background-color: #605ca8; +} +.skin-purple .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-purple .main-header .navbar .nav > li > a:hover, +.skin-purple .main-header .navbar .nav > li > a:active, +.skin-purple .main-header .navbar .nav > li > a:focus, +.skin-purple .main-header .navbar .nav .open > a, +.skin-purple .main-header .navbar .nav .open > a:hover, +.skin-purple .main-header .navbar .nav .open > a:focus, +.skin-purple .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-purple .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-purple .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-purple .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-purple .main-header .navbar .sidebar-toggle:hover { + background-color: #555299; +} +@media (max-width: 767px) { + .skin-purple .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-purple .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-purple .main-header .navbar .dropdown-menu li a:hover { + background: #555299; + } +} +.skin-purple .main-header li.user-header { + background-color: #605ca8; +} +.skin-purple .content-header { + background: transparent; +} +.skin-purple .wrapper, +.skin-purple .main-sidebar, +.skin-purple .left-side { + background-color: #222d32; +} +.skin-purple .user-panel > .info, +.skin-purple .user-panel > .info > a { + color: #fff; +} +.skin-purple .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-purple .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-purple .sidebar-menu > li:hover > a, +.skin-purple .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #605ca8; +} +.skin-purple .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-purple .sidebar a { + color: #b8c7ce; +} +.skin-purple .sidebar a:hover { + text-decoration: none; +} +.skin-purple .treeview-menu > li > a { + color: #8aa4af; +} +.skin-purple .treeview-menu > li.active > a, +.skin-purple .treeview-menu > li > a:hover { + color: #fff; +} +.skin-purple .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-purple .sidebar-form input[type="text"], +.skin-purple .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-purple .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-purple .sidebar-form input[type="text"]:focus, +.skin-purple .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-purple .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-purple .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-purple.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #4c4988; + border-color: #3a3767; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #3a3767; + border-color: #3a3767; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #4c4988; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #272546; + color: #fff; +} +a { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #3a3767; +} +:root { + --button-default: #434078; + --button-primary: #302e56; + --button-hover: #272546; + --header: #605ca8; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #605ca8; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #918ec3; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #c1c0dd; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #605ca8; +} +.far fa-life-ring { + color: var(--link); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: purple dark + * ---------- + */ +.skin-purple-dark .main-header .navbar { + background-color: #605ca8; +} +.skin-purple-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-purple-dark .main-header .navbar .nav > li > a:hover, +.skin-purple-dark .main-header .navbar .nav > li > a:active, +.skin-purple-dark .main-header .navbar .nav > li > a:focus, +.skin-purple-dark .main-header .navbar .nav .open > a, +.skin-purple-dark .main-header .navbar .nav .open > a:hover, +.skin-purple-dark .main-header .navbar .nav .open > a:focus, +.skin-purple-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-purple-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-purple-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-purple-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-purple-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #555299; +} +@media (max-width: 767px) { + .skin-purple-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-purple-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-purple-dark .main-header .navbar .dropdown-menu li a:hover { + background: #555299; + color: #fff; + } +} +.skin-purple-dark .main-header li.user-header { + background-color: #605ca8; +} +.skin-purple-dark .content-header { + background: transparent; +} +.skin-purple-dark .wrapper, +.skin-purple-dark .main-sidebar, +.skin-purple-dark .left-side { + background-color: #222d32; +} +.skin-purple-dark .user-panel > .info, +.skin-purple-dark .user-panel > .info > a { + color: #fff; +} +.skin-purple-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-purple-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-purple-dark .sidebar-menu > li:hover > a, +.skin-purple-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #605ca8; +} +.skin-purple-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-purple-dark .sidebar a { + color: #b8c7ce; +} +.skin-purple-dark .sidebar a:hover { + text-decoration: none; +} +.skin-purple-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-purple-dark .treeview-menu > li.active > a, +.skin-purple-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-purple-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-purple-dark .sidebar-form input[type="text"], +.skin-purple-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-purple-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-purple-dark .sidebar-form input[type="text"]:focus, +.skin-purple-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-purple-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-purple-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-purple-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff !important; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #4c4988; + border-color: #FFF; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #3a3767; + border-color: #3a3767; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #4c4988; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #272546; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #080808; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #434078; + --button-primary: #302e56; + --button-hover: #272546; + --header: #605ca8; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #AC83FF; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #dfd0ff; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #ffffff; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--button-default); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.label-default { + background-color: var(--back-sub); + color: #fff; +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link { + color: var(--link); +} +.far fa-life-ring { + color: var(--link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +a:link.btn-default { + color: var(--nav-link); +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--nav-link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-purple { + color: var(--nav-link) !important; +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--link); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-purple-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-purple-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #5f5ca8; +} +.fixed-table-body thead th .th-inner, +.skin-purple-dark .sidebar-menu > li.active > a, +.skin-purple .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); + text-decoration: underline; +} +a:visited { + color: var(--visited-link); +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--back-sub); + color: var(--visited-link); +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Red + * ---------- + */ +.skin-red .main-header .navbar { + background-color: #dd4b39; +} +.skin-red .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-red .main-header .navbar .nav > li > a:hover, +.skin-red .main-header .navbar .nav > li > a:active, +.skin-red .main-header .navbar .nav > li > a:focus, +.skin-red .main-header .navbar .nav .open > a, +.skin-red .main-header .navbar .nav .open > a:hover, +.skin-red .main-header .navbar .nav .open > a:focus, +.skin-red .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-red .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-red .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-red .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-red .main-header .navbar .sidebar-toggle:hover { + background-color: #d73925; +} +@media (max-width: 767px) { + .skin-red .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-red .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-red .main-header .navbar .dropdown-menu li a:hover { + background: #d73925; + } +} +.skin-red .main-header li.user-header { + background-color: #dd4b39; +} +.skin-red .content-header { + background: transparent; +} +.skin-red .wrapper, +.skin-red .main-sidebar, +.skin-red .left-side { + background-color: #222d32; +} +.skin-red .user-panel > .info, +.skin-red .user-panel > .info > a { + color: #fff; +} +.skin-red .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-red .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-red .sidebar-menu > li:hover > a, +.skin-red .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #dd4b39; +} +.skin-red .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-red .sidebar a { + color: #b8c7ce; +} +.skin-red .sidebar a:hover { + text-decoration: none; +} +.skin-red .treeview-menu > li > a { + color: #8aa4af; +} +.skin-red .treeview-menu > li.active > a, +.skin-red .treeview-menu > li > a:hover { + color: #fff; +} +.skin-red .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-red .sidebar-form input[type="text"], +.skin-red .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-red .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-red .sidebar-form input[type="text"]:focus, +.skin-red .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-red .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-red .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-red.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #c23321; + border-color: #96271a; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #96271a; + border-color: #96271a; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #c23321; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #6b1c12; + color: #fff; +} +a { + color: #dd4b39; +} +a:hover { + color: #96271a; +} +a:visited { + color: #dd4b39; +} +.text-primary { + color: #96271a; +} +:root { + --button-default: #ac2d1e; + --button-primary: #802216; + --button-hover: #6b1c12; + --header: #dd4b39; + /* Use same as Header picker */ + --text-main: #BBB; + --text-alt: #222; + --text-sub: #9b9b9b; + --link: #dd4b39; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #e8867a; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #f3c2bc; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a:link { + color: var(--link); +} +a:visited { + color: var(--visited-link); +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #dd4b39; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.far fa-life-ring { + color: var(--link); +} +/* + * Skin: red dark + * ---------- + */ +.skin-red-dark .main-header .navbar { + background-color: #dd4b39; +} +.skin-red-dark .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-red-dark .main-header .navbar .nav > li > a:hover, +.skin-red-dark .main-header .navbar .nav > li > a:active, +.skin-red-dark .main-header .navbar .nav > li > a:focus, +.skin-red-dark .main-header .navbar .nav .open > a, +.skin-red-dark .main-header .navbar .nav .open > a:hover, +.skin-red-dark .main-header .navbar .nav .open > a:focus, +.skin-red-dark .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-red-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-red-dark .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-red-dark .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-red-dark .main-header .navbar .sidebar-toggle:hover { + background-color: #d73925; +} +@media (max-width: 767px) { + .skin-red-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-red-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-red-dark .main-header .navbar .dropdown-menu li a:hover { + background: #d73925; + color: #fff; + } +} +.skin-red-dark .main-header li.user-header { + background-color: #dd4b39; +} +.skin-red-dark .content-header { + background: transparent; +} +.skin-red-dark .wrapper, +.skin-red-dark .main-sidebar, +.skin-red-dark .left-side { + background-color: #222d32; +} +.skin-red-dark .user-panel > .info, +.skin-red-dark .user-panel > .info > a { + color: #fff; +} +.skin-red-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-red-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-red-dark .sidebar-menu > li:hover > a, +.skin-red-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #dd4b39; +} +.skin-red-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-red-dark .sidebar a { + color: #b8c7ce; +} +.skin-red-dark .sidebar a:hover { + text-decoration: none; +} +.skin-red-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-red-dark .treeview-menu > li.active > a, +.skin-red-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-red-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-red-dark .sidebar-form input[type="text"], +.skin-red-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-red-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-red-dark .sidebar-form input[type="text"]:focus, +.skin-red-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-red-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-red-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-red-dark.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn, +.btn:hover { + color: #fff; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #c23321; + border-color: #FFF; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #96271a; + border-color: #96271a; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #c23321; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #6b1c12; + color: #fff; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:hover { + color: #fff; +} +a:visited { + color: #fff; +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #3d4144; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #ac2d1e; + --button-primary: #802216; + --button-hover: #6b1c12; + --header: #dd4b39; + /* Use same as Header picker */ + --text-main: #BBB; + --text-alt: #222; + --text-sub: #9b9b9b; + --link: #e00000; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #D24D57; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #D24D57; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +.pagination > li > a { + color: var(--light-link); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--visited-link); + border-color: var(--light-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--nav-link); +} +.far fa-life-ring { + color: var(--link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: var(--nav-link) !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--back-sub); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-red { + color: var(--button-primary); +} +.btn-primary:hover { + background-color: var(--button-primary); +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.fixed-table-body thead th .th-inner { + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-red-dark .main-header .navbar .dropdown-menu li a { + color: #FFFFFF; +} +.skin-red-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #c23320; +} +.fixed-table-body thead th .th-inner, +.skin-red-dark .sidebar-menu > li.active > a, +.skin-red .sidebar-menu > li:hover > a, +.sidebar-toggle:hover { + background-color: var(--header) !important; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +#upload-table tbody > tr.warning > td, +#upload-table h3, +#upload-table p { + background-color: #fcf8e3; + color: #000; +} +a { + color: var(--link); +} +a:link { + color: var(--nav-link); +} +a:hover { + color: var(--nav-link); + text-decoration: underline; +} +a:visited { + color: var(--nav-link); +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--back-sub); + color: var(--visited-link); +} +#customFieldsTable a[href*='/fieldsets'] { + background-color: transparent; +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} +/* + * Skin: Orange + * ---------- + */ +.skin-orange .main-header .navbar { + background-color: #FF8C00; +} +.skin-orange .main-header .navbar .nav > li > a { + color: #fff; +} +.skin-orange .main-header .navbar .nav > li > a:hover, +.skin-orange .main-header .navbar .nav > li > a:active, +.skin-orange .main-header .navbar .nav > li > a:focus, +.skin-orange .main-header .navbar .nav .open > a, +.skin-orange .main-header .navbar .nav .open > a:hover, +.skin-orange .main-header .navbar .nav .open > a:focus, +.skin-orange .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-orange .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-orange .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-orange .main-header .navbar .sidebar-toggle { + color: #fff; +} +.skin-orange .main-header .navbar .sidebar-toggle:hover { + background-color: #e67e00; +} +@media (max-width: 767px) { + .skin-orange .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-orange .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-orange .main-header .navbar .dropdown-menu li a:hover { + background: #e67e00; + } +} +.skin-orange .main-header li.user-header { + background-color: #FF8C00; +} +.skin-orange .content-header { + background: transparent; +} +.skin-orange .wrapper, +.skin-orange .main-sidebar, +.skin-orange .left-side { + background-color: #222d32; +} +.skin-orange .user-panel > .info, +.skin-orange .user-panel > .info > a { + color: #fff; +} +.skin-orange .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-orange .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-orange .sidebar-menu > li:hover > a, +.skin-orange .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #FF8C00; +} +.skin-orange .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-orange .sidebar a { + color: #b8c7ce; +} +.skin-orange .sidebar a:hover { + text-decoration: none; +} +.skin-orange .treeview-menu > li > a { + color: #8aa4af; +} +.skin-orange .treeview-menu > li.active > a, +.skin-orange .treeview-menu > li > a:hover { + color: #fff; +} +.skin-orange .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-orange .sidebar-form input[type="text"], +.skin-orange .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-orange .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-orange .sidebar-form input[type="text"]:focus, +.skin-orange .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-orange .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-orange .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-orange.layout-top-nav .main-header > .logo .logo-variant { + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #cc7000; + border-color: #995400; + color: #fff; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #995400; + border-color: #995400; + color: #fff; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #cc7000; + color: #fff; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #663800; + color: #fff; +} +a { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #995400; +} +:root { + --button-default: #b36200; + --button-primary: #804600; + --button-hover: #663800; + --header: #FF8C00; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #FF8C00; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #ffa333; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #ffaf4d; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #FFF; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +.far fa-life-ring { + color: var(--link); +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #FF8C00; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +/* + * Skin: Yellow + * ---------- + */ +.skin-yellow .main-header .navbar { + background-color: #FBCC34; +} +.skin-yellow .main-header .navbar .nav > li > a { + color: rgba(255, 255, 255, 0.8); +} +.skin-yellow .main-header .navbar .nav > li > a:hover, +.skin-yellow .main-header .navbar .nav > li > a:active, +.skin-yellow .main-header .navbar .nav > li > a:focus, +.skin-yellow .main-header .navbar .nav .open > a, +.skin-yellow .main-header .navbar .nav .open > a:hover, +.skin-yellow .main-header .navbar .nav .open > a:focus, +.skin-yellow .main-header .navbar .nav > .active > a { + background: rgba(0, 0, 0, 0.1); + color: #f6f6f6; +} +.skin-yellow .main-header .navbar .sidebar-toggle { + color: rgba(255, 255, 255, 0.8); +} +.skin-yellow .main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: rgba(0, 0, 0, 0.1); +} +.skin-yellow .main-header .navbar .sidebar-toggle { + color: #000; +} +.skin-yellow .main-header .navbar .sidebar-toggle:hover { + background-color: #fbc61b; +} +@media (max-width: 767px) { + .skin-yellow .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + } + .skin-yellow .main-header .navbar .dropdown-menu li a { + color: #333; + } + .skin-yellow .main-header .navbar .dropdown-menu li a:hover { + background: #fbc61b; + } +} +.skin-yellow .main-header li.user-header { + background-color: #FBCC34; +} +.skin-yellow .content-header { + background: transparent; +} +.skin-yellow .wrapper, +.skin-yellow .main-sidebar, +.skin-yellow .left-side { + background-color: #222d32; +} +.skin-yellow .user-panel > .info, +.skin-yellow .user-panel > .info > a { + color: #fff; +} +.skin-yellow .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-yellow .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-yellow .sidebar-menu > li:hover > a, +.skin-yellow .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #FBCC34; +} +.skin-yellow .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-yellow .sidebar a { + color: #b8c7ce; +} +.skin-yellow .sidebar a:hover { + text-decoration: none; +} +.skin-yellow .treeview-menu > li > a { + color: #8aa4af; +} +.skin-yellow .treeview-menu > li.active > a, +.skin-yellow .treeview-menu > li > a:hover { + color: #fff; +} +.skin-yellow .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-yellow .sidebar-form input[type="text"], +.skin-yellow .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-yellow .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-yellow .sidebar-form input[type="text"]:focus, +.skin-yellow .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-yellow .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-yellow .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.skin-yellow.layout-top-nav .main-header > .logo .logo-variant { + color: #413F42; + background-color: none; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: #f7be05; + border-color: #c59704; + color: var(--nav-link); +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: #c59704; + border-color: #c59704; + color: var(--nav-link); +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: #f7be05; + color: var(--nav-link); +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: #937103; + color: var(--nav-link); +} +a.btn-info:link, +a.btn-warning:link, +a.btn-danger:link { + color: #FFF; +} +a.btn-info:visited, +a.btn-warning:visited, +a.btn-danger:visited { + color: #FFF; +} +a { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #c59704; +} +:root { + --button-default: #deab04; + --button-primary: #ac8403; + --button-hover: #937103; + --header: #FBCC34; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #5b585c; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #747176; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #b4b1b5; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #222; + /* Use same as Header picker */ + --light-link: #fff; + /* Use same as Header picker */ +} +.fixed-table-container tbody .selected td { + background-color: #fff8af; +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: #FBCC34; +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.navbar-nav > li > a:visited { + color: var(--nav-link); +} +:hover { + color: var(--nav-link); +} +.skin-yellow .main-header .navbar .nav > li > a { + color: #413F42; +} +.far fa-life-ring { + color: var(--link); +} +/* + * Skin: yellow dark + * ---------- + */ +.skin-yellow-dark .main-header .navbar .sidebar-toggle { + color: #000 !important; +} +.skin-yellow-dark .main-header .navbar .sidebar-toggle:hover { + background-color: var(--link); +} +@media (max-width: 767px) { + .skin-yellow-dark .main-header .navbar .dropdown-menu li.divider { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; + } + .skin-yellow-dark .main-header .navbar .dropdown-menu li a { + color: #fff; + } + .skin-yellow-dark .main-header .navbar .dropdown-menu li a:hover { + background: #e08e0b; + color: #fff; + } +} +.skin-yellow-dark .main-header li.user-header { + background-color: #f39c12; +} +.skin-yellow-dark .content-header { + background: transparent; +} +.skin-yellow-dark .wrapper, +.skin-yellow-dark .main-sidebar, +.skin-yellow-dark .left-side { + background-color: #222d32; +} +.skin-yellow-dark .user-panel > .info, +.skin-yellow-dark .user-panel > .info > a { + color: #fff; +} +.skin-yellow-dark .sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} +.skin-yellow-dark .sidebar-menu > li > a { + border-left: 3px solid transparent; +} +.skin-yellow-dark .sidebar-menu > li:hover > a, +.skin-yellow-dark .sidebar-menu > li.active > a { + color: #fff; + background: #1e282c; + border-left-color: #f39c12; +} +.skin-yellow-dark .sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} +.skin-yellow-dark .sidebar a { + color: #b8c7ce; +} +.skin-yellow-dark .sidebar a:hover { + text-decoration: none; +} +.skin-yellow-dark .treeview-menu > li > a { + color: #8aa4af; +} +.skin-yellow-dark .treeview-menu > li.active > a, +.skin-yellow-dark .treeview-menu > li > a:hover { + color: #fff; +} +.skin-yellow-dark .sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} +.skin-yellow-dark .sidebar-form input[type="text"], +.skin-yellow-dark .sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; + transition: all 0.3s ease-in-out; +} +.skin-yellow-dark .sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} +.skin-yellow-dark .sidebar-form input[type="text"]:focus, +.skin-yellow-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} +.skin-yellow-dark .sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} +.skin-yellow-dark .sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} +.btn.btn-primary, +.btn:hover.btn-primary, +.btn .btn-primary:link, +.btn:hover .btn-primary:link { + background-color: var(--button-default); + border-color: #000000; + color: #545454; +} +.btna.btn-primary:hover, +.btn:hovera.btn-primary:hover { + background-color: var(--button-hover); + border-color: var(--button-hover); + color: #545454; +} +.btn.btn-white:link, +.btn:hover.btn-white:link { + background-color: var(--button-default); + color: #545454; +} +.btn.btn-white:hover, +.btn:hover.btn-white:hover { + background-color: var(--hover-link); + color: #545454; +} +a.actions { + color: #fff !important; +} +a:visited.label-default, +a:link.label-default { + color: #444; +} +/** +The dropdown is white, so use a darker color + */ +li.dropdown-item-marker { + color: var(--button-primary); +} +a:link { + color: var(--link); +} +a:hover { + color: var(--hover-link); +} +a:visited { + color: var(--visited-link); +} +.text-primary { + color: #fff; +} +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} +:root { + --background: #222; + --back-main: #333; + --back-sub: #3d4144; + --back-sub-alt: rgba(0, 0, 0, 0.36); + --button-default: #FFCC32; + --button-primary: #b28500; + --button-hover: #987200; + --header: #FFCC32; + /* Use same as Header picker */ + --text-main: #BBB; + --text-sub: #9b9b9b; + --link: #F0E68C; + /* Use same as Header picker, lighten by 70% */ + --visited-link: #f9f5d0; + /* Use same as Header picker, lighten by 70% */ + --hover-link: #ffffff; + /* Use same as Header picker, lighten by 70% */ + --nav-link: #222; + /* Use same as Header picker */ + --light-link: #545454; + /* Use same as Header picker */ +} +#ldapad_test_results.well.well-sm { + color: var(--back-main); +} +.pagination > li > a { + color: var(--text-main); + background-color: var(--back-main); +} +.pagination > .active > a { + background-color: var(--button-default); + border-color: var(--light-link); + color: var(--nav-link); +} +.pagination > .active > a:hover { + background-color: var(--hover-link); +} +.tasks-menu > .dropdown-menu > li .menu { + background-color: var(--back-main); +} +.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress { + background-color: var(--background); +} +h2.task_menu { + color: var(--link); +} +.navbar-custom-menu > .navbar-nav > li > .dropdown-menu { + background-color: var(--back-main); + color: var(--link); +} +.main-header .navbar, +.main-header .logo { + background-color: var(--header); + background: linear-gradient(to bottom, var(--header) 0%, var(--header) 100%); + border-color: var(--header); +} +a.btn.btn-default { + color: var(--nav-link); +} +.bootstrap-table .fixed-table-container .table thead th .sortable { + color: var(--text-main); +} +.bootstrap-table .fixed-table-toolbar .columns label { + color: #000; +} +.thead, +.navbar-nav > li > a:link { + color: var(--nav-link); +} +.navbar-nav > li > a:visited { + color: var(--nav-link); +} +.modal-content { + background-color: var(--back-main); + color: var(--text-main); +} +a:link.btn-default { + color: var(--nav-link); +} +.btn-primary.hover { + color: var(--nav-link); +} +.small-box h3, +.small-box p { + color: #fff !important; +} +.small-box h3 a:hover, +.small-box p a:hover { + text-decoration: none; + color: var(--nav-link) !important; +} +.far fa-life-ring { + color: var(--link); +} +#accessoriesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#assetsListingTable > tbody > tr.selected > td { + background-color: var(--back-main); +} +body { + color: var(--text-main); +} +.box, +.box.box-default { + border-top: 3px solid var(--header); + border-top-color: var(--header); +} +.box-body, +.box-footer, +.box-header { + background-color: var(--back-sub); + color: var(--header); +} +.btn-default { + background-color: var(--button-default); +} +.btn-default dropdown-toggle { + background-color: var(--button-default); + color: var(--nav-link); +} +.btn-default:active, +.btn-default:focus, +.btn-default:hover, +.btn-default.active { + background-color: var(--button-hover); + color: var(--nav-link); +} +.btn-primary, +.btn-primary.hover, +.btn-primary:active, +.text-yellow { + color: var(--nav-link) !important; +} +.btn-primary:hover { + background-color: var(--button-primary); + color: var(--nav-link) !important; +} +#componentsTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +#consumablesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.content-wrapper { + background-color: var(--background); +} +#create-form > div > div > div > span > i.fa { + background-color: var(--back-sub); + color: var(--header); +} +.datepicker .datepicker-switch:hover, +.datepicker .next:hover, +.datepicker .prev:hover, +.datepicker tfoot tr th:hover, +.datepicker table tr td.day.focused, +.datepicker table tr td.day:hover { + background-color: var(--header); +} +.datepicker.dropdown-menu { + background-color: var(--back-main); + color: var(--text-main); +} +#details > div > div > div > table { + background-color: transparent; +} +.dropdown-menu > li > a:hover { + background-color: var(--back-main); + color: var(--nav-link); +} +.form-control { + background-color: var(--back-main); + color: var(--text-main); +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: var(--back-main); + color: var(--text-main); +} +h1, +h2, +h3, +h4, +h5, +h6, +p { + color: var(--text-main); +} +.help-block { + color: var(--text-sub); +} +input[type=text], +input[type=search] { + background-color: var(--back-sub); + color: var(--text-main); +} +#licensesTable > tbody > tr > td > nobr > a > i.fa { + color: var(--text-main); +} +.main-footer { + background-color: var(--back-main); + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li > a, +.nav-tabs-custom > .nav-tabs > li.active > a { + color: var(--text-main); +} +.nav-tabs-custom > .nav-tabs > li.active { + border-top-color: var(--header); +} +.nav-tabs-custom > .nav-tabs > li.active > a { + background-color: var(--back-main); +} +.nav-tabs-custom > .nav-tabs > li.active > a:hover { + background-color: var(--back-sub); + color: var(--text-main); +} +.nav-tabs-custom, +.nav-tabs-custom > .tab-content { + background-color: var(--back-main); +} +.navbar-nav > .tasks-menu > .dropdown-menu > li.header { + background-color: var(--back-main); + color: var(--header); +} +.open > .dropdown-toggle.btn-default { + background-color: var(--back-sub); + color: var(--header); +} +.panel { + background-color: var(--back-sub); + color: var(--text-main); +} +.panel-default > .panel-heading { + background-color: var(--back-main); + color: var(--header); +} +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option[aria-selected=true]:hover { + background-color: var(--back-sub); + color: var(--header); +} +.select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: var(--header); + color: var(--back-main); +} +.select2-container--default .select2-selection--single .select2-selection__rendered { + color: var(--text-main); +} +.select2-container--default .select2-selection--multiple .select2-selection__choice { + background-color: var(--header); +} +.select2-selection--single { + background-color: var(--back-sub) !important; + color: var(--text-main) !important; +} +.select2-dropdown { + background-color: var(--back-main); + color: var(--text-main); +} +.skin-yellow-dark .main-header .navbar .dropdown-menu li a { + color: var(--header); +} +.skin-yellow-dark .main-header .navbar .dropdown-menu li a:hover { + background-color: #000000; +} +tr th div.th-inner { + color: #FFFFFF; +} +.tab-content, +.tab-pane { + background-color: var(--back-main); + color: var(--text-main); +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--back-sub); +} +.table-striped > tbody > tr:nth-of-type(even) { + background-color: var(--back-sub-alt); +} +.th-inner { + color: var(--nav-link); +} +#webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, +.box-body, +.box-footer, +.box-header { + color: var(--text-main); +} +.box-header.with-border { + border-bottom: #000; +} +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} +.row-striped { + vertical-align: top; + line-height: 2.6; + padding: 0px; + margin-left: 20px; + box-sizing: border-box; + display: table; +} +.row-striped .row:nth-of-type(odd) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-striped .row:nth-of-type(even) div { + background-color: var(--back-sub); + color: var(--text-main); + border-top: 1px solid #dddddd; + display: table-cell; +} +.row-new-striped > .row:nth-of-type(odd) { + background-color: var(--back-sub); + color: var(--text-main); +} +.row-new-striped > .row:nth-of-type(even) { + background-color: var(--back-sub-alt); + color: var(--text-main); +} +.search-highlight, +.search-highlight:hover { + background-color: #e9d15b; +} +.table > tbody > tr.warning > td { + background-color: var(--back-sub); +} + diff --git a/public/css/dist/skins/skin-black-dark.css b/public/css/dist/skins/skin-black-dark.css index 89528e8ac4..15c1fd488b 100644 --- a/public/css/dist/skins/skin-black-dark.css +++ b/public/css/dist/skins/skin-black-dark.css @@ -175,6 +175,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -392,11 +396,6 @@ input[type=search] { .search-highlight:hover { background-color: var(--back-sub) !important; } -.input-group, -.input-group-addon { - background-color: var(--back-sub); - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } diff --git a/public/css/dist/skins/skin-black-dark.min.css b/public/css/dist/skins/skin-black-dark.min.css index 89528e8ac4..15c1fd488b 100644 --- a/public/css/dist/skins/skin-black-dark.min.css +++ b/public/css/dist/skins/skin-black-dark.min.css @@ -175,6 +175,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -392,11 +396,6 @@ input[type=search] { .search-highlight:hover { background-color: var(--back-sub) !important; } -.input-group, -.input-group-addon { - background-color: var(--back-sub); - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } diff --git a/public/css/dist/skins/skin-blue-dark.css b/public/css/dist/skins/skin-blue-dark.css index bfac907779..97a05cf51c 100644 --- a/public/css/dist/skins/skin-blue-dark.css +++ b/public/css/dist/skins/skin-blue-dark.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -373,11 +377,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -463,7 +462,7 @@ input[type=search] { background-color: var(--back-sub); } .table-striped > tbody > tr:nth-of-type(even) { - background-color: var(--back-sub-alt); + background-color: var(--back-main); } #webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, .box-body, @@ -488,7 +487,16 @@ a:visited { color: var(--visited-link); } #customFieldsTable a[href*='/models'] { - color: var(--back-sub); + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); } #customFieldsTable a[href*='/fieldsets'] { background-color: transparent; diff --git a/public/css/dist/skins/skin-blue-dark.css.map b/public/css/dist/skins/skin-blue-dark.css.map new file mode 100644 index 0000000000..96583b65b1 --- /dev/null +++ b/public/css/dist/skins/skin-blue-dark.css.map @@ -0,0 +1 @@ +{"version":3,"file":"css/dist/skins/skin-blue-dark.css","mappings":"AAAA;;;EAGE;AAMF;ECHE;ADAF;AAGA;ECAI;ADAJ;AAAA;;;;;;;ECUI;EACA;ADDJ;AAVA;ECgBI;ADHJ;ACII;EACE;EACA;ADFN;AAjBA;EAMQ;AAcR;AAbQ;EACE;AAeV;AAZM;EAGM;IACE;IACA;EAYZ;EAjBI;IAQQ;EAYZ;EAXY;IACE;IACA;EAad;AACF;AApCA;EAgCM;AAON;AAvCA;EAsCI;AAIJ;AA1CA;;;ECsGI;ADvDJ;AA/CA;;EC2GM;ADxDN;AC8DI;EACE;EACA;AD5DN;AAvDA;ECuHM;AD7DN;ACgEI;;EACE;EACA;EACA;AD7DN;AAhEA;ECiIM;EACA;AD9DN;AApEA;ECuII;ADhEJ;ACiEI;EACE;AD/DN;AA1EA;ECgJQ;ADnER;ACqEM;;EACE;ADlER;AAjFA;ECgEE;EA0FE;EACA;ADrEJ;AAtFA;;EC6JM;EACA;EACA;EACA;ECuBI;AFvFV;AAhGA;ECoKM;EAhGJ;EACA;EACA;EACA;ADgCF;AC+DM;;EACE;EACA;AD5DR;AC8DM;EACE;AD5DR;AA/GA;EC+KM;EA3GJ;EACA;EACA;EACA;AD+CF;AAzEA;EAEI;AA0EJ;AApEA;;EACE;AAuEF;AArEE;;;;EACE;EACA;EACA;AA0EJ;AAtEE;;EACE;EACA;EACA;AAyEJ;AAtEE;;EACE;EACA;AAyEJ;AAtEE;;EACE;EACA;AAyEJ;AACA;;EAEE;AArEF;EACE;AAuEF;AAlEE;EACE;AAoEJ;AAlEE;EACE;AAoEJ;AAhEA;EACE;AAkEF;AA5DA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EA8DA,8BAA8B;EA7D9B;EACA;EACA;EA+DA,8CAA8C;EA9D9C;EAgEA,8CAA8C;EA/D9C;EAiEA,8CAA8C;EAhE9C;EAkEA,8BAA8B;EAjE9B;EAmEA,8BAA8B;AAChC;AAjEA;EACE;AAmEF;AAjEA;EACE;AAmEF;AAhEA;EACE;EACA;AAkEF;AAhEA;EACE;EACA;AAkEF;AAhEA;EACE;AAkEF;AAhEA;EACE;AAkEF;AA/DA;EACE;AAiEF;AA/DA;EACE;AAiEF;AA/DA;EACE;EACA;AAiEF;AA9DA;;EACE;EAEA;EACA;AAiEF;AA9DA;EACE;AAgEF;AA9DA;EACE;AAgEF;AA9DA;;EACE;AAiEF;AA/DA;EACE;AAiEF;AA/DA;EACE;EACA;AAiEF;AA9DA;EACE;AAgEF;AA7DA;EACE;AA+DF;AA7DA;EACE;AA+DF;AA7DA;;EACE;AAgEF;AAjEA;;EAGI;EACA;AAkEJ;AA7DA;EACE;AA+DF;AA7DA;EACE;AA+DF;AA5DA;EACE;AA8DF;AA5DA;EACE;AA8DF;AA5DA;;EACE;EACA;AA+DF;AA7DA;;;EACE;EACA;AAiEF;AA/DA;EACE;EACA;AAiEF;AA9DA;EACE;EACA;AAgEF;AA7DA;;;;EACE;EACA;AAkEF;AAhEA;;;;EACE;AAqEF;AAnEA;EACE;AAqEF;AAnEA;EACE;AAqEF;AAnEA;EACE;AAqEF;AAnEA;EACE;AAqEF;AAnEA;EACE;EACA;AAqEF;AAnEA;;;;;;EACE;AA0EF;AAxEA;EACE;EACA;AA0EF;AAxEA;EACE;AA0EF;AAxEA;EACE;EACA;AA0EF;AAxEA;EACE;AA0EF;AAxEA;EACE;EACA;AA0EF;AAxEA;;;EACE;EACA;AA4EF;AA1EA;;;;;;;EACE;AAkFF;AAhFA;EACE;AAkFF;AAhFA;;EACE;EACA;AAmFF;AAjFA;;EACE;EACA;AAoFF;AAlFA;EACE;AAoFF;AAlFA;EACE;EACA;AAoFF;AAlFA;;EACE;AAqFF;AAnFA;EACE;AAqFF;AAnFA;EACE;AAqFF;AAnFA;EACE;EACA;AAqFF;AAnFA;;EACE;AAsFF;AApFA;EACE;EACA;AAsFF;AApFA;EACE;EACA;AAsFF;AApFA;EACE;EACA;AAsFF;AApFA;EACE;EACA;AAsFF;AApFA;;EACE;EACA;AAuFF;AArFA;EACE;EACA;AAuFF;AArFA;EACE;AAuFF;AArFA;EACE;AAuFF;AArFA;EACE;EACA;AAuFF;AArFA;EACE;EACA;AAuFF;AArFA;EACE;AAuFF;AArFA;EACE;AAuFF;AArFA;;;;EACE;AA0FF;AAxFA;;EACE;EACA;AA2FF;AAzFA;EACE;AA2FF;AAzFA;EACE;AA2FF;AAzFA;;;;EACE;AA8FF;AA3FA;EACE;AA6FF;AA1FA;EACE;AA4FF;AA1FE;EACE;AA4FJ;AA3FE;EACE;EACA;AA6FJ;AA3FE;EACE;AA6FJ;AA1FA;EACE;AA4FF;AA1FA;EACE;AA4FF;AAzFA;EACE;EACA;EACA;EACA;EACA;EAGA;AAyFF;AArFA;EACE;EACA;EACA;EACA;AAuFF;AAnFA;EACE;EACA;EACA;EACA;AAqFF;AAnFA;EACE;EACA;AAqFF;AAnFA;EACE;EACA;AAqFF;AAlFA;;EACE;AAqFF;AAnFA;EACE;AAqFF","sources":["webpack:///./resources/assets/less/skins/skin-blue-dark.less","webpack:///./resources/assets/less/mixins.less","webpack:///./resources/assets/less/bootstrap-less/mixins/vendor-prefixes.less"],"sourcesContent":["/*\n * Skin: blue dark\n * ----------\n */\n@import \"../bootstrap-less/mixins.less\";\n@import \"../bootstrap-less/variables.less\";\n@import \"../variables.less\";\n@import \"../mixins.less\";\n\n.skin-blue-dark {\n //Navbar\n .main-header {\n .navbar {\n .navbar-variant(@blue; #fff);\n .sidebar-toggle {\n color: #fff;\n &:hover {\n background-color: darken(@blue, 5%);\n }\n }\n @media (max-width: @screen-header-collapse) {\n .dropdown-menu {\n li {\n &.divider {\n background-color: rgba(255, 255, 255, 0.1);\n color: #fff;\n }\n a {\n color: #fff;\n &:hover {\n background: darken(@blue, 5%);\n color: #fff;\n }\n }\n }\n }\n }\n }\n //Logo\n\n li.user-header {\n background-color: @blue;\n }\n }\n\n //Content Header\n .content-header {\n background: transparent;\n }\n\n //Create the sidebar skin\n .skin-dark-sidebar(@blue);\n}\n\n.skin-blue-dark.layout-top-nav .main-header > .logo {\n .logo-variant {\n background-color: none;\n }\n}\n\n\n\n.btn, .btn:hover {\n color: #fff !important;\n\n &.btn-primary, .btn-primary:link {\n background-color: darken(@blue, 10%);\n border-color: #FFF;\n color: #FFF;\n }\n\n\n &a.btn-primary:hover {\n background-color: darken(@blue, 20%);\n border-color: darken(@blue, 20%);\n color: #fff;\n }\n\n &.btn-white:link {\n background-color: darken(@blue, 10%);\n color: #fff;\n }\n\n &.btn-white:hover {\n background-color: darken(@blue, 30%);\n color: #fff;\n }\n}\n\n/**\nThe dropdown is white, so use a darker color\n */\nli.dropdown-item-marker {\n color: var(--button-primary);\n}\n\n\na {\n &:hover {\n color: var(--hover-link);\n }\n &:visited {\n color: var(--visited-link);\n }\n}\n\n.text-primary {\n color: #fff;\n}\n\n\n\n\n:root {\n --background: #222;\n --back-main: #333;\n --back-sub: #272727;\n --back-sub-alt: rgba(0, 0, 0, 0.36);\n --button-default: darken(@blue, 15%);\n --button-primary: darken(@blue, 25%);\n --button-hover: darken(@blue, 30%);\n --header: @blue; /* Use same as Header picker */\n --text-main: #BBB;\n --text-sub: #9b9b9b;\n --link: #00BFFF; /* Use same as Header picker, lighten by 70% */\n --visited-link: lighten(#00BFFF,15%); /* Use same as Header picker, lighten by 70% */\n --hover-link: lighten(#00BFFF,30%); /* Use same as Header picker, lighten by 70% */\n --nav-link: #FFF; /* Use same as Header picker */\n --light-link: #fff; /* Use same as Header picker */\n}\n\n#ldapad_test_results.well.well-sm{\n color: var(--back-main);\n}\na.actions {\n color:#fff !important;\n}\n//pagination\n.pagination > li >a{\n color: var(--light-link);\n background-color: var(--back-main);\n}\n.pagination > .active > a {\n background-color: var(--visited-link);\n border-color: var(--light-link);\n}\n.pagination > .active > a:hover{\n background-color: var(--hover-link);\n}\n.tasks-menu > .dropdown-menu > li .menu{\n background-color: var(--back-main);\n}\n\n.tasks-menu > .dropdown-menu > li .menu > li > a:hover .progress{\n background-color: var(--background);\n}\nh2.task_menu{\n color:var(--link);\n}\n.navbar-custom-menu > .navbar-nav > li > .dropdown-menu{\n background-color:var(--back-main);\n color:var(--link);\n}\n\n.main-header .navbar, .main-header .logo {\n background-color: var(--header);\n background: -webkit-linear-gradient(top, var(--header) 0%,var(--header) 100%);\n background: linear-gradient(to bottom, var(--header) 0%,var(--header) 100%);\n border-color: var(--header);\n}\n\n.bootstrap-table .fixed-table-container .table thead th .sortable {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n.thead, .navbar-nav>li>a:link {\n color: var(--nav-link);\n}\n.far fa-life-ring{\n color:var(--link);\n}\n.modal-content {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n\na:link {\n color: var(--link);\n}\n\n.btn-primary.hover {\n color: var(--nav-link);\n}\n.bootstrap-table .fixed-table-toolbar .columns label {\n color:#000;\n}\n.small-box h3, .small-box p {\n color: var(--nav-link) !important;\n a:hover {\n text-decoration: none;\n color: var(--nav-link) !important;\n }\n}\n\n\n#accessoriesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#assetsListingTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n\n#assetsListingTable>tbody>tr.selected>td {\n background-color: var(--back-main);\n}\nbody {\n color: var(--text-main);\n}\n.box, .box.box-default {\n border-top: 3px solid var(--header);\n border-top-color: var(--header);\n}\n.box-body, .box-footer, .box-header {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.btn-default{\n background-color: var(--button-default);\n color: var(--link);\n}\n\n.btn-default dropdown-toggle {\n background-color: var(--button-default);\n color: var(--link);\n}\n\n.btn-default:active, .btn-default:focus, .btn-default:hover, .btn-default.active {\n background-color: var(--button-hover);\n color: var(--link);\n}\n.btn-primary, .btn-primary.hover, .btn-primary:active, .text-blue {\n color: var(--text-main);\n}\n.btn-primary:hover {\n background-color: var(--button-primary);\n}\n#componentsTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n#consumablesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.content-wrapper {\n background-color: var(--background);\n}\n#create-form>div>div>div>span>i.fa {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.datepicker .datepicker-switch:hover, .datepicker .next:hover, .datepicker .prev:hover, .datepicker tfoot tr th:hover, .datepicker table tr td.day.focused, .datepicker table tr td.day:hover {\n background-color: var(--header);\n}\n.datepicker.dropdown-menu {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n#details>div>div>div>table {\n background-color: transparent;\n}\n.dropdown-menu>li>a:hover {\n background-color: var(--back-main);\n color: var(--nav-link);\n}\n.fixed-table-body thead th .th-inner{\n color: var(--nav-link);\n}\n.form-control {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control{\n background-color: var(--back-main);\n color: var(--text-main);\n}\nh1, h2, h3, h4, h5, h6, p {\n color: var(--text-main);\n}\n.help-block {\n color: var(--text-sub);\n}\ninput[type=text], input[type=search] {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.input-group, .input-group-addon {\n background-color: var(--back-sub)!important;\n color: var(--text-main);\n}\n#licensesTable>tbody>tr>td>nobr>a>i.fa {\n color: var(--text-main);\n}\n.main-footer {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li>a, .nav-tabs-custom>.nav-tabs>li.active>a {\n color: var(--text-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active {\n border-top-color: var(--header);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a {\n background-color: var(--back-main);\n}\n.nav-tabs-custom>.nav-tabs>li.active>a:hover {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.nav-tabs-custom, .nav-tabs-custom>.tab-content {\n background-color: var(--back-main);\n}\n.navbar-nav>.tasks-menu>.dropdown-menu>li.header {\n background-color: var(--back-main);\n color: var(--header);\n}\n.open>.dropdown-toggle.btn-default {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.panel {\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.panel-default>.panel-heading {\n background-color: var(--back-main);\n color: var(--header);\n}\n.select2-container--default .select2-results__option[aria-selected=true], .select2-container--default .select2-results__option[aria-selected=true]:hover {\n background-color: var(--back-sub);\n color: var(--header);\n}\n.select2-container--default .select2-results__option--highlighted[aria-selected] {\n background-color: var(--header);\n color: var(--back-main);\n}\n.select2-container--default .select2-selection--single .select2-selection__rendered {\n color: var(--text-main);\n}\n.select2-container--default .select2-selection--multiple .select2-selection__choice {\n background-color: var(--header);\n}\n.select2-selection--single {\n background-color: var(--back-sub)!important;\n color: var(--text-main)!important;\n}\n.select2-dropdown {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.skin-blue-dark .main-header .navbar .dropdown-menu li a {\n color: #FFFFFF;\n}\n.skin-blue-dark .main-header .navbar .dropdown-menu li a:hover {\n background-color: #3c8dbc;\n}\n.fixed-table-body thead th .th-inner, .skin-blue-dark .sidebar-menu>li.active>a, .skin-blue .sidebar-menu>li:hover>a, .sidebar-toggle:hover {\n background-color: var(--header)!important;\n}\n.tab-content, .tab-pane {\n background-color: var(--back-main);\n color: var(--text-main);\n}\n.table-striped>tbody>tr:nth-of-type(odd) {\n background-color: var(--back-sub);\n}\n.table-striped>tbody>tr:nth-of-type(even){\n background-color: var(--back-sub-alt);\n}\n#webui>div>div>div>div>div>table>tbody>tr>td>a>i.fa, .box-body, .box-footer, .box-header {\n color: var(--text-main);\n}\n\n.box-header.with-border {\n border-bottom: #000;\n}\n\na {\n color: var(--link);\n\n &:link {\n color: var(--link) }\n &:hover {\n color: var(--hover-link);\n text-decoration: underline;\n }\n &:visited {\n color: var(--visited-link)\n }\n}\n#customFieldsTable a[href*='/models'] {\n color: var(--back-sub);\n}\n#customFieldsTable a[href*='/fieldsets']{\n background-color: transparent;\n}\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background-color: var(--back-sub);\n color: var(--text-main);\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n.row-new-striped > .row:nth-of-type(odd){\n background-color: var(--back-sub);\n color: var(--text-main);\n}\n.row-new-striped > .row:nth-of-type(even){\n background-color: var(--back-sub-alt);\n color: var(--text-main);\n}\n\n.search-highlight, .search-highlight:hover {\n background-color: #e9d15b;\n}\n.table > tbody > tr.warning > td{\n background-color:var(--back-sub);\n}","//AdminLTE mixins\n//===============\n\n\n//Changes the color and the hovering properties of the navbar\n.navbar-variant(@color; @font-color: rgba(255, 255, 255, 0.8); @hover-color: #f6f6f6; @hover-bg: rgba(0, 0, 0, 0.1)) {\n background-color: @color;\n //Navbar links\n .nav > li > a {\n color: @font-color;\n }\n\n .nav > li > a:hover,\n .nav > li > a:active,\n .nav > li > a:focus,\n .nav .open > a,\n .nav .open > a:hover,\n .nav .open > a:focus,\n .nav > .active > a {\n background: @hover-bg;\n color: @hover-color;\n }\n\n //Add color to the sidebar toggle button\n .sidebar-toggle {\n color: @font-color;\n &:hover {\n color: @hover-color;\n background: @hover-bg;\n }\n }\n}\n\n//Logo color variation\n.logo-variant(@bg-color; @color: #fff; @border-bottom-color: transparent; @border-bottom-width: 0) {\n background-color: @bg-color;\n color: @color;\n border-bottom: @border-bottom-width solid @border-bottom-color;\n\n &:hover {\n background-color: darken(@bg-color, 1%);\n }\n}\n\n//Box solid color variantion creator\n.box-solid-variant(@color; @text-color: #fff) {\n border: 1px solid @color;\n > .box-header {\n color: @text-color;\n background: @color;\n background-color: @color;\n a,\n .btn {\n color: @text-color;\n }\n }\n}\n\n//Direct Chat Variant\n.direct-chat-variant(@bg-color; @color: #fff) {\n .right > .direct-chat-text {\n background: @bg-color;\n border-color: @bg-color;\n color: @color;\n &:after,\n &:before {\n border-left-color: @bg-color;\n }\n }\n}\n\n//border radius creator\n.border-radius(@radius) {\n border-radius: @radius;\n}\n//Different radius each side\n.border-radius(@top-left; @top-right; @bottom-left; @bottom-right) {\n border-top-left-radius: @top-left;\n border-top-right-radius: @top-right;\n border-bottom-right-radius: @bottom-right;\n border-bottom-left-radius: @bottom-left;\n}\n\n//Gradient background\n.gradient(@color: #F5F5F5, @start: #EEE, @stop: #FFF) {\n background: @color;\n background: -webkit-gradient(linear,\n left bottom,\n left top,\n color-stop(0, @start),\n color-stop(1, @stop));\n background: -ms-linear-gradient(bottom,\n @start,\n @stop);\n background: -moz-linear-gradient(center bottom,\n @start 0%,\n @stop 100%);\n background: -o-linear-gradient(@stop,\n @start);\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",@stop,@start));\n}\n\n//Added 2.1.0\n//Skins Mixins\n\n//Dark Sidebar Mixin\n.skin-dark-sidebar(@link-hover-border-color) {\n // Sidebar background color (Both .wrapper and .left-side are responsible for sidebar bg color)\n .wrapper,\n .main-sidebar,\n .left-side {\n background-color: @sidebar-dark-bg;\n }\n //User Panel (resides in the sidebar)\n .user-panel {\n > .info, > .info > a {\n color: #fff;\n }\n }\n //Sidebar Menu. First level links\n .sidebar-menu > li {\n //Section Headning\n &.header {\n color: lighten(@sidebar-dark-bg, 20%);\n background: darken(@sidebar-dark-bg, 4%);\n }\n //links\n > a {\n border-left: 3px solid transparent;\n }\n //Hover and active states\n &:hover > a, &.active > a {\n color: @sidebar-dark-hover-color;\n background: @sidebar-dark-hover-bg;\n border-left-color: @link-hover-border-color;\n }\n //First Level Submenu\n > .treeview-menu {\n margin: 0 1px;\n background: @sidebar-dark-submenu-bg;\n }\n }\n //All links within the sidebar menu\n .sidebar a {\n color: @sidebar-dark-color;\n &:hover {\n text-decoration: none;\n }\n }\n //All submenus\n .treeview-menu {\n > li {\n > a {\n color: @sidebar-dark-submenu-color;\n }\n &.active > a, > a:hover {\n color: @sidebar-dark-submenu-hover-color;\n }\n }\n }\n //The sidebar search form\n .sidebar-form {\n .border-radius(3px);\n border: 1px solid lighten(@sidebar-dark-bg, 10%);\n margin: 10px 10px;\n input[type=\"text\"], .btn {\n box-shadow: none;\n background-color: lighten(@sidebar-dark-bg, 10%);\n border: 1px solid transparent;\n height: 35px;\n .transition(all @transition-speed @transition-fn);\n }\n input[type=\"text\"] {\n color: #666;\n .border-radius(2px, 0, 2px, 0);\n &:focus, &:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n }\n &:focus + .input-group-btn .btn {\n border-left-color: #fff;\n }\n }\n .btn {\n color: #999;\n .border-radius(0, 2px, 0, 2px);\n }\n }\n}\n\n//Light Sidebar Mixin\n.skin-light-sidebar(@icon-active-color) {\n // Sidebar background color (Both .wrapper and .left-side are responsible for sidebar bg color)\n .wrapper,\n .main-sidebar,\n .left-side {\n background-color: @sidebar-light-bg;\n }\n .content-wrapper,\n .main-footer {\n border-left: 1px solid @gray;\n }\n //User Panel (resides in the sidebar)\n .user-panel {\n > .info, > .info > a {\n color: @sidebar-light-color;\n }\n }\n //Sidebar Menu. First level links\n .sidebar-menu > li {\n .transition(border-left-color .3s ease);\n //border-left: 3px solid transparent;\n //Section Headning\n &.header {\n color: lighten(@sidebar-light-color, 25%);\n background: @sidebar-light-bg;\n }\n //links\n > a {\n border-left: 3px solid transparent;\n font-weight: 600;\n }\n //Hover and active states\n &:hover > a,\n &.active > a {\n color: @sidebar-light-hover-color;\n background: @sidebar-light-hover-bg;\n }\n &:hover > a {\n\n }\n &.active {\n border-left-color: @icon-active-color;\n > a {\n font-weight: 600;\n }\n }\n //First Level Submenu\n > .treeview-menu {\n background: @sidebar-light-submenu-bg;\n }\n }\n //All links within the sidebar menu\n .sidebar a {\n color: @sidebar-light-color;\n &:hover {\n text-decoration: none;\n }\n }\n //All submenus\n .treeview-menu {\n > li {\n > a {\n color: @sidebar-light-submenu-color;\n }\n &.active > a,\n > a:hover {\n color: @sidebar-light-submenu-hover-color;\n }\n &.active > a {\n font-weight: 600;\n }\n }\n }\n //The sidebar search form\n .sidebar-form {\n .border-radius(3px);\n border: 1px solid @gray;//darken(@sidebar-light-bg, 5%);\n margin: 10px 10px;\n input[type=\"text\"],\n .btn {\n box-shadow: none;\n background-color: #fff;//darken(@sidebar-light-bg, 3%);\n border: 1px solid transparent;\n height: 35px;\n .transition(all @transition-speed @transition-fn);\n }\n input[type=\"text\"] {\n color: #666;\n .border-radius(2px, 0, 2px, 0);\n &:focus,\n &:focus + .input-group-btn .btn {\n background-color: #fff;\n color: #666;\n }\n &:focus + .input-group-btn .btn {\n border-left-color: #fff;\n }\n }\n .btn {\n color: #999;\n .border-radius(0, 2px, 0, 2px);\n }\n }\n @media(min-width: @screen-sm-min) {\n &.sidebar-mini.sidebar-collapse {\n .sidebar-menu > li > .treeview-menu {\n border-left: 1px solid @gray;\n }\n }\n }\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/public/css/dist/skins/skin-blue-dark.min.css b/public/css/dist/skins/skin-blue-dark.min.css index bfac907779..97a05cf51c 100644 --- a/public/css/dist/skins/skin-blue-dark.min.css +++ b/public/css/dist/skins/skin-blue-dark.min.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -373,11 +377,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -463,7 +462,7 @@ input[type=search] { background-color: var(--back-sub); } .table-striped > tbody > tr:nth-of-type(even) { - background-color: var(--back-sub-alt); + background-color: var(--back-main); } #webui > div > div > div > div > div > table > tbody > tr > td > a > i.fa, .box-body, @@ -488,7 +487,16 @@ a:visited { color: var(--visited-link); } #customFieldsTable a[href*='/models'] { - color: var(--back-sub); + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); } #customFieldsTable a[href*='/fieldsets'] { background-color: transparent; diff --git a/public/css/dist/skins/skin-blue.css b/public/css/dist/skins/skin-blue.css index cac9000174..216768cdf1 100644 --- a/public/css/dist/skins/skin-blue.css +++ b/public/css/dist/skins/skin-blue.css @@ -236,4 +236,16 @@ a.settings_button:visited, a.settings_button:hover { color: #3c8dbc; } +a.label.label-default:link { + color: #307095; + /* Use same as Header picker, lighten by 70% */ +} +a.label.label-default:visited { + color: #23536f; + /* Use same as Header picker, lighten by 70% */ +} +a.label.label-default:hover { + background-color: #BBB; + color: #296282; +} diff --git a/public/css/dist/skins/skin-blue.min.css b/public/css/dist/skins/skin-blue.min.css index cac9000174..216768cdf1 100644 --- a/public/css/dist/skins/skin-blue.min.css +++ b/public/css/dist/skins/skin-blue.min.css @@ -236,4 +236,16 @@ a.settings_button:visited, a.settings_button:hover { color: #3c8dbc; } +a.label.label-default:link { + color: #307095; + /* Use same as Header picker, lighten by 70% */ +} +a.label.label-default:visited { + color: #23536f; + /* Use same as Header picker, lighten by 70% */ +} +a.label.label-default:hover { + background-color: #BBB; + color: #296282; +} diff --git a/public/css/dist/skins/skin-green-dark.css b/public/css/dist/skins/skin-green-dark.css index 275fa58ade..fed4dd79a5 100644 --- a/public/css/dist/skins/skin-green-dark.css +++ b/public/css/dist/skins/skin-green-dark.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -364,11 +368,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -475,7 +474,16 @@ a:visited { color: var(--visited-link); } #customFieldsTable a[href*='/models'] { - color: var(--back-sub); + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); } #customFieldsTable a[href*='/fieldsets'] { background-color: transparent; diff --git a/public/css/dist/skins/skin-green-dark.min.css b/public/css/dist/skins/skin-green-dark.min.css index 275fa58ade..fed4dd79a5 100644 --- a/public/css/dist/skins/skin-green-dark.min.css +++ b/public/css/dist/skins/skin-green-dark.min.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -364,11 +368,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -475,7 +474,16 @@ a:visited { color: var(--visited-link); } #customFieldsTable a[href*='/models'] { - color: var(--back-sub); + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); } #customFieldsTable a[href*='/fieldsets'] { background-color: transparent; diff --git a/public/css/dist/skins/skin-orange-dark.css b/public/css/dist/skins/skin-orange-dark.css index 816c529ef2..04fd2f16ad 100644 --- a/public/css/dist/skins/skin-orange-dark.css +++ b/public/css/dist/skins/skin-orange-dark.css @@ -164,6 +164,10 @@ li.dropdown-item-marker { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -374,11 +378,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -476,7 +475,16 @@ input[type=search] { border-bottom: #000; } #customFieldsTable a[href*='/models'] { - color: var(--back-sub); + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); } #customFieldsTable a[href*='/fieldsets'] { background-color: transparent; diff --git a/public/css/dist/skins/skin-orange-dark.min.css b/public/css/dist/skins/skin-orange-dark.min.css index 816c529ef2..04fd2f16ad 100644 --- a/public/css/dist/skins/skin-orange-dark.min.css +++ b/public/css/dist/skins/skin-orange-dark.min.css @@ -164,6 +164,10 @@ li.dropdown-item-marker { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -374,11 +378,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -476,7 +475,16 @@ input[type=search] { border-bottom: #000; } #customFieldsTable a[href*='/models'] { - color: var(--back-sub); + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); } #customFieldsTable a[href*='/fieldsets'] { background-color: transparent; diff --git a/public/css/dist/skins/skin-purple-dark.css b/public/css/dist/skins/skin-purple-dark.css index 88fe4ee35b..f07f224a34 100644 --- a/public/css/dist/skins/skin-purple-dark.css +++ b/public/css/dist/skins/skin-purple-dark.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -374,11 +378,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -496,6 +495,18 @@ a:visited { box-sizing: border-box; display: table; } +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--back-sub); + color: var(--visited-link); +} .row-striped .row:nth-of-type(odd) div { background-color: var(--back-sub); color: var(--text-main); diff --git a/public/css/dist/skins/skin-purple-dark.min.css b/public/css/dist/skins/skin-purple-dark.min.css index 88fe4ee35b..f07f224a34 100644 --- a/public/css/dist/skins/skin-purple-dark.min.css +++ b/public/css/dist/skins/skin-purple-dark.min.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -374,11 +378,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -496,6 +495,18 @@ a:visited { box-sizing: border-box; display: table; } +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--back-sub); + color: var(--visited-link); +} .row-striped .row:nth-of-type(odd) div { background-color: var(--back-sub); color: var(--text-main); diff --git a/public/css/dist/skins/skin-red-dark.css b/public/css/dist/skins/skin-red-dark.css index db532575b2..907f3ee816 100644 --- a/public/css/dist/skins/skin-red-dark.css +++ b/public/css/dist/skins/skin-red-dark.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -373,11 +377,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -494,7 +493,16 @@ a:visited { color: var(--nav-link); } #customFieldsTable a[href*='/models'] { - color: var(--back-sub); + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--back-sub); + color: var(--visited-link); } #customFieldsTable a[href*='/fieldsets'] { background-color: transparent; diff --git a/public/css/dist/skins/skin-red-dark.min.css b/public/css/dist/skins/skin-red-dark.min.css index db532575b2..907f3ee816 100644 --- a/public/css/dist/skins/skin-red-dark.min.css +++ b/public/css/dist/skins/skin-red-dark.min.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -373,11 +377,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -494,7 +493,16 @@ a:visited { color: var(--nav-link); } #customFieldsTable a[href*='/models'] { - color: var(--back-sub); + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--back-sub); + color: var(--visited-link); } #customFieldsTable a[href*='/fieldsets'] { background-color: transparent; diff --git a/public/css/dist/skins/skin-yellow-dark.css b/public/css/dist/skins/skin-yellow-dark.css index fb692ce058..a1311532e4 100644 --- a/public/css/dist/skins/skin-yellow-dark.css +++ b/public/css/dist/skins/skin-yellow-dark.css @@ -150,6 +150,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -351,11 +355,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -452,6 +451,18 @@ tr th div.th-inner { .box-header.with-border { border-bottom: #000; } +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} .row-striped { vertical-align: top; line-height: 2.6; diff --git a/public/css/dist/skins/skin-yellow-dark.min.css b/public/css/dist/skins/skin-yellow-dark.min.css index fb692ce058..a1311532e4 100644 --- a/public/css/dist/skins/skin-yellow-dark.min.css +++ b/public/css/dist/skins/skin-yellow-dark.min.css @@ -150,6 +150,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -351,11 +355,6 @@ input[type=search] { background-color: var(--back-sub); color: var(--text-main); } -.input-group, -.input-group-addon { - background-color: var(--back-sub) !important; - color: var(--text-main); -} #licensesTable > tbody > tr > td > nobr > a > i.fa { color: var(--text-main); } @@ -452,6 +451,18 @@ tr th div.th-inner { .box-header.with-border { border-bottom: #000; } +#customFieldsTable a[href*='/models'] { + background-color: var(--back-sub-alt); + color: var(--link); +} +#customFieldsTable a[href*='/models']:hover { + background-color: var(--text-sub); + color: var(--button-hover); +} +#customFieldsTable a[href*='/models']:visited { + background-color: var(--background); + color: var(--visited-link); +} .row-striped { vertical-align: top; line-height: 2.6; diff --git a/public/css/overrides.css.map b/public/css/overrides.css.map index bfec6b1f14..3ee4545e6c 100644 --- a/public/css/overrides.css.map +++ b/public/css/overrides.css.map @@ -1 +1 @@ -{"version":3,"sources":["webpack:///./resources/assets/less/overrides.less"],"names":[],"mappings":"AAAA,6OAkBE,yBAEF,mBACE,qBACA,mBACA,gBACA,cACA,WACA,iFACE,WAGJ,MACE,eAGF,UACI,kBACA,gBAGJ,oBACI,cAIJ,iBACA,kBACA,aACA,mBACA,kBACA,8BACA,WACA,YAGA,mBACE,WACA,WACA,YACA,kBACA,kBAGF,gCACE,yBAGF,mBACI,WAGJ,kBACE,aACA,YAIF,QACE,kBAGF,2BACI,kBACA,MACA,QACA,eACA,gBACA,gBACA,iBACA,wBACA,UACA,aACA,gBACA,eACA,cAEJ,aACE,eAEF,aACE,iBAIF,sCACE,cAEF,mBACE,qBAIF,gBACE,kBACA,YACA,yBAGF,uBACE,YACA,WACA,iBAwBF,oCACE,eAAS,CAGX,sBACE,aACA,aAIF,mBACE,WAEF,aACE,cACA,mCAGF,wBACE,cACA,cAGF,0BACQ,mBACA,kBAEN,yBACI,iBAGN,aACE,cACA,6BACA,8CAGF,yBACE,cAGF,yBACE,cAGF,sBACE,cAGF,yBACE,cAGF,wBACE,cAGF,uJACE,WAGF,eACC,eACA,MACA,aACA,YACA,YACA,gBACA,YACC,iBACA,WACA,aAEF,kBACC,kBACA,YACA,WACA,gBACA,gBACA,6BAED,qCACC,kBACA,SACA,WACA,qBACA,oBACA,6BACA,gBACA,cACA,gBACA,WACA,qBACA,mBAED,2CACC,WAED,kBACC,gBACA,gBACA,6BACA,gCAED,qBACC,6BACA,gCAED,uBACC,kBACA,cACA,aACA,WACA,qBAED,6BACC,gBACA,WAED,yBACC,kBACA,SACA,WACA,WAGA,iBACE,eACA,aAKF,WAAY,gBAGA,gCAAkC,iBAC9C,2BAA8B,UAAY,kBAE1C,8CAAiD,cAAgB,eAAiB,kBAClF,2CAA8C,WAAa,eAC3D,0CAA+C,kBAAoB,WAAa,YAAc,cAAgB,mBAAqB,SAAW,SAAW,iBAAmB,kBAAoB,kBAChM,gDAAqD,YAAc,WAAa,YAAc,mBAAqB,mBAAqB,kBAAoB,QAAU,SACtK,qCAA0C,kBAAoB,gBAAoB,WAAa,wCAAkB,cACjH,mDAA0D,QAAW,wCAAkB,mBACvF,4DAAmE,WACnE,0DAAiE,UACjE,sEAA6E,QAC7E,qEAA4E,WAC5E,mDAAwD,yBACxD,yDAA8D,UAC9D,iDAAuD,SAAW,UAClE,gDAAsD,UACtD,oDAAuD,oBAGxD,eACE,qBACA,WACA,gBACA,WACA,UAIF,mQAkBE,WAGF,wBACC,6BAID,UACE,8BAGF,KACE,oIAIA,eAGF,cACE,eACA,mBAGF,aACE,cACE,aAGF,uBACE,wBACA,oBACA,8BAIJ,uCACE,WACA,sBACA,gBAGF,iBACE,gBAGF,8BACE,eAGF,SACE,uBAGF,SACE,gBAGF,eACE,qBACA,CAIF,UACE,eACA,cAGF,YACE,iBACA,oBACA,gBAIF,YACE,YACA,kBACA,SACA,UACA,WACA,gBACA,aAEF,qCACE,WACA,sBACA,UACA,SACA,UACA,YACA,cACA,gBACA,YACA,mBACA,sBACA,kBACA,gBACA,YAGF,GACE,eAGF,GACE,eAGF,GACE,eAIF,aACE,mBACA,gBACA,UACA,iBACA,oDAGA,cAIF,uCACE,yBACA,0BACA,mBAIF,sCACE,gBACA,0BACA,mBAoBF,yGAKE,YACA,wBACA,gBACA,oBACA,mCACA,kCAIF,qKAOE,qBACA,6CACA,eAGF,gCAEE,gBACA,kBAGF,gCAEE,eAAS,CAGX,gCAEE,eAAS,CAGX,mCAEE,eAAS,CAGX,kCAEE,eAAS","file":"css/overrides.css","sourcesContent":[".skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n\n.logo {\n background-color: inherit;\n}\n.main-header .logo {\n width: 100% !important;\n white-space: nowrap;\n text-align: left;\n display: block;\n clear: both;\n &a:link, a:hover, a:visited {\n color: #fff\n }\n}\n.huge {\n font-size: 40px;\n}\n\n.btn-file {\n position: relative;\n overflow: hidden;\n}\n\n.dropdown-menu>li>a {\n color: #354044;\n}\n\n\n#sort tr.cansort {\nborder-radius: 2px;\npadding: 10px;\nbackground: #f4f4f4;\nmargin-bottom: 3px;\nborder-left: 2px solid #e6e7e8;\ncolor: #444;\ncursor: move;\n}\n\n.user-image-inline {\n float: left;\n width: 25px;\n height: 25px;\n border-radius: 50%;\n margin-right: 10px;\n}\n\n.input-group .input-group-addon {\n background-color: #f4f4f4;\n}\n\na.accordion-header {\n color: #333;\n}\n\n.dynamic-form-row {\n padding: 10px;\n margin: 20px;\n}\n\n\n.handle {\n padding-left: 10px;\n}\n\n.btn-file input[type=file] {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 100%;\n min-height: 100%;\n font-size: 100px;\n text-align: right;\n filter: alpha(opacity=0);\n opacity: 0;\n outline: none;\n background: white;\n cursor: inherit;\n display: block;\n}\n.main-footer {\n font-size: 13px;\n}\n.main-header {\n max-height: 150px;\n}\n\n\n.navbar-nav>.user-menu>.dropdown-menu {\n width: inherit;\n}\n.main-header .logo {\n padding: 0px 5px 0px 15px;\n}\n\n\n.sidebar-toggle {\n margin-left: -48px;\n z-index: 100;\n background-color: inherit;\n}\n\n.sidebar-toggle-mobile {\n z-index: 100;\n width: 50px;\n padding-top: 10px;\n}\n\n.skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n.navbar\n.dropdown-menu li a {\n //color: inherit;\n}\n\n.main-header .sidebar-toggle:before {\n content: \"\\f0c9\";\n}\n\n.direct-chat-contacts {\n padding: 10px;\n height: 150px;\n}\n\n\n.select2-container {\n width: 100%;\n}\n.error input {\n color: #dd4b39;\n border: 2px solid #dd4b39 !important;\n}\n\n.error label, .alert-msg {\n color: #dd4b39;\n display: block;\n}\n\n.input-group[class*=\"col-\"] {\n padding-right: 15px;\n padding-left: 15px;\n }\n .control-label.multiline {\n padding-top: 10px;\n }\n\n.btn-outline {\n color: inherit;\n background-color: transparent;\n transition: all .5s;\n}\n\n.btn-primary.btn-outline {\n color: #428bca;\n}\n\n.btn-success.btn-outline {\n color: #5cb85c;\n}\n\n.btn-info.btn-outline {\n color: #5bc0de;\n}\n\n.btn-warning.btn-outline {\n color: #f0ad4e;\n}\n\n.btn-danger.btn-outline {\n color: #d9534f;\n}\n\n.btn-primary.btn-outline:hover, .btn-success.btn-outline:hover, .btn-info.btn-outline:hover, .btn-warning.btn-outline:hover, .btn-danger.btn-outline:hover {\n color: #fff;\n}\n\n.slideout-menu {\n\tposition: fixed;\n\ttop: 0;\n\tright: -250px;\n\twidth: 250px;\n\theight: 100%;\n\tbackground: #333;\n\tz-index: 100;\n margin-top: 100px;\n color: white;\n padding: 10px;\n}\n.slideout-menu h3 {\n\tposition: relative;\n\tpadding: 5px 5px;\n\tcolor: #fff;\n\tfont-size: 1.2em;\n\tfont-weight: 400;\n\tborder-bottom: 4px solid #222;\n}\n.slideout-menu .slideout-menu-toggle {\n\tposition: absolute;\n\ttop: 12px;\n\tright: 10px;\n\tdisplay: inline-block;\n\tpadding: 6px 9px 5px;\n\tfont-family: Arial, sans-serif;\n\tfont-weight: bold;\n\tline-height: 1;\n\tbackground: #222;\n\tcolor: #999;\n\ttext-decoration: none;\n\tvertical-align: top;\n}\n.slideout-menu .slideout-menu-toggle:hover {\n\tcolor: #fff;\n}\n.slideout-menu ul {\n\tlist-style: none;\n\tfont-weight: 300;\n\tborder-top: 1px solid #151515;\n\tborder-bottom: 1px solid #454545;\n}\n.slideout-menu ul li {\n\tborder-top: 1px solid #454545;\n\tborder-bottom: 1px solid #151515;\n}\n.slideout-menu ul li a {\n\tposition: relative;\n\tdisplay: block;\n\tpadding: 10px;\n\tcolor: #999;\n\ttext-decoration: none;\n}\n.slideout-menu ul li a:hover {\n\tbackground: #000;\n\tcolor: #fff;\n}\n.slideout-menu ul li a i {\n\tposition: absolute;\n\ttop: 15px;\n\tright: 10px;\n\topacity: .5;\n}\n\n .btn-box-tool-lg {\n font-size: 16px;\n color: orange;\n }\n\n\n\n .bs-wizard {margin-top: 20px;}\n\n /*Form Wizard*/\n .bs-wizard {border-bottom: solid 1px #e0e0e0; padding: 0 0 10px 0;}\n .bs-wizard > .bs-wizard-step {padding: 0; position: relative;}\n .bs-wizard > .bs-wizard-step + .bs-wizard-step {}\n .bs-wizard > .bs-wizard-step .bs-wizard-stepnum {color: #595959; font-size: 16px; margin-bottom: 5px;}\n .bs-wizard > .bs-wizard-step .bs-wizard-info {color: #999; font-size: 14px;}\n .bs-wizard > .bs-wizard-step > .bs-wizard-dot {position: absolute; width: 30px; height: 30px; display: block; background: #fbe8aa; top: 45px; left: 50%; margin-top: -15px; margin-left: -15px; border-radius: 50%;}\n .bs-wizard > .bs-wizard-step > .bs-wizard-dot:after {content: ' '; width: 14px; height: 14px; background: #fbbd19; border-radius: 50px; position: absolute; top: 8px; left: 8px; }\n .bs-wizard > .bs-wizard-step > .progress {position: relative; border-radius: 0px; height: 8px; box-shadow: none; margin: 20px 0;}\n .bs-wizard > .bs-wizard-step > .progress > .progress-bar {width:0px; box-shadow: none; background: #fbe8aa;}\n .bs-wizard > .bs-wizard-step.complete > .progress > .progress-bar {width:100%;}\n .bs-wizard > .bs-wizard-step.active > .progress > .progress-bar {width:50%;}\n .bs-wizard > .bs-wizard-step:first-child.active > .progress > .progress-bar {width:0%;}\n .bs-wizard > .bs-wizard-step:last-child.active > .progress > .progress-bar {width: 100%;}\n .bs-wizard > .bs-wizard-step.disabled > .bs-wizard-dot {background-color: #f5f5f5;}\n .bs-wizard > .bs-wizard-step.disabled > .bs-wizard-dot:after {opacity: 0;}\n .bs-wizard > .bs-wizard-step:first-child > .progress {left: 50%; width: 50%;}\n .bs-wizard > .bs-wizard-step:last-child > .progress {width: 50%;}\n .bs-wizard > .bs-wizard-step.disabled a.bs-wizard-dot{ pointer-events: none; }\n /*END Form Wizard*/\n\n.left-navblock {\n display: inline-block;\n float: left;\n text-align: left;\n color: white;\n padding: 0px;\n /* adjust based on your layout */\n\n}\n.skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n.navbar\n.dropdown-menu li a {\n color: #333;\n}\n\na.logo.no-hover a:hover {\n background-color: transparent;\n}\n\n\n.required {\n border-right: 6px solid orange;\n}\n\nbody {\n font-family: -apple-system, BlinkMacSystemFont,\n \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\",\n \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n font-size: 13px;\n}\n\n.sidebar-menu {\n font-size: 14px;\n white-space: normal;\n}\n\n@media print {\n a[href]:after {\n content: none;\n }\n\n .tab-content > .tab-pane {\n display: block !important;\n opacity: 1 !important;\n visibility: visible !important;\n }\n}\n\nimg.navbar-brand-img, .navbar-brand>img {\n float: left;\n padding: 5px 5px 5px 0;\n max-height: 50px;\n}\n\n.input-daterange {\n border-radius: 0px;\n}\n\n.btn.bg-maroon, .btn.bg-purple{\n min-width:90px;\n}\n\n[hidden] {\n display: none !important;\n}\n\n#toolbar {\n margin-top: 10px;\n}\n\n#uploadPreview {\n border-color: grey;\n border-width: 1px;\n border-style: solid\n}\n\n.icon-med {\n font-size: 20px;\n color: #889195;\n}\n\n#login-logo {\n padding-top: 20px;\n padding-bottom: 10px;\n max-width: 200px\n}\n\n// accessibility skip link\na.skip-main {\n left:-999px;\n position:absolute;\n top:auto;\n width:1px;\n height:1px;\n overflow:hidden;\n z-index:-999;\n}\na.skip-main:focus, a.skip-main:active {\n color: #fff;\n background-color:#000;\n left: auto;\n top: auto;\n width: 30%;\n height: auto;\n overflow:auto;\n margin: 10px 35%;\n padding:5px;\n border-radius: 15px;\n border:4px solid yellow;\n text-align:center;\n font-size:1.2em;\n z-index:999;\n}\n\nh2 {\n font-size: 22px;\n}\n\nh3 {\n font-size: 20px;\n}\n\nh4 {\n font-size: 16px;\n}\n\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: #f9f9f9;\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background: #FFFFFF;\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n\n\n\n\n/**\n\n COLUMN SELECTOR ICONS\n -----------------------------\n This is kind of weird, but it is necessary to prevent the column-selector code from barfing, since\n any HTML used in the UserPresenter \"title\" attribute breaks the column selector HTML.\n\n Instead, we use CSS to add the icon into the table header, which leaves the column selector\n \"title\" text as-is.\n\n See https://github.com/snipe/snipe-it/issues/7989\n\n */\n\nth.css-barcode > .th-inner,\nth.css-license > .th-inner,\nth.css-consumable > .th-inner,\nth.css-accessory > .th-inner\n{\n font-size: 0px;\n line-height: 4!important;\n text-align: left;\n text-rendering: auto;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n\nth.css-padlock > .th-inner::before,\nth.css-barcode > .th-inner::before,\nth.css-license > .th-inner::before,\nth.css-consumable > .th-inner::before,\nth.css-accessory > .th-inner::before\n\n{\n display: inline-block;\n font: normal normal normal 14px/1 FontAwesome;\n font-size: 20px;\n}\n\nth.css-padlock > .th-inner::before\n{\n content: \"\\f023\";\n padding-right: 2px;\n}\n\nth.css-barcode > .th-inner::before\n{\n content: \"\\f02a\";\n}\n\nth.css-license > .th-inner::before\n{\n content: \"\\f0c7\";\n}\n\nth.css-consumable > .th-inner::before\n{\n content: \"\\f043\";\n}\n\nth.css-accessory > .th-inner::before\n{\n content: \"\\f11c\";\n}\n\n\n// WEBPACK FOOTER //\n// ./resources/assets/less/overrides.less"],"sourceRoot":""} \ No newline at end of file +{"version":3,"sources":["webpack:///./resources/assets/less/overrides.less"],"names":[],"mappings":"AAAA,6OAkBE,yBAEF,mBACE,qBACA,mBACA,gBACA,cACA,WACA,iFACE,WAGJ,MACE,eAGF,UACI,kBACA,gBAGJ,oBACI,cAIJ,iBACA,kBACA,aACA,mBACA,kBACA,8BACA,WACA,YAGA,mBACE,WACA,WACA,YACA,kBACA,kBAGF,gCACE,yBAGF,mBACI,WAGJ,kBACE,aACA,YAIF,QACE,kBAGF,2BACI,kBACA,MACA,QACA,eACA,gBACA,gBACA,iBACA,wBACA,UACA,aACA,gBACA,eACA,cAEJ,aACE,eAEF,aACE,iBAIF,sCACE,cAEF,mBACE,qBAIF,gBACE,kBACA,YACA,yBAGF,uBACE,YACA,WACA,iBAwBF,oCACE,eAAS,CAGX,sBACE,aACA,aAIF,mBACE,WAEF,aACE,cACA,mCAGF,wBACE,cACA,cAGF,0BACQ,mBACA,kBAEN,yBACI,iBAGN,aACE,cACA,6BACA,8CAGF,yBACE,cAGF,yBACE,cAGF,sBACE,cAGF,yBACE,cAGF,wBACE,cAGF,uJACE,WAGF,eACC,eACA,MACA,aACA,YACA,YACA,gBACA,YACC,iBACA,WACA,aAEF,kBACC,kBACA,YACA,WACA,gBACA,gBACA,6BAED,qCACC,kBACA,SACA,WACA,qBACA,oBACA,6BACA,gBACA,cACA,gBACA,WACA,qBACA,mBAED,2CACC,WAED,kBACC,gBACA,gBACA,6BACA,gCAED,qBACC,6BACA,gCAED,uBACC,kBACA,cACA,aACA,WACA,qBAED,6BACC,gBACA,WAED,yBACC,kBACA,SACA,WACA,WAGA,iBACE,eACA,aAKF,WAAY,gBAGA,gCAAkC,iBAC9C,2BAA8B,UAAY,kBAE1C,8CAAiD,cAAgB,eAAiB,kBAClF,2CAA8C,WAAa,eAC3D,0CAA+C,kBAAoB,WAAa,YAAc,cAAgB,mBAAqB,SAAW,SAAW,iBAAmB,kBAAoB,kBAChM,gDAAqD,YAAc,WAAa,YAAc,mBAAqB,mBAAqB,kBAAoB,QAAU,SACtK,qCAA0C,kBAAoB,gBAAoB,WAAa,wCAAkB,cACjH,mDAA0D,QAAW,wCAAkB,mBACvF,4DAAmE,WACnE,0DAAiE,UACjE,sEAA6E,QAC7E,qEAA4E,WAC5E,mDAAwD,yBACxD,yDAA8D,UAC9D,iDAAuD,SAAW,UAClE,gDAAsD,UACtD,oDAAuD,oBAGxD,eACE,qBACA,WACA,gBACA,WACA,UAIF,mQAkBE,WAGF,wBACC,6BAID,UACE,8BAGF,KACE,oIAIA,eAGF,cACE,eACA,mBAGF,aACE,cACE,aAGF,uBACE,wBACA,oBACA,8BAIJ,uCACE,WACA,sBACA,gBAGF,iBACE,gBAGF,8BACE,eAGF,SACE,uBAGF,SACE,gBAGF,eACE,qBACA,CAIF,UACE,eACA,cAGF,YACE,iBACA,oBACA,gBAIF,YACE,YACA,kBACA,SACA,UACA,WACA,gBACA,aAEF,qCACE,WACA,sBACA,UACA,SACA,UACA,YACA,cACA,gBACA,YACA,mBACA,sBACA,kBACA,gBACA,YAGF,GACE,eAGF,GACE,eAGF,GACE,eAIF,aACE,mBACA,gBACA,UACA,iBACA,oDAGA,cAIF,uCACE,yBACA,0BACA,mBAIF,sCACE,gBACA,0BACA,mBAoBF,yGAKE,YACA,wBACA,gBACA,oBACA,mCACA,kCAIF,qKAOE,qBACA,6CACA,eAGF,gCAEE,gBACA,kBAGF,gCAEE,eAAS,CAGX,gCAEE,eAAS,CAGX,mCAEE,eAAS,CAGX,kCAEE,eAAS","file":"css/overrides.css","sourcesContent":[".skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n\n.logo {\n background-color: inherit;\n}\n.main-header .logo {\n width: 100% !important;\n white-space: nowrap;\n text-align: left;\n display: block;\n clear: both;\n &a:link, a:hover, a:visited {\n color: #fff\n }\n}\n.huge {\n font-size: 40px;\n}\n\n.btn-file {\n position: relative;\n overflow: hidden;\n}\n\n.dropdown-menu>li>a {\n color: #354044;\n}\n\n\n#sort tr.cansort {\nborder-radius: 2px;\npadding: 10px;\nbackground: #f4f4f4;\nmargin-bottom: 3px;\nborder-left: 2px solid #e6e7e8;\ncolor: #444;\ncursor: move;\n}\n\n.user-image-inline {\n float: left;\n width: 25px;\n height: 25px;\n border-radius: 50%;\n margin-right: 10px;\n}\n\n.input-group .input-group-addon {\n background-color: #f4f4f4;\n}\n\na.accordion-header {\n color: #333;\n}\n\n.dynamic-form-row {\n padding: 10px;\n margin: 20px;\n}\n\n\n.handle {\n padding-left: 10px;\n}\n\n.btn-file input[type=file] {\n position: absolute;\n top: 0;\n right: 0;\n min-width: 100%;\n min-height: 100%;\n font-size: 100px;\n text-align: right;\n filter: alpha(opacity=0);\n opacity: 0;\n outline: none;\n background: white;\n cursor: inherit;\n display: block;\n}\n.main-footer {\n font-size: 13px;\n}\n.main-header {\n max-height: 150px;\n}\n\n\n.navbar-nav>.user-menu>.dropdown-menu {\n width: inherit;\n}\n.main-header .logo {\n padding: 0px 5px 0px 15px;\n}\n\n\n.sidebar-toggle {\n margin-left: -48px;\n z-index: 100;\n background-color: inherit;\n}\n\n.sidebar-toggle-mobile {\n z-index: 100;\n width: 50px;\n padding-top: 10px;\n}\n\n.skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n.navbar\n.dropdown-menu li a {\n //color: inherit;\n}\n\n.main-header .sidebar-toggle:before {\n content: \"\\f0c9\";\n}\n\n.direct-chat-contacts {\n padding: 10px;\n height: 150px;\n}\n\n\n.select2-container {\n width: 100%;\n}\n.error input {\n color: #dd4b39;\n border: 2px solid #dd4b39 !important;\n}\n\n.error label, .alert-msg {\n color: #dd4b39;\n display: block;\n}\n\n.input-group[class*=\"col-\"] {\n padding-right: 15px;\n padding-left: 15px;\n }\n .control-label.multiline {\n padding-top: 10px;\n }\n\n.btn-outline {\n color: inherit;\n background-color: transparent;\n transition: all .5s;\n}\n\n.btn-primary.btn-outline {\n color: #428bca;\n}\n\n.btn-success.btn-outline {\n color: #5cb85c;\n}\n\n.btn-info.btn-outline {\n color: #5bc0de;\n}\n\n.btn-warning.btn-outline {\n color: #f0ad4e;\n}\n\n.btn-danger.btn-outline {\n color: #d9534f;\n}\n\n.btn-primary.btn-outline:hover, .btn-success.btn-outline:hover, .btn-info.btn-outline:hover, .btn-warning.btn-outline:hover, .btn-danger.btn-outline:hover {\n color: #fff;\n}\n\n.slideout-menu {\n\tposition: fixed;\n\ttop: 0;\n\tright: -250px;\n\twidth: 250px;\n\theight: 100%;\n\tbackground: #333;\n\tz-index: 100;\n margin-top: 100px;\n color: white;\n padding: 10px;\n}\n.slideout-menu h3 {\n\tposition: relative;\n\tpadding: 5px 5px;\n\tcolor: #fff;\n\tfont-size: 1.2em;\n\tfont-weight: 400;\n\tborder-bottom: 4px solid #222;\n}\n.slideout-menu .slideout-menu-toggle {\n\tposition: absolute;\n\ttop: 12px;\n\tright: 10px;\n\tdisplay: inline-block;\n\tpadding: 6px 9px 5px;\n\tfont-family: Arial, sans-serif;\n\tfont-weight: bold;\n\tline-height: 1;\n\tbackground: #222;\n\tcolor: #999;\n\ttext-decoration: none;\n\tvertical-align: top;\n}\n.slideout-menu .slideout-menu-toggle:hover {\n\tcolor: #fff;\n}\n.slideout-menu ul {\n\tlist-style: none;\n\tfont-weight: 300;\n\tborder-top: 1px solid #151515;\n\tborder-bottom: 1px solid #454545;\n}\n.slideout-menu ul li {\n\tborder-top: 1px solid #454545;\n\tborder-bottom: 1px solid #151515;\n}\n.slideout-menu ul li a {\n\tposition: relative;\n\tdisplay: block;\n\tpadding: 10px;\n\tcolor: #999;\n\ttext-decoration: none;\n}\n.slideout-menu ul li a:hover {\n\tbackground: #000;\n\tcolor: #fff;\n}\n.slideout-menu ul li a i {\n\tposition: absolute;\n\ttop: 15px;\n\tright: 10px;\n\topacity: .5;\n}\n\n .btn-box-tool-lg {\n font-size: 16px;\n color: orange;\n }\n\n\n\n .bs-wizard {margin-top: 20px;}\n\n /*Form Wizard*/\n .bs-wizard {border-bottom: solid 1px #e0e0e0; padding: 0 0 10px 0;}\n .bs-wizard > .bs-wizard-step {padding: 0; position: relative;}\n .bs-wizard > .bs-wizard-step + .bs-wizard-step {}\n .bs-wizard > .bs-wizard-step .bs-wizard-stepnum {color: #595959; font-size: 16px; margin-bottom: 5px;}\n .bs-wizard > .bs-wizard-step .bs-wizard-info {color: #999; font-size: 14px;}\n .bs-wizard > .bs-wizard-step > .bs-wizard-dot {position: absolute; width: 30px; height: 30px; display: block; background: #fbe8aa; top: 45px; left: 50%; margin-top: -15px; margin-left: -15px; border-radius: 50%;}\n .bs-wizard > .bs-wizard-step > .bs-wizard-dot:after {content: ' '; width: 14px; height: 14px; background: #fbbd19; border-radius: 50px; position: absolute; top: 8px; left: 8px; }\n .bs-wizard > .bs-wizard-step > .progress {position: relative; border-radius: 0px; height: 8px; box-shadow: none; margin: 20px 0;}\n .bs-wizard > .bs-wizard-step > .progress > .progress-bar {width:0px; box-shadow: none; background: #fbe8aa;}\n .bs-wizard > .bs-wizard-step.complete > .progress > .progress-bar {width:100%;}\n .bs-wizard > .bs-wizard-step.active > .progress > .progress-bar {width:50%;}\n .bs-wizard > .bs-wizard-step:first-child.active > .progress > .progress-bar {width:0%;}\n .bs-wizard > .bs-wizard-step:last-child.active > .progress > .progress-bar {width: 100%;}\n .bs-wizard > .bs-wizard-step.disabled > .bs-wizard-dot {background-color: #f5f5f5;}\n .bs-wizard > .bs-wizard-step.disabled > .bs-wizard-dot:after {opacity: 0;}\n .bs-wizard > .bs-wizard-step:first-child > .progress {left: 50%; width: 50%;}\n .bs-wizard > .bs-wizard-step:last-child > .progress {width: 50%;}\n .bs-wizard > .bs-wizard-step.disabled a.bs-wizard-dot{ pointer-events: none; }\n /*END Form Wizard*/\n\n.left-navblock {\n display: inline-block;\n float: left;\n text-align: left;\n color: white;\n padding: 0px;\n /* adjust based on your layout */\n\n}\n.skin-red\n.skin-purple\n.skin-blue\n.skin-black\n.skin-orange\n.skin-yellow\n.skin-green\n.skin-red-dark\n.skin-purple-dark\n.skin-blue-dark\n.skin-black-dark\n.skin-orange-dark\n.skin-yellow-dark\n.skin-green-dark\n.skin-contrast\n.main-header\n.navbar\n.dropdown-menu li a {\n color: #333;\n}\n\na.logo.no-hover a:hover {\n background-color: transparent;\n}\n\n\n.required {\n border-right: 6px solid orange;\n}\n\nbody {\n font-family: -apple-system, BlinkMacSystemFont,\n \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\",\n \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n font-size: 13px;\n}\n\n.sidebar-menu {\n font-size: 14px;\n white-space: normal;\n}\n\n@media print {\n a[href]:after {\n content: none;\n }\n\n .tab-content > .tab-pane {\n display: block !important;\n opacity: 1 !important;\n visibility: visible !important;\n }\n}\n\nimg.navbar-brand-img, .navbar-brand>img {\n float: left;\n padding: 5px 5px 5px 0;\n max-height: 50px;\n}\n\n.input-daterange {\n border-radius: 0px;\n}\n\n.btn.bg-maroon, .btn.bg-purple{\n min-width:90px;\n}\n\n[hidden] {\n display: none !important;\n}\n\n#toolbar {\n margin-top: 10px;\n}\n\n#uploadPreview {\n border-color: grey;\n border-width: 1px;\n border-style: solid\n}\n\n.icon-med {\n font-size: 20px;\n color: #889195;\n}\n\n#login-logo {\n padding-top: 20px;\n padding-bottom: 10px;\n max-width: 200px\n}\n\n// accessibility skip link\na.skip-main {\n left:-999px;\n position:absolute;\n top:auto;\n width:1px;\n height:1px;\n overflow:hidden;\n z-index:-999;\n}\na.skip-main:focus, a.skip-main:active {\n color: #fff;\n background-color:#000;\n left: auto;\n top: auto;\n width: 30%;\n height: auto;\n overflow:auto;\n margin: 10px 35%;\n padding:5px;\n border-radius: 15px;\n border:4px solid yellow;\n text-align:center;\n font-size:1.2em;\n z-index:999;\n}\n\nh2 {\n font-size: 22px;\n}\n\nh3 {\n font-size: 20px;\n}\n\nh4 {\n font-size: 16px;\n}\n\n\n.row-striped {\n vertical-align: top;\n line-height: 2.6;\n padding: 0px;\n margin-left: 20px;\n box-sizing: border-box;\n //border-left: 1px solid #dddddd;\n //border-right: 1px solid #dddddd;\n display: table;\n\n}\n\n.row-striped .row:nth-of-type(odd) div {\n background-color: #f9f9f9;\n border-top: 1px solid #dddddd;\n display: table-cell;\n\n}\n\n.row-striped .row:nth-of-type(even) div {\n background: #FFFFFF;\n border-top: 1px solid #dddddd;\n display: table-cell;\n}\n\n\n\n\n/**\n\n COLUMN SELECTOR ICONS\n -----------------------------\n This is kind of weird, but it is necessary to prevent the column-selector code from barfing, since\n any HTML used in the UserPresenter \"title\" attribute breaks the column selector HTML.\n\n Instead, we use CSS to add the icon into the table header, which leaves the column selector\n \"title\" text as-is.\n\n See https://github.com/grokability/snipe-it/issues/7989\n\n */\n\nth.css-barcode > .th-inner,\nth.css-license > .th-inner,\nth.css-consumable > .th-inner,\nth.css-accessory > .th-inner\n{\n font-size: 0px;\n line-height: 4!important;\n text-align: left;\n text-rendering: auto;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n\nth.css-padlock > .th-inner::before,\nth.css-barcode > .th-inner::before,\nth.css-license > .th-inner::before,\nth.css-consumable > .th-inner::before,\nth.css-accessory > .th-inner::before\n\n{\n display: inline-block;\n font: normal normal normal 14px/1 FontAwesome;\n font-size: 20px;\n}\n\nth.css-padlock > .th-inner::before\n{\n content: \"\\f023\";\n padding-right: 2px;\n}\n\nth.css-barcode > .th-inner::before\n{\n content: \"\\f02a\";\n}\n\nth.css-license > .th-inner::before\n{\n content: \"\\f0c7\";\n}\n\nth.css-consumable > .th-inner::before\n{\n content: \"\\f043\";\n}\n\nth.css-accessory > .th-inner::before\n{\n content: \"\\f11c\";\n}\n\n\n// WEBPACK FOOTER //\n// ./resources/assets/less/overrides.less"],"sourceRoot":""} diff --git a/public/css/webfonts/fa-brands-400.ttf b/public/css/webfonts/fa-brands-400.ttf index 1fbb1f7c32..0f82a83605 100644 Binary files a/public/css/webfonts/fa-brands-400.ttf and b/public/css/webfonts/fa-brands-400.ttf differ diff --git a/public/css/webfonts/fa-brands-400.woff2 b/public/css/webfonts/fa-brands-400.woff2 index 5d28021697..3c5cf97ec3 100644 Binary files a/public/css/webfonts/fa-brands-400.woff2 and b/public/css/webfonts/fa-brands-400.woff2 differ diff --git a/public/css/webfonts/fa-regular-400.ttf b/public/css/webfonts/fa-regular-400.ttf index 549d68dc02..9ee1919dc2 100644 Binary files a/public/css/webfonts/fa-regular-400.ttf and b/public/css/webfonts/fa-regular-400.ttf differ diff --git a/public/css/webfonts/fa-regular-400.woff2 b/public/css/webfonts/fa-regular-400.woff2 index 18400d7fad..57d9179654 100644 Binary files a/public/css/webfonts/fa-regular-400.woff2 and b/public/css/webfonts/fa-regular-400.woff2 differ diff --git a/public/css/webfonts/fa-solid-900.ttf b/public/css/webfonts/fa-solid-900.ttf index bb2a869565..1c10972ece 100644 Binary files a/public/css/webfonts/fa-solid-900.ttf and b/public/css/webfonts/fa-solid-900.ttf differ diff --git a/public/css/webfonts/fa-solid-900.woff2 b/public/css/webfonts/fa-solid-900.woff2 index 758dd4f607..16721020f0 100644 Binary files a/public/css/webfonts/fa-solid-900.woff2 and b/public/css/webfonts/fa-solid-900.woff2 differ diff --git a/public/css/webfonts/fa-v4compatibility.ttf b/public/css/webfonts/fa-v4compatibility.ttf index 8c5864c42f..3bcb67ffcc 100644 Binary files a/public/css/webfonts/fa-v4compatibility.ttf and b/public/css/webfonts/fa-v4compatibility.ttf differ diff --git a/public/css/webfonts/fa-v4compatibility.woff2 b/public/css/webfonts/fa-v4compatibility.woff2 index f94bec2275..fbafb22222 100644 Binary files a/public/css/webfonts/fa-v4compatibility.woff2 and b/public/css/webfonts/fa-v4compatibility.woff2 differ diff --git a/public/img/default-sm.png b/public/img/default-sm.png deleted file mode 100644 index 49aa98b55d..0000000000 Binary files a/public/img/default-sm.png and /dev/null differ diff --git a/public/img/default.png b/public/img/demo/avatars/default.png similarity index 100% rename from public/img/default.png rename to public/img/demo/avatars/default.png diff --git a/public/img/demo/manufacturers/google.webp b/public/img/demo/manufacturers/google.webp new file mode 100644 index 0000000000..4355fcec82 Binary files /dev/null and b/public/img/demo/manufacturers/google.webp differ diff --git a/public/img/demo/manufacturers/huawei.webp b/public/img/demo/manufacturers/huawei.webp new file mode 100644 index 0000000000..23a6c21dfe Binary files /dev/null and b/public/img/demo/manufacturers/huawei.webp differ diff --git a/public/img/demo/manufacturers/samsung.png b/public/img/demo/manufacturers/samsung.png new file mode 100644 index 0000000000..0dc9444ef0 Binary files /dev/null and b/public/img/demo/manufacturers/samsung.png differ diff --git a/public/img/demo/manufacturers/sony.png b/public/img/demo/manufacturers/sony.png new file mode 100644 index 0000000000..95753078c9 Binary files /dev/null and b/public/img/demo/manufacturers/sony.png differ diff --git a/public/js/build/app.js b/public/js/build/app.js index 50fab0d8d0..4c548c266f 100644 --- a/public/js/build/app.js +++ b/public/js/build/app.js @@ -1,828 +1,16 @@ /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ -/***/ "./resources/assets/js/bootstrap.js": -/*!******************************************!*\ - !*** ./resources/assets/js/bootstrap.js ***! - \******************************************/ -/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { +/***/ "./node_modules/admin-lte/build/less/AdminLTE.less": +/*!*********************************************************!*\ + !*** ./node_modules/admin-lte/build/less/AdminLTE.less ***! + \*********************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -window._ = __webpack_require__(/*! lodash */ "./node_modules/lodash/lodash.js"); +"use strict"; +__webpack_require__.r(__webpack_exports__); +// extracted by mini-css-extract-plugin -/** - * We'll load jQuery and the Bootstrap jQuery plugin which provides support - * for JavaScript based Bootstrap features such as modals and tabs. This - * code may be modified to fit the specific needs of your application. - */ -//window.$ = window.jQuery = require('jquery'); - -/** - * jQuery UI is loaded here and then the tooltip is assigned another funtion name - * This resolves the issue of jquery-ui & bootstrap tooltip conflict - */ -__webpack_require__(/*! jquery-ui */ "./node_modules/jquery-ui/ui/widget.js"); -jQuery.fn.uitooltip = jQuery.fn.tooltip; - -/** - * Load boostrap - */ -__webpack_require__(/*! bootstrap-less */ "./node_modules/bootstrap-less/js/bootstrap.js"); - -// require('admin-lte'); - -// require('chart.js'); - -// require('jquery-form-validator'); //says something about dependency -/** - * Echo exposes an expressive API for subscribing to channels and listening - * for events that are broadcast by Laravel. Echo and event broadcasting - * allows your team to easily build robust real-time web applications. - */ - -// import Echo from "laravel-echo" - -// window.Echo = new Echo({ -// broadcaster: 'pusher', -// key: 'your-pusher-key' -// }); - -/***/ }), - -/***/ "./resources/assets/js/snipeit.js": -/*!****************************************!*\ - !*** ./resources/assets/js/snipeit.js ***! - \****************************************/ -/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { - -// var jQuery = require('jquery'); -// window.jQuery = jQuery -// window.$ = jQuery - -__webpack_require__(/*! ./bootstrap */ "./resources/assets/js/bootstrap.js"); - -/** - * Module containing core application logic. - * @param {jQuery} $ Insulated jQuery object - * @param {JSON} settings Insulated `window.snipeit.settings` object. - * @return {IIFE} Immediately invoked. Returns self. - */ - -lineOptions = { - legend: { - position: "bottom" - }, - scales: { - yAxes: [{ - ticks: { - fontColor: "rgba(0,0,0,0.5)", - fontStyle: "bold", - beginAtZero: true, - maxTicksLimit: 5, - padding: 20 - }, - gridLines: { - drawTicks: false, - display: false - } - }], - xAxes: [{ - gridLines: { - zeroLineColor: "transparent" - }, - ticks: { - padding: 20, - fontColor: "rgba(0,0,0,0.5)", - fontStyle: "bold" - } - }] - } -}; -pieOptions = { - //Boolean - Whether we should show a stroke on each segment - segmentShowStroke: true, - //String - The colour of each segment stroke - segmentStrokeColor: "#fff", - //Number - The width of each segment stroke - segmentStrokeWidth: 1, - //Number - The percentage of the chart that we cut out of the middle - percentageInnerCutout: 50, - // This is 0 for Pie charts - //Number - Amount of animation steps - animationSteps: 100, - //String - Animation easing effect - animationEasing: "easeOutBounce", - //Boolean - Whether we animate the rotation of the Doughnut - animateRotate: true, - //Boolean - Whether we animate scaling the Doughnut from the centre - animateScale: false, - //Boolean - whether to make the chart responsive to window resizing - responsive: true, - // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container - maintainAspectRatio: false, - //String - A legend template - legendTemplate: "
    -legend\"><% for (var i=0; i
  • " + "" + "<%if(segments[i].label){%><%=segments[i].label%><%}%> foo
  • <%}%>
", - //String - A tooltip template - tooltipTemplate: "<%=value %> <%=label%> " -}; - -//----------------- -//- END PIE CHART - -//----------------- - -var baseUrl = $('meta[name="baseUrl"]').attr('content'); -(function ($, settings) { - var Components = {}; - Components.modals = {}; - - // confirm restore modal - Components.modals.confirmRestore = function () { - var $el = $('table'); - var events = { - 'click': function click(evnt) { - var $context = $(this); - var $restoreConfirmModal = $('#restoreConfirmModal'); - var href = $context.attr('href'); - var message = $context.attr('data-content'); - var title = $context.attr('data-title'); - $('#restoreConfirmModalLabel').text(title); - $restoreConfirmModal.find('.modal-body').text(message); - $('#restoreForm').attr('action', href); - $restoreConfirmModal.modal({ - show: true - }); - return false; - } - }; - var render = function render() { - $el.on('click', '.restore-asset', events['click']); - }; - return { - render: render - }; - }; - - // confirm delete modal - Components.modals.confirmDelete = function () { - var $el = $('table'); - var events = { - 'click': function click(evnt) { - var $context = $(this); - var $dataConfirmModal = $('#dataConfirmModal'); - var href = $context.attr('href'); - var message = $context.attr('data-content'); - var title = $context.attr('data-title'); - $('#myModalLabel').text(title); - $dataConfirmModal.find('.modal-body').text(message); - $('#deleteForm').attr('action', href); - $dataConfirmModal.modal({ - show: true - }); - return false; - } - }; - var render = function render() { - $el.on('click', '.delete-asset', events['click']); - }; - return { - render: render - }; - }; - - /** - * Application start point - * Component definition stays out of load event, execution only happens. - */ - $(function () { - new Components.modals.confirmRestore().render(); - new Components.modals.confirmDelete().render(); - }); -})(jQuery, window.snipeit.settings); -$(document).ready(function () { - /* - * Slideout help menu - */ - $('.slideout-menu-toggle').on('click', function (event) { - event.preventDefault(); - // create menu variables - var slideoutMenu = $('.slideout-menu'); - var slideoutMenuWidth = $('.slideout-menu').width(); - - // toggle open class - slideoutMenu.toggleClass("open"); - - // slide menu - if (slideoutMenu.hasClass("open")) { - slideoutMenu.show(); - slideoutMenu.animate({ - right: "0px" - }); - } else { - slideoutMenu.animate({ - right: -slideoutMenuWidth - }, "-350px"); - slideoutMenu.fadeOut(); - } - }); - - /* - * Select2 - */ - - $('select.select2:not(".select2-hidden-accessible")').each(function (i, obj) { - { - $(obj).select2(); - } - }); - - // $('.datepicker').datepicker(); - // var datepicker = $.fn.datepicker.noConflict(); // return $.fn.datepicker to previously assigned value - // $.fn.bootstrapDP = datepicker; - // $('.datepicker').datepicker(); - - // Crazy select2 rich dropdowns with images! - $('.js-data-ajax').each(function (i, item) { - var link = $(item); - var endpoint = link.data("endpoint"); - var select = link.data("select"); - link.select2({ - /** - * Adds an empty placeholder, allowing every select2 instance to be cleared. - * This placeholder can be overridden with the "data-placeholder" attribute. - */ - placeholder: '', - allowClear: true, - ajax: { - // the baseUrl includes a trailing slash - url: baseUrl + 'api/v1/' + endpoint + '/selectlist', - dataType: 'json', - delay: 250, - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - }, - data: function data(params) { - var data = { - search: params.term, - page: params.page || 1, - assetStatusType: link.data("asset-status-type") - }; - return data; - }, - /* processResults: function (data, params) { - params.page = params.page || 1; - var answer = { - results: data.items, - pagination: { - more: data.pagination.more - } - }; - return answer; - }, */ - cache: true - }, - //escapeMarkup: function (markup) { return markup; }, // let our custom formatter work - templateResult: formatDatalistSafe - //templateSelection: formatDataSelection - }); - }); - function getSelect2Value(element) { - // if the passed object is not a jquery object, assuming 'element' is a selector - if (!(element instanceof jQuery)) element = $(element); - var select = element.data("select2"); - - // There's two different locations where the select2-generated input element can be. - searchElement = select.dropdown.$search || select.$container.find(".select2-search__field"); - var value = searchElement.val(); - return value; - } - $(".select2-hidden-accessible").on('select2:selecting', function (e) { - var data = e.params.args.data; - var isMouseUp = false; - var element = $(this); - var value = getSelect2Value(element); - if (e.params.args.originalEvent) isMouseUp = e.params.args.originalEvent.type == "mouseup"; - - // if selected item does not match typed text, do not allow it to pass - force close for ajax. - if (!isMouseUp) { - if (value.toLowerCase() && data.text.toLowerCase().indexOf(value) < 0) { - e.preventDefault(); - element.select2('close'); - - // if it does match, we set a flag in the event (which gets passed to subsequent events), telling it not to worry about the ajax - } else if (value.toLowerCase() && data.text.toLowerCase().indexOf(value) > -1) { - e.params.args.noForceAjax = true; - } - } - }); - $(".select2-hidden-accessible").on('select2:closing', function (e) { - var element = $(this); - var value = getSelect2Value(element); - var noForceAjax = false; - var isMouseUp = false; - if (e.params.args.originalSelect2Event) noForceAjax = e.params.args.originalSelect2Event.noForceAjax; - if (e.params.args.originalEvent) isMouseUp = e.params.args.originalEvent.type == "mouseup"; - if (value && !noForceAjax && !isMouseUp) { - var endpoint = element.data("endpoint"); - var assetStatusType = element.data("asset-status-type"); - $.ajax({ - url: baseUrl + 'api/v1/' + endpoint + '/selectlist?search=' + value + '&page=1' + (assetStatusType ? '&assetStatusType=' + assetStatusType : ''), - dataType: 'json', - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - } - }).done(function (response) { - var currentlySelected = element.select2('data').map(function (x) { - return +x.id; - }).filter(function (x) { - return x !== 0; - }); - - // makes sure we're not selecting the same thing twice for multiples - var filteredResponse = response.results.filter(function (item) { - return currentlySelected.indexOf(+item.id) < 0; - }); - var first = currentlySelected.length > 0 ? filteredResponse[0] : response.results[0]; - if (first && first.id) { - first.selected = true; - if ($("option[value='" + first.id + "']", element).length < 1) { - var option = new Option(first.text, first.id, true, true); - element.append(option); - } else { - var isMultiple = element.attr("multiple") == "multiple"; - element.val(isMultiple ? element.val().concat(first.id) : element.val(first.id)); - } - element.trigger('change'); - element.trigger({ - type: 'select2:select', - params: { - data: first - } - }); - } - }); - } - }); - function formatDatalist(datalist) { - var loading_markup = ' Loading...'; - if (datalist.loading) { - return loading_markup; - } - var markup = '
'; - markup += '
'; - if (datalist.image) { - markup += "
" + datalist.text + "
"; - } else { - markup += '
'; - } - markup += "
" + datalist.text + "
"; - markup += "
"; - return markup; - } - function formatDatalistSafe(datalist) { - // console.warn("What in the hell is going on with Select2?!?!!?!?"); - // console.warn($.select2); - if (datalist.loading) { - return $(' Loading...'); - } - var root_div = $("
"); - var left_pull = $("
"); - if (datalist.image) { - var inner_div = $("
"); - /****************************************************************** - * - * We are specifically chosing empty alt-text below, because this - * image conveys no additional information, relative to the text - * that will *always* be there in any select2 list that is in use - * in Snipe-IT. If that changes, we would probably want to change - * some signatures of some functions, but right now, we don't want - * screen readers to say "HP SuperJet 5000, .... picture of HP - * SuperJet 5000..." and so on, for every single row in a list of - * assets or models or whatever. - * - *******************************************************************/ - var img = $(""); - // console.warn("Img is: "); - // console.dir(img); - // console.warn("Strigularly, that's: "); - // console.log(img); - img.attr("src", datalist.image); - inner_div.append(img); - } else { - var inner_div = $("
"); - } - left_pull.append(inner_div); - root_div.append(left_pull); - var name_div = $("
"); - name_div.text(datalist.text); - root_div.append(name_div); - var safe_html = root_div.get(0).outerHTML; - var old_html = formatDatalist(datalist); - if (safe_html != old_html) { - //console.log("HTML MISMATCH: "); - //console.log("FormatDatalistSafe: "); - // console.dir(root_div.get(0)); - //console.log(safe_html); - //console.log("FormatDataList: "); - //console.log(old_html); - } - return root_div; - } - function formatDataSelection(datalist) { - // 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, '>').replace(/Click me - $('a[data-toggle="tab"]').click(function (e) { - var href = $(this).attr("href"); - history.pushState(null, null, href); - e.preventDefault(); - $('a[href="' + $(this).attr('href') + '"]').tab('show'); - }); - - // ------------------------------------------------ - // End Deep Linking for Bootstrap tabs - // ------------------------------------------------ - - // Image preview - function readURL(input, $preview) { - if (input.files && input.files[0]) { - var reader = new FileReader(); - reader.onload = function (e) { - $preview.attr('src', e.target.result); - }; - reader.readAsDataURL(input.files[0]); - } - } - function formatBytes(bytes) { - if (bytes < 1024) return bytes + " Bytes";else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " KB";else if (bytes < 1073741824) return (bytes / 1048576).toFixed(2) + " MB";else return (bytes / 1073741824).toFixed(2) + " GB"; - } - - // File size validation - $('.js-uploadFile').bind('change', function () { - var $this = $(this); - var id = '#' + $this.attr('id'); - var status = id + '-status'; - var $status = $(status); - var delete_id = $(id + '-deleteCheckbox'); - var preview_container = $(id + '-previewContainer'); - $status.removeClass('text-success').removeClass('text-danger'); - $(status + ' .goodfile').remove(); - $(status + ' .badfile').remove(); - $(status + ' .previewSize').hide(); - preview_container.hide(); - $(id + '-info').html(''); - var max_size = $this.data('maxsize'); - var total_size = 0; - for (var i = 0; i < this.files.length; i++) { - total_size += this.files[i].size; - $(id + '-info').append('' + htmlEntities(this.files[i].name) + ' (' + formatBytes(this.files[i].size) + ') '); - } - if (total_size > max_size) { - $status.addClass('text-danger').removeClass('help-block').prepend(' ').append(' Upload is ' + formatBytes(total_size) + '.'); - } else { - $status.addClass('text-success').removeClass('help-block').prepend(' '); - var $preview = $(id + '-imagePreview'); - readURL(this, $preview); - $preview.fadeIn(); - preview_container.fadeIn(); - delete_id.hide(); - } - }); -}); -function htmlEntities(str) { - return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); -} - -/** - * Toggle disabled - */ -(function ($) { - $.fn.toggleDisabled = function (callback) { - return this.each(function () { - var disabled, - $this = $(this); - if ($this.attr('disabled')) { - $this.removeAttr('disabled'); - disabled = false; - } else { - $this.attr('disabled', 'disabled'); - disabled = true; - } - if (callback && typeof callback === 'function') { - callback(this, disabled); - } - }); - }; -})(jQuery); - -/** - * Universal Livewire Select2 integration - * - * How to use: - * - * 1. Set the class of your select2 elements to 'livewire-select2'). - * 2. Name your element to match a property in your Livewire component - * 3. Add an attribute called 'data-livewire-component' that points to $this->getId() (via `{{ }}` if you're in a blade, - * or just $this->getId() if not). - */ -document.addEventListener('livewire:init', function () { - $('.livewire-select2').select2(); - $(document).on('select2:select', '.livewire-select2', function (event) { - var target = $(event.target); - if (!event.target.name || !target.data('livewire-component')) { - console.error("You need to set both name (which should match a Livewire property) and data-livewire-component on your Livewire-ed select2 elements!"); - console.error("For data-livewire-component, you probably want to use $this->getId() or {{ $this->getId() }}, as appropriate"); - return false; - } - Livewire.find(target.data('livewire-component')).set(event.target.name, this.options[this.selectedIndex].value); - }); - Livewire.hook('request', function (_ref) { - var succeed = _ref.succeed; - succeed(function () { - queueMicrotask(function () { - $('.livewire-select2').select2(); - }); - }); - }); -}); - -/***/ }), - -/***/ "./resources/assets/js/snipeit_modals.js": -/*!***********************************************!*\ - !*** ./resources/assets/js/snipeit_modals.js ***! - \***********************************************/ -/***/ (() => { - -/* - * - * Snipe-IT Universal Modal support - * - * Enables modal dialogs to create sub-resources throughout Snipe-IT - * - */ - -/* -HOW TO USE - Create a Button looking like this: - New - If you don't have access to Blade commands (like {{ and }}, etc), you can hard-code a URL as the 'href' - data-toggle="modal" - required for Bootstrap Modals -data-target="#createModal" - fixed ID for the modal, do not change -data-select="assigned_to" - What is the *ID* of the select-dropdown that you're going to be adding to, if the modal-create was a success? Be on the lookout for duplicate ID's, it will confuse this library! -class="btn btn-sm btn-primary" - makes it look button-ey, feel free to change :) - -If you want to pass additional variables to the modal (In the Category Create one, for example, you can pass category_id), you can encode them as URL variables in the href - -*/ - -$(function () { - var baseUrl = $('meta[name="baseUrl"]').attr('content'); - //handle modal-add-interstitial calls - var model, select, refreshSelector; - if ($('#createModal').length == 0) { - $('body').append(''); - } - $('#createModal').on("show.bs.modal", function (event) { - var link = $(event.relatedTarget); - model = link.data("dependency"); - select = link.data("select"); - refreshSelector = link.data("refresh"); - $('#createModal').load(link.attr('href'), function () { - // this sets the focus to be the name field - $('#modal-name').focus(); - - //do we need to re-select2 this, after load? Probably. - $('#createModal').find('select.select2').select2(); - // Initialize the ajaxy select2 with images. - // This is a copy/paste of the code from snipeit.js, would be great to only have this in one place. - - $('.js-data-ajax').each(function (i, item) { - var link = $(item); - var endpoint = link.data("endpoint"); - var select = link.data("select"); - link.select2({ - ajax: { - // the baseUrl includes a trailing slash - url: baseUrl + 'api/v1/' + endpoint + '/selectlist', - //WARNING - we're hoping that's defined on the page somewhere... - dataType: 'json', - delay: 250, - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - }, - data: function data(params) { - var data = { - search: params.term, - page: params.page || 1, - assetStatusType: link.data("asset-status-type") - }; - return data; - }, - /*processResults: function (data, params) { - params.page = params.page || 1; - var answer = { - results: data.items, - pagination: { - more: data.pagination.more - } - }; - return answer; - },*/ - cache: true - }, - //escapeMarkup: function (markup) { return markup; }, // let our custom formatter work - templateResult: formatDatalistSafe - //templateSelection: formatDataSelection - }); - }); - }); - }); - $('#createModal').on('click', '#modal-save', function () { - $.ajax({ - type: 'POST', - url: $('.modal-body form').attr('action'), - headers: { - "X-Requested-With": 'XMLHttpRequest', - "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') - }, - data: $('.modal-body form').serialize(), - success: function success(result) { - if (result.status == "error") { - var error_message = ""; - for (var field in result.messages) { - error_message += "
  • Problem(s) with field " + field + ": " + result.messages[field]; - } - $('#modal_error_msg').html(error_message).show(); - return false; - } - var id = result.payload.id; - var name = result.payload.name || result.payload.first_name + " " + result.payload.last_name; - if (!id || !name) { - console.error("Could not find resulting name or ID from modal-create. Name: " + name + ", id: " + id); - return false; - } - $('#createModal').modal('hide'); - $('#createModal').html(""); - var refreshTable = $('#' + refreshSelector); - if (refreshTable.length > 0) { - refreshTable.bootstrapTable('refresh'); - } - - // "select" is the original drop-down menu that someone - // clicked 'add' on to add a new 'thing' - // this code adds the newly created object to that select - var selector = document.getElementById(select); - if (!selector) { - return false; - } - selector.options[selector.length] = new Option(name, id); - selector.selectedIndex = selector.length - 1; - $(selector).trigger("change"); - if (window.fetchCustomFields) { - fetchCustomFields(); - } - }, - error: function error(result) { - msg = result.responseJSON.messages || result.responseJSON.error; - $('#modal_error_msg').html("Server Error: " + msg).show(); - } - }); - }); -}); -function formatDatalistSafe(datalist) { - // console.warn("What in the hell is going on with Select2?!?!!?!?"); - // console.warn($.select2); - if (datalist.loading) { - return $(' Loading...'); - } - var root_div = $("
    "); - var left_pull = $("
    "); - if (datalist.image) { - var inner_div = $("
    "); - /****************************************************************** - * - * We are specifically chosing empty alt-text below, because this - * image conveys no additional information, relative to the text - * that will *always* be there in any select2 list that is in use - * in Snipe-IT. If that changes, we would probably want to change - * some signatures of some functions, but right now, we don't want - * screen readers to say "HP SuperJet 5000, .... picture of HP - * SuperJet 5000..." and so on, for every single row in a list of - * assets or models or whatever. - * - *******************************************************************/ - var img = $(""); - // console.warn("Img is: "); - // console.dir(img); - // console.warn("Strigularly, that's: "); - // console.log(img); - img.attr("src", datalist.image); - inner_div.append(img); - } else { - var inner_div = $("
    "); - } - left_pull.append(inner_div); - root_div.append(left_pull); - var name_div = $("
    "); - name_div.text(datalist.text); - root_div.append(name_div); - var safe_html = root_div.get(0).outerHTML; - var old_html = formatDatalist(datalist); - if (safe_html != old_html) { - // console.log("HTML MISMATCH: "); - // console.log("FormatDatalistSafe: "); - // console.dir(root_div.get(0)); - // console.log(safe_html); - // console.log("FormatDataList: "); - // console.log(old_html); - } - return root_div; -} -function formatDatalist(datalist) { - var loading_markup = ' Loading...'; - if (datalist.loading) { - return loading_markup; - } - var markup = "
    "; - markup += "
    "; - if (datalist.image) { - markup += "
    " + datalist.tex + "
    "; - } else { - markup += "
    "; - } - markup += "
    " + datalist.text + "
    "; - markup += "
    "; - return markup; -} -function formatDataSelection(datalist) { - return datalist.text.replace(/>/g, '>').replace(/ { + +// canvas-confetti v1.9.3 built on 2024-04-30T22:19:17.794Z +!(function (window, module) { +// source content +/* globals Map */ + +(function main(global, module, isWorker, workerSize) { + var canUseWorker = !!( + global.Worker && + global.Blob && + global.Promise && + global.OffscreenCanvas && + global.OffscreenCanvasRenderingContext2D && + global.HTMLCanvasElement && + global.HTMLCanvasElement.prototype.transferControlToOffscreen && + global.URL && + global.URL.createObjectURL); + + var canUsePaths = typeof Path2D === 'function' && typeof DOMMatrix === 'function'; + var canDrawBitmap = (function () { + // this mostly supports ssr + if (!global.OffscreenCanvas) { + return false; + } + + var canvas = new OffscreenCanvas(1, 1); + var ctx = canvas.getContext('2d'); + ctx.fillRect(0, 0, 1, 1); + var bitmap = canvas.transferToImageBitmap(); + + try { + ctx.createPattern(bitmap, 'no-repeat'); + } catch (e) { + return false; + } + + return true; + })(); + + function noop() {} + + // create a promise if it exists, otherwise, just + // call the function directly + function promise(func) { + var ModulePromise = module.exports.Promise; + var Prom = ModulePromise !== void 0 ? ModulePromise : global.Promise; + + if (typeof Prom === 'function') { + return new Prom(func); + } + + func(noop, noop); + + return null; + } + + var bitmapMapper = (function (skipTransform, map) { + // see https://github.com/catdad/canvas-confetti/issues/209 + // creating canvases is actually pretty expensive, so we should create a + // 1:1 map for bitmap:canvas, so that we can animate the confetti in + // a performant manner, but also not store them forever so that we don't + // have a memory leak + return { + transform: function(bitmap) { + if (skipTransform) { + return bitmap; + } + + if (map.has(bitmap)) { + return map.get(bitmap); + } + + var canvas = new OffscreenCanvas(bitmap.width, bitmap.height); + var ctx = canvas.getContext('2d'); + ctx.drawImage(bitmap, 0, 0); + + map.set(bitmap, canvas); + + return canvas; + }, + clear: function () { + map.clear(); + } + }; + })(canDrawBitmap, new Map()); + + var raf = (function () { + var TIME = Math.floor(1000 / 60); + var frame, cancel; + var frames = {}; + var lastFrameTime = 0; + + if (typeof requestAnimationFrame === 'function' && typeof cancelAnimationFrame === 'function') { + frame = function (cb) { + var id = Math.random(); + + frames[id] = requestAnimationFrame(function onFrame(time) { + if (lastFrameTime === time || lastFrameTime + TIME - 1 < time) { + lastFrameTime = time; + delete frames[id]; + + cb(); + } else { + frames[id] = requestAnimationFrame(onFrame); + } + }); + + return id; + }; + cancel = function (id) { + if (frames[id]) { + cancelAnimationFrame(frames[id]); + } + }; + } else { + frame = function (cb) { + return setTimeout(cb, TIME); + }; + cancel = function (timer) { + return clearTimeout(timer); + }; + } + + return { frame: frame, cancel: cancel }; + }()); + + var getWorker = (function () { + var worker; + var prom; + var resolves = {}; + + function decorate(worker) { + function execute(options, callback) { + worker.postMessage({ options: options || {}, callback: callback }); + } + worker.init = function initWorker(canvas) { + var offscreen = canvas.transferControlToOffscreen(); + worker.postMessage({ canvas: offscreen }, [offscreen]); + }; + + worker.fire = function fireWorker(options, size, done) { + if (prom) { + execute(options, null); + return prom; + } + + var id = Math.random().toString(36).slice(2); + + prom = promise(function (resolve) { + function workerDone(msg) { + if (msg.data.callback !== id) { + return; + } + + delete resolves[id]; + worker.removeEventListener('message', workerDone); + + prom = null; + + bitmapMapper.clear(); + + done(); + resolve(); + } + + worker.addEventListener('message', workerDone); + execute(options, id); + + resolves[id] = workerDone.bind(null, { data: { callback: id }}); + }); + + return prom; + }; + + worker.reset = function resetWorker() { + worker.postMessage({ reset: true }); + + for (var id in resolves) { + resolves[id](); + delete resolves[id]; + } + }; + } + + return function () { + if (worker) { + return worker; + } + + if (!isWorker && canUseWorker) { + var code = [ + 'var CONFETTI, SIZE = {}, module = {};', + '(' + main.toString() + ')(this, module, true, SIZE);', + 'onmessage = function(msg) {', + ' if (msg.data.options) {', + ' CONFETTI(msg.data.options).then(function () {', + ' if (msg.data.callback) {', + ' postMessage({ callback: msg.data.callback });', + ' }', + ' });', + ' } else if (msg.data.reset) {', + ' CONFETTI && CONFETTI.reset();', + ' } else if (msg.data.resize) {', + ' SIZE.width = msg.data.resize.width;', + ' SIZE.height = msg.data.resize.height;', + ' } else if (msg.data.canvas) {', + ' SIZE.width = msg.data.canvas.width;', + ' SIZE.height = msg.data.canvas.height;', + ' CONFETTI = module.exports.create(msg.data.canvas);', + ' }', + '}', + ].join('\n'); + try { + worker = new Worker(URL.createObjectURL(new Blob([code]))); + } catch (e) { + // eslint-disable-next-line no-console + typeof console !== undefined && typeof console.warn === 'function' ? console.warn('🎊 Could not load worker', e) : null; + + return null; + } + + decorate(worker); + } + + return worker; + }; + })(); + + var defaults = { + particleCount: 50, + angle: 90, + spread: 45, + startVelocity: 45, + decay: 0.9, + gravity: 1, + drift: 0, + ticks: 200, + x: 0.5, + y: 0.5, + shapes: ['square', 'circle'], + zIndex: 100, + colors: [ + '#26ccff', + '#a25afd', + '#ff5e7e', + '#88ff5a', + '#fcff42', + '#ffa62d', + '#ff36ff' + ], + // probably should be true, but back-compat + disableForReducedMotion: false, + scalar: 1 + }; + + function convert(val, transform) { + return transform ? transform(val) : val; + } + + function isOk(val) { + return !(val === null || val === undefined); + } + + function prop(options, name, transform) { + return convert( + options && isOk(options[name]) ? options[name] : defaults[name], + transform + ); + } + + function onlyPositiveInt(number){ + return number < 0 ? 0 : Math.floor(number); + } + + function randomInt(min, max) { + // [min, max) + return Math.floor(Math.random() * (max - min)) + min; + } + + function toDecimal(str) { + return parseInt(str, 16); + } + + function colorsToRgb(colors) { + return colors.map(hexToRgb); + } + + function hexToRgb(str) { + var val = String(str).replace(/[^0-9a-f]/gi, ''); + + if (val.length < 6) { + val = val[0]+val[0]+val[1]+val[1]+val[2]+val[2]; + } + + return { + r: toDecimal(val.substring(0,2)), + g: toDecimal(val.substring(2,4)), + b: toDecimal(val.substring(4,6)) + }; + } + + function getOrigin(options) { + var origin = prop(options, 'origin', Object); + origin.x = prop(origin, 'x', Number); + origin.y = prop(origin, 'y', Number); + + return origin; + } + + function setCanvasWindowSize(canvas) { + canvas.width = document.documentElement.clientWidth; + canvas.height = document.documentElement.clientHeight; + } + + function setCanvasRectSize(canvas) { + var rect = canvas.getBoundingClientRect(); + canvas.width = rect.width; + canvas.height = rect.height; + } + + function getCanvas(zIndex) { + var canvas = document.createElement('canvas'); + + canvas.style.position = 'fixed'; + canvas.style.top = '0px'; + canvas.style.left = '0px'; + canvas.style.pointerEvents = 'none'; + canvas.style.zIndex = zIndex; + + return canvas; + } + + function ellipse(context, x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) { + context.save(); + context.translate(x, y); + context.rotate(rotation); + context.scale(radiusX, radiusY); + context.arc(0, 0, 1, startAngle, endAngle, antiClockwise); + context.restore(); + } + + function randomPhysics(opts) { + var radAngle = opts.angle * (Math.PI / 180); + var radSpread = opts.spread * (Math.PI / 180); + + return { + x: opts.x, + y: opts.y, + wobble: Math.random() * 10, + wobbleSpeed: Math.min(0.11, Math.random() * 0.1 + 0.05), + velocity: (opts.startVelocity * 0.5) + (Math.random() * opts.startVelocity), + angle2D: -radAngle + ((0.5 * radSpread) - (Math.random() * radSpread)), + tiltAngle: (Math.random() * (0.75 - 0.25) + 0.25) * Math.PI, + color: opts.color, + shape: opts.shape, + tick: 0, + totalTicks: opts.ticks, + decay: opts.decay, + drift: opts.drift, + random: Math.random() + 2, + tiltSin: 0, + tiltCos: 0, + wobbleX: 0, + wobbleY: 0, + gravity: opts.gravity * 3, + ovalScalar: 0.6, + scalar: opts.scalar, + flat: opts.flat + }; + } + + function updateFetti(context, fetti) { + fetti.x += Math.cos(fetti.angle2D) * fetti.velocity + fetti.drift; + fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; + fetti.velocity *= fetti.decay; + + if (fetti.flat) { + fetti.wobble = 0; + fetti.wobbleX = fetti.x + (10 * fetti.scalar); + fetti.wobbleY = fetti.y + (10 * fetti.scalar); + + fetti.tiltSin = 0; + fetti.tiltCos = 0; + fetti.random = 1; + } else { + fetti.wobble += fetti.wobbleSpeed; + fetti.wobbleX = fetti.x + ((10 * fetti.scalar) * Math.cos(fetti.wobble)); + fetti.wobbleY = fetti.y + ((10 * fetti.scalar) * Math.sin(fetti.wobble)); + + fetti.tiltAngle += 0.1; + fetti.tiltSin = Math.sin(fetti.tiltAngle); + fetti.tiltCos = Math.cos(fetti.tiltAngle); + fetti.random = Math.random() + 2; + } + + var progress = (fetti.tick++) / fetti.totalTicks; + + var x1 = fetti.x + (fetti.random * fetti.tiltCos); + var y1 = fetti.y + (fetti.random * fetti.tiltSin); + var x2 = fetti.wobbleX + (fetti.random * fetti.tiltCos); + var y2 = fetti.wobbleY + (fetti.random * fetti.tiltSin); + + context.fillStyle = 'rgba(' + fetti.color.r + ', ' + fetti.color.g + ', ' + fetti.color.b + ', ' + (1 - progress) + ')'; + + context.beginPath(); + + if (canUsePaths && fetti.shape.type === 'path' && typeof fetti.shape.path === 'string' && Array.isArray(fetti.shape.matrix)) { + context.fill(transformPath2D( + fetti.shape.path, + fetti.shape.matrix, + fetti.x, + fetti.y, + Math.abs(x2 - x1) * 0.1, + Math.abs(y2 - y1) * 0.1, + Math.PI / 10 * fetti.wobble + )); + } else if (fetti.shape.type === 'bitmap') { + var rotation = Math.PI / 10 * fetti.wobble; + var scaleX = Math.abs(x2 - x1) * 0.1; + var scaleY = Math.abs(y2 - y1) * 0.1; + var width = fetti.shape.bitmap.width * fetti.scalar; + var height = fetti.shape.bitmap.height * fetti.scalar; + + var matrix = new DOMMatrix([ + Math.cos(rotation) * scaleX, + Math.sin(rotation) * scaleX, + -Math.sin(rotation) * scaleY, + Math.cos(rotation) * scaleY, + fetti.x, + fetti.y + ]); + + // apply the transform matrix from the confetti shape + matrix.multiplySelf(new DOMMatrix(fetti.shape.matrix)); + + var pattern = context.createPattern(bitmapMapper.transform(fetti.shape.bitmap), 'no-repeat'); + pattern.setTransform(matrix); + + context.globalAlpha = (1 - progress); + context.fillStyle = pattern; + context.fillRect( + fetti.x - (width / 2), + fetti.y - (height / 2), + width, + height + ); + context.globalAlpha = 1; + } else if (fetti.shape === 'circle') { + context.ellipse ? + context.ellipse(fetti.x, fetti.y, Math.abs(x2 - x1) * fetti.ovalScalar, Math.abs(y2 - y1) * fetti.ovalScalar, Math.PI / 10 * fetti.wobble, 0, 2 * Math.PI) : + ellipse(context, fetti.x, fetti.y, Math.abs(x2 - x1) * fetti.ovalScalar, Math.abs(y2 - y1) * fetti.ovalScalar, Math.PI / 10 * fetti.wobble, 0, 2 * Math.PI); + } else if (fetti.shape === 'star') { + var rot = Math.PI / 2 * 3; + var innerRadius = 4 * fetti.scalar; + var outerRadius = 8 * fetti.scalar; + var x = fetti.x; + var y = fetti.y; + var spikes = 5; + var step = Math.PI / spikes; + + while (spikes--) { + x = fetti.x + Math.cos(rot) * outerRadius; + y = fetti.y + Math.sin(rot) * outerRadius; + context.lineTo(x, y); + rot += step; + + x = fetti.x + Math.cos(rot) * innerRadius; + y = fetti.y + Math.sin(rot) * innerRadius; + context.lineTo(x, y); + rot += step; + } + } else { + context.moveTo(Math.floor(fetti.x), Math.floor(fetti.y)); + context.lineTo(Math.floor(fetti.wobbleX), Math.floor(y1)); + context.lineTo(Math.floor(x2), Math.floor(y2)); + context.lineTo(Math.floor(x1), Math.floor(fetti.wobbleY)); + } + + context.closePath(); + context.fill(); + + return fetti.tick < fetti.totalTicks; + } + + function animate(canvas, fettis, resizer, size, done) { + var animatingFettis = fettis.slice(); + var context = canvas.getContext('2d'); + var animationFrame; + var destroy; + + var prom = promise(function (resolve) { + function onDone() { + animationFrame = destroy = null; + + context.clearRect(0, 0, size.width, size.height); + bitmapMapper.clear(); + + done(); + resolve(); + } + + function update() { + if (isWorker && !(size.width === workerSize.width && size.height === workerSize.height)) { + size.width = canvas.width = workerSize.width; + size.height = canvas.height = workerSize.height; + } + + if (!size.width && !size.height) { + resizer(canvas); + size.width = canvas.width; + size.height = canvas.height; + } + + context.clearRect(0, 0, size.width, size.height); + + animatingFettis = animatingFettis.filter(function (fetti) { + return updateFetti(context, fetti); + }); + + if (animatingFettis.length) { + animationFrame = raf.frame(update); + } else { + onDone(); + } + } + + animationFrame = raf.frame(update); + destroy = onDone; + }); + + return { + addFettis: function (fettis) { + animatingFettis = animatingFettis.concat(fettis); + + return prom; + }, + canvas: canvas, + promise: prom, + reset: function () { + if (animationFrame) { + raf.cancel(animationFrame); + } + + if (destroy) { + destroy(); + } + } + }; + } + + function confettiCannon(canvas, globalOpts) { + var isLibCanvas = !canvas; + var allowResize = !!prop(globalOpts || {}, 'resize'); + var hasResizeEventRegistered = false; + var globalDisableForReducedMotion = prop(globalOpts, 'disableForReducedMotion', Boolean); + var shouldUseWorker = canUseWorker && !!prop(globalOpts || {}, 'useWorker'); + var worker = shouldUseWorker ? getWorker() : null; + var resizer = isLibCanvas ? setCanvasWindowSize : setCanvasRectSize; + var initialized = (canvas && worker) ? !!canvas.__confetti_initialized : false; + var preferLessMotion = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion)').matches; + var animationObj; + + function fireLocal(options, size, done) { + var particleCount = prop(options, 'particleCount', onlyPositiveInt); + var angle = prop(options, 'angle', Number); + var spread = prop(options, 'spread', Number); + var startVelocity = prop(options, 'startVelocity', Number); + var decay = prop(options, 'decay', Number); + var gravity = prop(options, 'gravity', Number); + var drift = prop(options, 'drift', Number); + var colors = prop(options, 'colors', colorsToRgb); + var ticks = prop(options, 'ticks', Number); + var shapes = prop(options, 'shapes'); + var scalar = prop(options, 'scalar'); + var flat = !!prop(options, 'flat'); + var origin = getOrigin(options); + + var temp = particleCount; + var fettis = []; + + var startX = canvas.width * origin.x; + var startY = canvas.height * origin.y; + + while (temp--) { + fettis.push( + randomPhysics({ + x: startX, + y: startY, + angle: angle, + spread: spread, + startVelocity: startVelocity, + color: colors[temp % colors.length], + shape: shapes[randomInt(0, shapes.length)], + ticks: ticks, + decay: decay, + gravity: gravity, + drift: drift, + scalar: scalar, + flat: flat + }) + ); + } + + // if we have a previous canvas already animating, + // add to it + if (animationObj) { + return animationObj.addFettis(fettis); + } + + animationObj = animate(canvas, fettis, resizer, size , done); + + return animationObj.promise; + } + + function fire(options) { + var disableForReducedMotion = globalDisableForReducedMotion || prop(options, 'disableForReducedMotion', Boolean); + var zIndex = prop(options, 'zIndex', Number); + + if (disableForReducedMotion && preferLessMotion) { + return promise(function (resolve) { + resolve(); + }); + } + + if (isLibCanvas && animationObj) { + // use existing canvas from in-progress animation + canvas = animationObj.canvas; + } else if (isLibCanvas && !canvas) { + // create and initialize a new canvas + canvas = getCanvas(zIndex); + document.body.appendChild(canvas); + } + + if (allowResize && !initialized) { + // initialize the size of a user-supplied canvas + resizer(canvas); + } + + var size = { + width: canvas.width, + height: canvas.height + }; + + if (worker && !initialized) { + worker.init(canvas); + } + + initialized = true; + + if (worker) { + canvas.__confetti_initialized = true; + } + + function onResize() { + if (worker) { + // TODO this really shouldn't be immediate, because it is expensive + var obj = { + getBoundingClientRect: function () { + if (!isLibCanvas) { + return canvas.getBoundingClientRect(); + } + } + }; + + resizer(obj); + + worker.postMessage({ + resize: { + width: obj.width, + height: obj.height + } + }); + return; + } + + // don't actually query the size here, since this + // can execute frequently and rapidly + size.width = size.height = null; + } + + function done() { + animationObj = null; + + if (allowResize) { + hasResizeEventRegistered = false; + global.removeEventListener('resize', onResize); + } + + if (isLibCanvas && canvas) { + if (document.body.contains(canvas)) { + document.body.removeChild(canvas); + } + canvas = null; + initialized = false; + } + } + + if (allowResize && !hasResizeEventRegistered) { + hasResizeEventRegistered = true; + global.addEventListener('resize', onResize, false); + } + + if (worker) { + return worker.fire(options, size, done); + } + + return fireLocal(options, size, done); + } + + fire.reset = function () { + if (worker) { + worker.reset(); + } + + if (animationObj) { + animationObj.reset(); + } + }; + + return fire; + } + + // Make default export lazy to defer worker creation until called. + var defaultFire; + function getDefaultFire() { + if (!defaultFire) { + defaultFire = confettiCannon(null, { useWorker: true, resize: true }); + } + return defaultFire; + } + + function transformPath2D(pathString, pathMatrix, x, y, scaleX, scaleY, rotation) { + var path2d = new Path2D(pathString); + + var t1 = new Path2D(); + t1.addPath(path2d, new DOMMatrix(pathMatrix)); + + var t2 = new Path2D(); + // see https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix/DOMMatrix + t2.addPath(t1, new DOMMatrix([ + Math.cos(rotation) * scaleX, + Math.sin(rotation) * scaleX, + -Math.sin(rotation) * scaleY, + Math.cos(rotation) * scaleY, + x, + y + ])); + + return t2; + } + + function shapeFromPath(pathData) { + if (!canUsePaths) { + throw new Error('path confetti are not supported in this browser'); + } + + var path, matrix; + + if (typeof pathData === 'string') { + path = pathData; + } else { + path = pathData.path; + matrix = pathData.matrix; + } + + var path2d = new Path2D(path); + var tempCanvas = document.createElement('canvas'); + var tempCtx = tempCanvas.getContext('2d'); + + if (!matrix) { + // attempt to figure out the width of the path, up to 1000x1000 + var maxSize = 1000; + var minX = maxSize; + var minY = maxSize; + var maxX = 0; + var maxY = 0; + var width, height; + + // do some line skipping... this is faster than checking + // every pixel and will be mostly still correct + for (var x = 0; x < maxSize; x += 2) { + for (var y = 0; y < maxSize; y += 2) { + if (tempCtx.isPointInPath(path2d, x, y, 'nonzero')) { + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + } + + width = maxX - minX; + height = maxY - minY; + + var maxDesiredSize = 10; + var scale = Math.min(maxDesiredSize/width, maxDesiredSize/height); + + matrix = [ + scale, 0, 0, scale, + -Math.round((width/2) + minX) * scale, + -Math.round((height/2) + minY) * scale + ]; + } + + return { + type: 'path', + path: path, + matrix: matrix + }; + } + + function shapeFromText(textData) { + var text, + scalar = 1, + color = '#000000', + // see https://nolanlawson.com/2022/04/08/the-struggle-of-using-native-emoji-on-the-web/ + fontFamily = '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", "EmojiOne Color", "Android Emoji", "Twemoji Mozilla", "system emoji", sans-serif'; + + if (typeof textData === 'string') { + text = textData; + } else { + text = textData.text; + scalar = 'scalar' in textData ? textData.scalar : scalar; + fontFamily = 'fontFamily' in textData ? textData.fontFamily : fontFamily; + color = 'color' in textData ? textData.color : color; + } + + // all other confetti are 10 pixels, + // so this pixel size is the de-facto 100% scale confetti + var fontSize = 10 * scalar; + var font = '' + fontSize + 'px ' + fontFamily; + + var canvas = new OffscreenCanvas(fontSize, fontSize); + var ctx = canvas.getContext('2d'); + + ctx.font = font; + var size = ctx.measureText(text); + var width = Math.ceil(size.actualBoundingBoxRight + size.actualBoundingBoxLeft); + var height = Math.ceil(size.actualBoundingBoxAscent + size.actualBoundingBoxDescent); + + var padding = 2; + var x = size.actualBoundingBoxLeft + padding; + var y = size.actualBoundingBoxAscent + padding; + width += padding + padding; + height += padding + padding; + + canvas = new OffscreenCanvas(width, height); + ctx = canvas.getContext('2d'); + ctx.font = font; + ctx.fillStyle = color; + + ctx.fillText(text, x, y); + + var scale = 1 / scalar; + + return { + type: 'bitmap', + // TODO these probably need to be transfered for workers + bitmap: canvas.transferToImageBitmap(), + matrix: [scale, 0, 0, scale, -width * scale / 2, -height * scale / 2] + }; + } + + module.exports = function() { + return getDefaultFire().apply(this, arguments); + }; + module.exports.reset = function() { + getDefaultFire().reset(); + }; + module.exports.create = confettiCannon; + module.exports.shapeFromPath = shapeFromPath; + module.exports.shapeFromText = shapeFromText; +}((function () { + if (typeof window !== 'undefined') { + return window; + } + + if (typeof self !== 'undefined') { + return self; + } + + return this || {}; +})(), module, false)); + +// end source content + + window.confetti = module.exports; +}(window, {})); + + /***/ }), /***/ "./node_modules/jquery-ui/ui/version.js": @@ -3179,7 +3264,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ $.ui = $.ui || {}; -return $.ui.version = "1.13.3"; +return $.ui.version = "1.14.1"; } ); @@ -3193,7 +3278,7 @@ return $.ui.version = "1.13.3"; /***/ ((module, exports, __webpack_require__) => { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! - * jQuery UI Widget 1.13.3 + * jQuery UI Widget 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -3249,6 +3334,9 @@ $.widget = function( name, base, prototype ) { var namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; + if ( name === "__proto__" || name === "constructor" ) { + return $.error( "Invalid widget name: " + name ); + } var fullName = namespace + "-" + name; if ( !prototype ) { @@ -32046,10 +32134,807 @@ var __WEBPACK_AMD_DEFINE_RESULT__;/** /***/ }), -/***/ "./resources/assets/less/skins/skin-contrast.less": -/*!********************************************************!*\ - !*** ./resources/assets/less/skins/skin-contrast.less ***! - \********************************************************/ +/***/ "./resources/assets/js/bootstrap.js": +/*!******************************************!*\ + !*** ./resources/assets/js/bootstrap.js ***! + \******************************************/ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { + +window._ = __webpack_require__(/*! lodash */ "./node_modules/lodash/lodash.js"); + +/** + * We'll load jQuery and the Bootstrap jQuery plugin which provides support + * for JavaScript based Bootstrap features such as modals and tabs. This + * code may be modified to fit the specific needs of your application. + */ +//window.$ = window.jQuery = require('jquery'); + +/** + * jQuery UI is loaded here and then the tooltip is assigned another funtion name + * This resolves the issue of jquery-ui & bootstrap tooltip conflict + */ +__webpack_require__(/*! jquery-ui */ "./node_modules/jquery-ui/ui/widget.js"); +jQuery.fn.uitooltip = jQuery.fn.tooltip; + +/** + * Load boostrap + */ +__webpack_require__(/*! bootstrap-less */ "./node_modules/bootstrap-less/js/bootstrap.js"); + +// require('admin-lte'); + +// require('chart.js'); + +// require('jquery-form-validator'); //says something about dependency +/** + * Echo exposes an expressive API for subscribing to channels and listening + * for events that are broadcast by Laravel. Echo and event broadcasting + * allows your team to easily build robust real-time web applications. + */ + +// import Echo from "laravel-echo" + +// window.Echo = new Echo({ +// broadcaster: 'pusher', +// key: 'your-pusher-key' +// }); + +/***/ }), + +/***/ "./resources/assets/js/snipeit.js": +/*!****************************************!*\ + !*** ./resources/assets/js/snipeit.js ***! + \****************************************/ +/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => { + +// var jQuery = require('jquery'); +// window.jQuery = jQuery +// window.$ = jQuery + +__webpack_require__(/*! ./bootstrap */ "./resources/assets/js/bootstrap.js"); + +/** + * Module containing core application logic. + * @param {jQuery} $ Insulated jQuery object + * @param {JSON} settings Insulated `window.snipeit.settings` object. + * @return {IIFE} Immediately invoked. Returns self. + */ + +lineOptions = { + legend: { + position: "bottom" + }, + scales: { + yAxes: [{ + ticks: { + fontColor: "rgba(0,0,0,0.5)", + fontStyle: "bold", + beginAtZero: true, + maxTicksLimit: 5, + padding: 20 + }, + gridLines: { + drawTicks: false, + display: false + } + }], + xAxes: [{ + gridLines: { + zeroLineColor: "transparent" + }, + ticks: { + padding: 20, + fontColor: "rgba(0,0,0,0.5)", + fontStyle: "bold" + } + }] + } +}; +pieOptions = { + //Boolean - Whether we should show a stroke on each segment + segmentShowStroke: true, + //String - The colour of each segment stroke + segmentStrokeColor: "#fff", + //Number - The width of each segment stroke + segmentStrokeWidth: 1, + //Number - The percentage of the chart that we cut out of the middle + percentageInnerCutout: 50, + // This is 0 for Pie charts + //Number - Amount of animation steps + animationSteps: 100, + //String - Animation easing effect + animationEasing: "easeOutBounce", + //Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + //Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false, + //Boolean - whether to make the chart responsive to window resizing + responsive: true, + // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container + maintainAspectRatio: false, + //String - A legend template + legendTemplate: "
      -legend\"><% for (var i=0; i
    • " + "" + "<%if(segments[i].label){%><%=segments[i].label%><%}%> foo
    • <%}%>
    ", + //String - A tooltip template + tooltipTemplate: "<%=value %> <%=label%> " +}; + +//----------------- +//- END PIE CHART - +//----------------- + +var baseUrl = $('meta[name="baseUrl"]').attr('content'); +$(function () { + var $el = $('table'); + + // confirm restore modal + + $el.on('click', '.restore-asset', function (evnt) { + var $context = $(this); + var $restoreConfirmModal = $('#restoreConfirmModal'); + var href = $context.attr('href'); + var message = $context.attr('data-content'); + var title = $context.attr('data-title'); + $('#confirmModalLabel').text(title); + $restoreConfirmModal.find('.modal-body').text(message); + $('#restoreForm').attr('action', href); + $restoreConfirmModal.modal({ + show: true + }); + return false; + }); + + // confirm delete modal + + $el.on('click', '.delete-asset', function (evnt) { + var $context = $(this); + var $dataConfirmModal = $('#dataConfirmModal'); + var href = $context.attr('href'); + var message = $context.attr('data-content'); + var title = $context.attr('data-title'); + $('#myModalLabel').text(title); + $dataConfirmModal.find('.modal-body').text(message); + $('#deleteForm').attr('action', href); + $dataConfirmModal.modal({ + show: true + }); + return false; + }); + + /* + * Slideout help menu + */ + $('.slideout-menu-toggle').on('click', function (event) { + event.preventDefault(); + // create menu variables + var slideoutMenu = $('.slideout-menu'); + var slideoutMenuWidth = $('.slideout-menu').width(); + + // toggle open class + slideoutMenu.toggleClass("open"); + + // slide menu + if (slideoutMenu.hasClass("open")) { + slideoutMenu.show(); + slideoutMenu.animate({ + right: "0px" + }); + } else { + slideoutMenu.animate({ + right: -slideoutMenuWidth + }, "-350px"); + slideoutMenu.fadeOut(); + } + }); + + /* + * Select2 + */ + + $('select.select2:not(".select2-hidden-accessible")').each(function (i, obj) { + { + $(obj).select2(); + } + }); + + // $('.datepicker').datepicker(); + // var datepicker = $.fn.datepicker.noConflict(); // return $.fn.datepicker to previously assigned value + // $.fn.bootstrapDP = datepicker; + // $('.datepicker').datepicker(); + + // Crazy select2 rich dropdowns with images! + $('.js-data-ajax').each(function (i, item) { + var link = $(item); + var endpoint = link.data("endpoint"); + var select = link.data("select"); + link.select2({ + /** + * Adds an empty placeholder, allowing every select2 instance to be cleared. + * This placeholder can be overridden with the "data-placeholder" attribute. + */ + placeholder: '', + allowClear: true, + language: $('meta[name="language"]').attr('content'), + dir: $('meta[name="language-direction"]').attr('content'), + ajax: { + // the baseUrl includes a trailing slash + url: baseUrl + 'api/v1/' + endpoint + '/selectlist', + dataType: 'json', + delay: 250, + headers: { + "X-Requested-With": 'XMLHttpRequest', + "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') + }, + data: function data(params) { + var data = { + search: params.term, + page: params.page || 1, + assetStatusType: link.data("asset-status-type"), + companyId: link.data("company-id") + }; + return data; + }, + /* processResults: function (data, params) { + params.page = params.page || 1; + var answer = { + results: data.items, + pagination: { + more: data.pagination.more + } + }; + return answer; + }, */ + cache: true + }, + //escapeMarkup: function (markup) { return markup; }, // let our custom formatter work + templateResult: formatDatalistSafe + //templateSelection: formatDataSelection + }); + }); + function getSelect2Value(element) { + // if the passed object is not a jquery object, assuming 'element' is a selector + if (!(element instanceof jQuery)) element = $(element); + var select = element.data("select2"); + + // There's two different locations where the select2-generated input element can be. + searchElement = select.dropdown.$search || select.$container.find(".select2-search__field"); + var value = searchElement.val(); + return value; + } + $(".select2-hidden-accessible").on('select2:selecting', function (e) { + var data = e.params.args.data; + var isMouseUp = false; + var element = $(this); + var value = getSelect2Value(element); + if (e.params.args.originalEvent) isMouseUp = e.params.args.originalEvent.type == "mouseup"; + + // if selected item does not match typed text, do not allow it to pass - force close for ajax. + if (!isMouseUp) { + if (value.toLowerCase() && data.text.toLowerCase().indexOf(value) < 0) { + e.preventDefault(); + element.select2('close'); + + // if it does match, we set a flag in the event (which gets passed to subsequent events), telling it not to worry about the ajax + } else if (value.toLowerCase() && data.text.toLowerCase().indexOf(value) > -1) { + e.params.args.noForceAjax = true; + } + } + }); + $(".select2-hidden-accessible").on('select2:closing', function (e) { + var element = $(this); + var value = getSelect2Value(element); + var noForceAjax = false; + var isMouseUp = false; + if (e.params.args.originalSelect2Event) noForceAjax = e.params.args.originalSelect2Event.noForceAjax; + if (e.params.args.originalEvent) isMouseUp = e.params.args.originalEvent.type == "mouseup"; + if (value && !noForceAjax && !isMouseUp) { + var endpoint = element.data("endpoint"); + var assetStatusType = element.data("asset-status-type"); + $.ajax({ + url: baseUrl + 'api/v1/' + endpoint + '/selectlist?search=' + value + '&page=1' + (assetStatusType ? '&assetStatusType=' + assetStatusType : ''), + dataType: 'json', + headers: { + "X-Requested-With": 'XMLHttpRequest', + "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') + } + }).done(function (response) { + var currentlySelected = element.select2('data').map(function (x) { + return +x.id; + }).filter(function (x) { + return x !== 0; + }); + + // makes sure we're not selecting the same thing twice for multiples + var filteredResponse = response.results.filter(function (item) { + return currentlySelected.indexOf(+item.id) < 0; + }); + var first = currentlySelected.length > 0 ? filteredResponse[0] : response.results[0]; + if (first && first.id) { + first.selected = true; + if ($("option[value='" + first.id + "']", element).length < 1) { + var option = new Option(first.text, first.id, true, true); + element.append(option); + } else { + var isMultiple = element.attr("multiple") == "multiple"; + element.val(isMultiple ? element.val().concat(first.id) : element.val(first.id)); + } + element.trigger('change'); + element.trigger({ + type: 'select2:select', + params: { + data: first + } + }); + } + }); + } + }); + function formatDatalist(datalist) { + var loading_markup = ' Loading...'; + if (datalist.loading) { + return loading_markup; + } + var markup = '
    '; + markup += '
    '; + if (datalist.image) { + markup += "
    " + datalist.text + "
    "; + } else { + markup += '
    '; + } + markup += "
    " + datalist.text + "
    "; + markup += "
    "; + return markup; + } + function formatDatalistSafe(datalist) { + // console.warn("What in the hell is going on with Select2?!?!!?!?"); + // console.warn($.select2); + if (datalist.loading) { + return $(' Loading...'); + } + var root_div = $("
    "); + var left_pull = $("
    "); + if (datalist.image) { + var inner_div = $("
    "); + /****************************************************************** + * + * We are specifically chosing empty alt-text below, because this + * image conveys no additional information, relative to the text + * that will *always* be there in any select2 list that is in use + * in Snipe-IT. If that changes, we would probably want to change + * some signatures of some functions, but right now, we don't want + * screen readers to say "HP SuperJet 5000, .... picture of HP + * SuperJet 5000..." and so on, for every single row in a list of + * assets or models or whatever. + * + *******************************************************************/ + var img = $(""); + // console.warn("Img is: "); + // console.dir(img); + // console.warn("Strigularly, that's: "); + // console.log(img); + img.attr("src", datalist.image); + inner_div.append(img); + } else { + var inner_div = $("
    "); + } + left_pull.append(inner_div); + root_div.append(left_pull); + var name_div = $("
    "); + name_div.text(datalist.text); + root_div.append(name_div); + var safe_html = root_div.get(0).outerHTML; + var old_html = formatDatalist(datalist); + if (safe_html != old_html) { + //console.log("HTML MISMATCH: "); + //console.log("FormatDatalistSafe: "); + // console.dir(root_div.get(0)); + //console.log(safe_html); + //console.log("FormatDataList: "); + //console.log(old_html); + } + return root_div; + } + function formatDataSelection(datalist) { + // 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, '>').replace(/Click me + $('a[data-toggle="tab"]').click(function (e) { + var href = $(this).attr("href"); + history.pushState(null, null, href); + e.preventDefault(); + $('a[href="' + $(this).attr('href') + '"]').tab('show'); + }); + + // ------------------------------------------------ + // End Deep Linking for Bootstrap tabs + // ------------------------------------------------ + + // Image preview + function readURL(input, $preview) { + if (input.files && input.files[0]) { + var reader = new FileReader(); + reader.onload = function (e) { + $preview.attr('src', e.target.result); + }; + reader.readAsDataURL(input.files[0]); + } + } + function formatBytes(bytes) { + if (bytes < 1024) return bytes + " Bytes";else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " KB";else if (bytes < 1073741824) return (bytes / 1048576).toFixed(2) + " MB";else return (bytes / 1073741824).toFixed(2) + " GB"; + } + + // File size validation + $('.js-uploadFile').bind('change', function () { + var $this = $(this); + var id = '#' + $this.attr('id'); + var status = id + '-status'; + var $status = $(status); + var delete_id = $(id + '-deleteCheckbox'); + var preview_container = $(id + '-previewContainer'); + $status.removeClass('text-success').removeClass('text-danger'); + $(status + ' .goodfile').remove(); + $(status + ' .badfile').remove(); + $(status + ' .previewSize').hide(); + preview_container.hide(); + $(id + '-info').html(''); + var max_size = $this.data('maxsize'); + var total_size = 0; + for (var i = 0; i < this.files.length; i++) { + total_size += this.files[i].size; + $(id + '-info').append('' + htmlEntities(this.files[i].name) + ' (' + formatBytes(this.files[i].size) + ') '); + } + if (total_size > max_size) { + $status.addClass('text-danger').removeClass('help-block').prepend(' ').append(' Upload is ' + formatBytes(total_size) + '.'); + } else { + $status.addClass('text-success').removeClass('help-block').prepend(' '); + var $preview = $(id + '-imagePreview'); + readURL(this, $preview); + $preview.fadeIn(); + preview_container.fadeIn(); + delete_id.hide(); + } + }); +}); +function htmlEntities(str) { + return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} + +/** + * Toggle disabled + */ +(function ($) { + $.fn.toggleDisabled = function (callback) { + return this.each(function () { + var disabled, + $this = $(this); + if ($this.attr('disabled')) { + $this.removeAttr('disabled'); + disabled = false; + } else { + $this.attr('disabled', 'disabled'); + disabled = true; + } + if (callback && typeof callback === 'function') { + callback(this, disabled); + } + }); + }; +})(jQuery); + +/** + * Universal Livewire Select2 integration + * + * How to use: + * + * 1. Set the class of your select2 elements to 'livewire-select2'). + * 2. Name your element to match a property in your Livewire component + * 3. Add an attribute called 'data-livewire-component' that points to $this->getId() (via `{{ }}` if you're in a blade, + * or just $this->getId() if not). + */ +document.addEventListener('livewire:init', function () { + $('.livewire-select2').select2(); + $(document).on('select2:select', '.livewire-select2', function (event) { + var target = $(event.target); + if (!event.target.name || !target.data('livewire-component')) { + console.error("You need to set both name (which should match a Livewire property) and data-livewire-component on your Livewire-ed select2 elements!"); + console.error("For data-livewire-component, you probably want to use $this->getId() or {{ $this->getId() }}, as appropriate"); + return false; + } + Livewire.find(target.data('livewire-component')).set(event.target.name, this.options[this.selectedIndex].value); + }); + Livewire.hook('request', function (_ref) { + var succeed = _ref.succeed; + succeed(function () { + queueMicrotask(function () { + $('.livewire-select2').select2(); + }); + }); + }); +}); + +/***/ }), + +/***/ "./resources/assets/js/snipeit_modals.js": +/*!***********************************************!*\ + !*** ./resources/assets/js/snipeit_modals.js ***! + \***********************************************/ +/***/ (() => { + +/* + * + * Snipe-IT Universal Modal support + * + * Enables modal dialogs to create sub-resources throughout Snipe-IT + * + */ + +/* +HOW TO USE + Create a Button looking like this: + New + If you don't have access to Blade commands (like {{ and }}, etc), you can hard-code a URL as the 'href' + data-toggle="modal" - required for Bootstrap Modals +data-target="#createModal" - fixed ID for the modal, do not change +data-select="assigned_to" - What is the *ID* of the select-dropdown that you're going to be adding to, if the modal-create was a success? Be on the lookout for duplicate ID's, it will confuse this library! +class="btn btn-sm btn-primary" - makes it look button-ey, feel free to change :) + +If you want to pass additional variables to the modal (In the Category Create one, for example, you can pass category_id), you can encode them as URL variables in the href + +*/ + +$(function () { + var baseUrl = $('meta[name="baseUrl"]').attr('content'); + //handle modal-add-interstitial calls + var model, select, refreshSelector; + if ($('#createModal').length == 0) { + $('body').append(''); + } + $('#createModal').on("show.bs.modal", function (event) { + var link = $(event.relatedTarget); + model = link.data("dependency"); + select = link.data("select"); + refreshSelector = link.data("refresh"); + $('#createModal').load(link.attr('href'), function () { + // this sets the focus to be the name field + $('#modal-name').focus(); + + //do we need to re-select2 this, after load? Probably. + $('#createModal').find('select.select2').select2(); + // Initialize the ajaxy select2 with images. + // This is a copy/paste of the code from snipeit.js, would be great to only have this in one place. + + $('.js-data-ajax').each(function (i, item) { + var link = $(item); + var endpoint = link.data("endpoint"); + var select = link.data("select"); + link.select2({ + ajax: { + // the baseUrl includes a trailing slash + url: baseUrl + 'api/v1/' + endpoint + '/selectlist', + //WARNING - we're hoping that's defined on the page somewhere... + dataType: 'json', + delay: 250, + headers: { + "X-Requested-With": 'XMLHttpRequest', + "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') + }, + data: function data(params) { + var data = { + search: params.term, + page: params.page || 1, + assetStatusType: link.data("asset-status-type") + }; + return data; + }, + /*processResults: function (data, params) { + params.page = params.page || 1; + var answer = { + results: data.items, + pagination: { + more: data.pagination.more + } + }; + return answer; + },*/ + cache: true + }, + //escapeMarkup: function (markup) { return markup; }, // let our custom formatter work + templateResult: formatDatalistSafe + //templateSelection: formatDataSelection + }); + }); + }); + }); + $('#createModal').on('click', '#modal-save', function () { + $.ajax({ + type: 'POST', + url: $('.modal-body form').attr('action'), + headers: { + "X-Requested-With": 'XMLHttpRequest', + "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr('content') + }, + data: $('.modal-body form').serialize(), + success: function success(result) { + if (result.status == "error") { + var error_message = ""; + for (var field in result.messages) { + error_message += "
  • Problem(s) with field " + field + ": " + result.messages[field]; + } + $('#modal_error_msg').html(error_message).show(); + return false; + } + var id = result.payload.id; + var name = result.payload.name || result.payload.first_name + " " + result.payload.last_name; + if (!id || !name) { + console.error("Could not find resulting name or ID from modal-create. Name: " + name + ", id: " + id); + return false; + } + $('#createModal').modal('hide'); + $('#createModal').html(""); + var refreshTable = $('#' + refreshSelector); + if (refreshTable.length > 0) { + refreshTable.bootstrapTable('refresh'); + } + + // "select" is the original drop-down menu that someone + // clicked 'add' on to add a new 'thing' + // this code adds the newly created object to that select + var selector = document.getElementById(select); + if (!selector) { + return false; + } + selector.options[selector.length] = new Option(name, id); + selector.selectedIndex = selector.length - 1; + $(selector).trigger("change"); + if (window.fetchCustomFields) { + fetchCustomFields(); + } + }, + error: function error(result) { + msg = result.responseJSON.messages || result.responseJSON.error; + $('#modal_error_msg').html("Server Error: " + msg).show(); + } + }); + }); +}); +function formatDatalistSafe(datalist) { + // console.warn("What in the hell is going on with Select2?!?!!?!?"); + // console.warn($.select2); + if (datalist.loading) { + return $(' Loading...'); + } + var root_div = $("
    "); + var left_pull = $("
    "); + if (datalist.image) { + var inner_div = $("
    "); + /****************************************************************** + * + * We are specifically chosing empty alt-text below, because this + * image conveys no additional information, relative to the text + * that will *always* be there in any select2 list that is in use + * in Snipe-IT. If that changes, we would probably want to change + * some signatures of some functions, but right now, we don't want + * screen readers to say "HP SuperJet 5000, .... picture of HP + * SuperJet 5000..." and so on, for every single row in a list of + * assets or models or whatever. + * + *******************************************************************/ + var img = $(""); + // console.warn("Img is: "); + // console.dir(img); + // console.warn("Strigularly, that's: "); + // console.log(img); + img.attr("src", datalist.image); + inner_div.append(img); + } else { + var inner_div = $("
    "); + } + left_pull.append(inner_div); + root_div.append(left_pull); + var name_div = $("
    "); + name_div.text(datalist.text); + root_div.append(name_div); + var safe_html = root_div.get(0).outerHTML; + var old_html = formatDatalist(datalist); + if (safe_html != old_html) { + // console.log("HTML MISMATCH: "); + // console.log("FormatDatalistSafe: "); + // console.dir(root_div.get(0)); + // console.log(safe_html); + // console.log("FormatDataList: "); + // console.log(old_html); + } + return root_div; +} +function formatDatalist(datalist) { + var loading_markup = ' Loading...'; + if (datalist.loading) { + return loading_markup; + } + var markup = "
    "; + markup += "
    "; + if (datalist.image) { + markup += "
    " + datalist.tex + "
    "; + } else { + markup += "
    "; + } + markup += "
    " + datalist.text + "
    "; + markup += "
    "; + return markup; +} +function formatDataSelection(datalist) { + return datalist.text.replace(/>/g, '>').replace(/ { "use strict"; @@ -32059,35 +32944,22 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/***/ "./resources/assets/less/skins/skin-green.less": +/***/ "./resources/assets/less/overrides.less": +/*!**********************************************!*\ + !*** ./resources/assets/less/overrides.less ***! + \**********************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +// extracted by mini-css-extract-plugin + + +/***/ }), + +/***/ "./resources/assets/less/skins/_all-skins.less": /*!*****************************************************!*\ - !*** ./resources/assets/less/skins/skin-green.less ***! - \*****************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -// extracted by mini-css-extract-plugin - - -/***/ }), - -/***/ "./resources/assets/less/skins/skin-green-dark.less": -/*!**********************************************************!*\ - !*** ./resources/assets/less/skins/skin-green-dark.less ***! - \**********************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -// extracted by mini-css-extract-plugin - - -/***/ }), - -/***/ "./resources/assets/less/skins/skin-black.less": -/*!*****************************************************!*\ - !*** ./resources/assets/less/skins/skin-black.less ***! + !*** ./resources/assets/less/skins/_all-skins.less ***! \*****************************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { @@ -32111,62 +32983,10 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/***/ "./resources/assets/less/skins/skin-red-dark.less": -/*!********************************************************!*\ - !*** ./resources/assets/less/skins/skin-red-dark.less ***! - \********************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -// extracted by mini-css-extract-plugin - - -/***/ }), - -/***/ "./resources/assets/less/skins/skin-purple.less": -/*!******************************************************!*\ - !*** ./resources/assets/less/skins/skin-purple.less ***! - \******************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -// extracted by mini-css-extract-plugin - - -/***/ }), - -/***/ "./resources/assets/less/skins/skin-purple-dark.less": -/*!***********************************************************!*\ - !*** ./resources/assets/less/skins/skin-purple-dark.less ***! - \***********************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -// extracted by mini-css-extract-plugin - - -/***/ }), - -/***/ "./resources/assets/less/skins/skin-yellow.less": -/*!******************************************************!*\ - !*** ./resources/assets/less/skins/skin-yellow.less ***! - \******************************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -// extracted by mini-css-extract-plugin - - -/***/ }), - -/***/ "./resources/assets/less/skins/skin-yellow-dark.less": -/*!***********************************************************!*\ - !*** ./resources/assets/less/skins/skin-yellow-dark.less ***! - \***********************************************************/ +/***/ "./resources/assets/less/skins/skin-black.less": +/*!*****************************************************!*\ + !*** ./resources/assets/less/skins/skin-black.less ***! + \*****************************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -32187,6 +33007,58 @@ __webpack_require__.r(__webpack_exports__); // extracted by mini-css-extract-plugin +/***/ }), + +/***/ "./resources/assets/less/skins/skin-blue.less": +/*!****************************************************!*\ + !*** ./resources/assets/less/skins/skin-blue.less ***! + \****************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +// extracted by mini-css-extract-plugin + + +/***/ }), + +/***/ "./resources/assets/less/skins/skin-contrast.less": +/*!********************************************************!*\ + !*** ./resources/assets/less/skins/skin-contrast.less ***! + \********************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +// extracted by mini-css-extract-plugin + + +/***/ }), + +/***/ "./resources/assets/less/skins/skin-green-dark.less": +/*!**********************************************************!*\ + !*** ./resources/assets/less/skins/skin-green-dark.less ***! + \**********************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +// extracted by mini-css-extract-plugin + + +/***/ }), + +/***/ "./resources/assets/less/skins/skin-green.less": +/*!*****************************************************!*\ + !*** ./resources/assets/less/skins/skin-green.less ***! + \*****************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +// extracted by mini-css-extract-plugin + + /***/ }), /***/ "./resources/assets/less/skins/skin-orange-dark.less": @@ -32215,10 +33087,10 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/***/ "./node_modules/admin-lte/build/less/AdminLTE.less": -/*!*********************************************************!*\ - !*** ./node_modules/admin-lte/build/less/AdminLTE.less ***! - \*********************************************************/ +/***/ "./resources/assets/less/skins/skin-purple-dark.less": +/*!***********************************************************!*\ + !*** ./resources/assets/less/skins/skin-purple-dark.less ***! + \***********************************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -32228,10 +33100,10 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/***/ "./resources/assets/less/app.less": -/*!****************************************!*\ - !*** ./resources/assets/less/app.less ***! - \****************************************/ +/***/ "./resources/assets/less/skins/skin-purple.less": +/*!******************************************************!*\ + !*** ./resources/assets/less/skins/skin-purple.less ***! + \******************************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -32241,23 +33113,10 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/***/ "./resources/assets/less/overrides.less": -/*!**********************************************!*\ - !*** ./resources/assets/less/overrides.less ***! - \**********************************************/ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -// extracted by mini-css-extract-plugin - - -/***/ }), - -/***/ "./resources/assets/less/skins/skin-blue.less": -/*!****************************************************!*\ - !*** ./resources/assets/less/skins/skin-blue.less ***! - \****************************************************/ +/***/ "./resources/assets/less/skins/skin-red-dark.less": +/*!********************************************************!*\ + !*** ./resources/assets/less/skins/skin-red-dark.less ***! + \********************************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; @@ -32278,6 +33137,32 @@ __webpack_require__.r(__webpack_exports__); // extracted by mini-css-extract-plugin +/***/ }), + +/***/ "./resources/assets/less/skins/skin-yellow-dark.less": +/*!***********************************************************!*\ + !*** ./resources/assets/less/skins/skin-yellow-dark.less ***! + \***********************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +// extracted by mini-css-extract-plugin + + +/***/ }), + +/***/ "./resources/assets/less/skins/skin-yellow.less": +/*!******************************************************!*\ + !*** ./resources/assets/less/skins/skin-yellow.less ***! + \******************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +// extracted by mini-css-extract-plugin + + /***/ }) /******/ }); @@ -32391,24 +33276,25 @@ __webpack_require__.r(__webpack_exports__); /******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded /******/ var installedChunks = { /******/ "/js/build/app": 0, -/******/ "css/dist/skins/skin-red": 0, -/******/ "css/dist/skins/skin-blue": 0, +/******/ "css/dist/skins/skin-black-dark": 0, +/******/ "css/dist/skins/_all-skins": 0, /******/ "css/build/overrides": 0, /******/ "css/build/app": 0, /******/ "css/build/AdminLTE": 0, +/******/ "css/dist/skins/skin-yellow": 0, +/******/ "css/dist/skins/skin-yellow-dark": 0, +/******/ "css/dist/skins/skin-red": 0, +/******/ "css/dist/skins/skin-red-dark": 0, +/******/ "css/dist/skins/skin-purple": 0, +/******/ "css/dist/skins/skin-purple-dark": 0, /******/ "css/dist/skins/skin-orange": 0, /******/ "css/dist/skins/skin-orange-dark": 0, -/******/ "css/dist/skins/skin-blue-dark": 0, -/******/ "css/dist/skins/skin-yellow-dark": 0, -/******/ "css/dist/skins/skin-yellow": 0, -/******/ "css/dist/skins/skin-purple-dark": 0, -/******/ "css/dist/skins/skin-purple": 0, -/******/ "css/dist/skins/skin-red-dark": 0, -/******/ "css/dist/skins/skin-black-dark": 0, -/******/ "css/dist/skins/skin-black": 0, -/******/ "css/dist/skins/skin-green-dark": 0, /******/ "css/dist/skins/skin-green": 0, -/******/ "css/dist/skins/skin-contrast": 0 +/******/ "css/dist/skins/skin-green-dark": 0, +/******/ "css/dist/skins/skin-contrast": 0, +/******/ "css/dist/skins/skin-blue": 0, +/******/ "css/dist/skins/skin-blue-dark": 0, +/******/ "css/dist/skins/skin-black": 0 /******/ }; /******/ /******/ // no chunk on demand loading @@ -32458,26 +33344,28 @@ __webpack_require__.r(__webpack_exports__); /******/ // startup /******/ // Load entry module and return exports /******/ // This entry module depends on other loaded chunks and execution need to be delayed -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/js/snipeit.js"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/js/snipeit_modals.js"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./node_modules/admin-lte/build/less/AdminLTE.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/app.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/overrides.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-blue.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-red.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-contrast.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-green.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-green-dark.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-black.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-black-dark.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-red-dark.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-purple.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-purple-dark.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-yellow.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-yellow-dark.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-blue-dark.less"))) -/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-orange-dark.less"))) -/******/ var __webpack_exports__ = __webpack_require__.O(undefined, ["css/dist/skins/skin-red","css/dist/skins/skin-blue","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-blue-dark","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-yellow","css/dist/skins/skin-purple-dark","css/dist/skins/skin-purple","css/dist/skins/skin-red-dark","css/dist/skins/skin-black-dark","css/dist/skins/skin-black","css/dist/skins/skin-green-dark","css/dist/skins/skin-green","css/dist/skins/skin-contrast"], () => (__webpack_require__("./resources/assets/less/skins/skin-orange.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/js/snipeit.js"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/js/snipeit_modals.js"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./node_modules/canvas-confetti/dist/confetti.browser.js"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./node_modules/admin-lte/build/less/AdminLTE.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/app.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/overrides.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/_all-skins.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-black-dark.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-black.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-blue-dark.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-blue.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-contrast.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-green-dark.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-green.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-orange-dark.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-orange.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-purple-dark.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-purple.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-red-dark.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-red.less"))) +/******/ __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-yellow-dark.less"))) +/******/ var __webpack_exports__ = __webpack_require__.O(undefined, ["css/dist/skins/skin-black-dark","css/dist/skins/_all-skins","css/build/overrides","css/build/app","css/build/AdminLTE","css/dist/skins/skin-yellow","css/dist/skins/skin-yellow-dark","css/dist/skins/skin-red","css/dist/skins/skin-red-dark","css/dist/skins/skin-purple","css/dist/skins/skin-purple-dark","css/dist/skins/skin-orange","css/dist/skins/skin-orange-dark","css/dist/skins/skin-green","css/dist/skins/skin-green-dark","css/dist/skins/skin-contrast","css/dist/skins/skin-blue","css/dist/skins/skin-blue-dark","css/dist/skins/skin-black"], () => (__webpack_require__("./resources/assets/less/skins/skin-yellow.less"))) /******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); /******/ /******/ })() diff --git a/public/js/build/app.js.LICENSE.txt b/public/js/build/app.js.LICENSE.txt index cb3342c839..5b8cf0d08b 100644 --- a/public/js/build/app.js.LICENSE.txt +++ b/public/js/build/app.js.LICENSE.txt @@ -30,7 +30,7 @@ */ /*! - * jQuery UI Widget 1.13.3 + * jQuery UI Widget 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors diff --git a/public/js/build/vendor.js b/public/js/build/vendor.js index 03b90c5529..102490976b 100644 --- a/public/js/build/vendor.js +++ b/public/js/build/vendor.js @@ -12707,14 +12707,14 @@ return Tether; })); -/*! jQuery UI - v1.13.3 - 2024-04-26 +/*! jQuery UI - v1.14.1 - 2024-10-30 * https://jqueryui.com * Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js * Copyright OpenJS Foundation and other contributors; Licensed MIT */ ( function( factory ) { "use strict"; - + if ( typeof define === "function" && define.amd ) { // AMD. Register as an anonymous module. @@ -12729,11 +12729,11 @@ return Tether; $.ui = $.ui || {}; -var version = $.ui.version = "1.13.3"; +var version = $.ui.version = "1.14.1"; /*! - * jQuery UI Widget 1.13.3 + * jQuery UI Widget 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -12776,6 +12776,9 @@ $.widget = function( name, base, prototype ) { var namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; + if ( name === "__proto__" || name === "constructor" ) { + return $.error( "Invalid widget name: " + name ); + } var fullName = namespace + "-" + name; if ( !prototype ) { @@ -13475,7 +13478,7 @@ var widget = $.widget; /*! - * jQuery UI Position 1.13.3 + * jQuery UI Position 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -13972,7 +13975,7 @@ var position = $.ui.position; /*! - * jQuery UI :data 1.13.3 + * jQuery UI :data 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -13987,21 +13990,15 @@ var position = $.ui.position; var data = $.extend( $.expr.pseudos, { - data: $.expr.createPseudo ? - $.expr.createPseudo( function( dataName ) { - return function( elem ) { - return !!$.data( elem, dataName ); - }; - } ) : - - // Support: jQuery <1.8 - function( elem, i, match ) { - return !!$.data( elem, match[ 3 ] ); - } + data: $.expr.createPseudo( function( dataName ) { + return function( elem ) { + return !!$.data( elem, dataName ); + }; + } ) } ); /*! - * jQuery UI Disable Selection 1.13.3 + * jQuery UI Disable Selection 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -14044,18 +14041,17 @@ var jQuery = $; /*! - * jQuery Color Animations v2.2.0 + * jQuery Color Animations v3.0.0 * https://github.com/jquery/jquery-color * * Copyright OpenJS Foundation and other contributors * Released under the MIT license. * https://jquery.org/license * - * Date: Sun May 10 09:02:36 2020 +0200 + * Date: Wed May 15 16:49:44 2024 +0200 */ - var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " + "borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor", @@ -14180,10 +14176,6 @@ var jQuery = $; floor: true } }, - support = color.support = {}, - - // element for support tests - supportElem = jQuery( "

    " )[ 0 ], // colors = jQuery.Color.names colors, @@ -14191,10 +14183,6 @@ var jQuery = $; // local aliases of functions called often each = jQuery.each; -// determine rgba support immediately -supportElem.style.cssText = "background-color:rgba(1,1,1,.5)"; -support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1; - // define cache name and alpha properties // for rgba and hsla spaces each( spaces, function( spaceName, space ) { @@ -14232,12 +14220,6 @@ function clamp( value, prop, allowEmpty ) { // ~~ is an short way of doing floor for positive numbers value = type.floor ? ~~value : parseFloat( value ); - // IE will pass in empty strings as value for alpha, - // which will hit this case - if ( isNaN( value ) ) { - return prop.def; - } - if ( type.mod ) { // we add mod before modding to make sure that negatives values @@ -14350,7 +14332,10 @@ color.fn = jQuery.extend( color.prototype, { } ); // everything defined but alpha? - if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) { + if ( inst[ cache ] && jQuery.inArray( + null, + inst[ cache ].slice( 0, 3 ) + ) < 0 ) { // use the default of 1 if ( inst[ cache ][ 3 ] == null ) { @@ -14462,7 +14447,7 @@ color.fn = jQuery.extend( color.prototype, { prefix = "rgb("; } - return prefix + rgba.join() + ")"; + return prefix + rgba.join( ", " ) + ")"; }, toHslaString: function() { var prefix = "hsla(", @@ -14482,7 +14467,7 @@ color.fn = jQuery.extend( color.prototype, { hsla.pop(); prefix = "hsl("; } - return prefix + hsla.join() + ")"; + return prefix + hsla.join( ", " ) + ")"; }, toHexString: function( includeAlpha ) { var rgba = this._rgba.slice(), @@ -14495,12 +14480,11 @@ color.fn = jQuery.extend( color.prototype, { return "#" + jQuery.map( rgba, function( v ) { // default to 0 when nulls exist - v = ( v || 0 ).toString( 16 ); - return v.length === 1 ? "0" + v : v; + return ( "0" + ( v || 0 ).toString( 16 ) ).substr( -2 ); } ).join( "" ); }, toString: function() { - return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString(); + return this.toRgbaString(); } } ); color.fn.parse.prototype = color.fn; @@ -14667,37 +14651,15 @@ color.hook = function( hook ) { each( hooks, function( _i, hook ) { jQuery.cssHooks[ hook ] = { set: function( elem, value ) { - var parsed, curElem, - backgroundColor = ""; + var parsed; - if ( value !== "transparent" && ( getType( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) { + if ( value !== "transparent" && + ( getType( value ) !== "string" || + ( parsed = stringParse( value ) ) ) ) { value = color( parsed || value ); - if ( !support.rgba && value._rgba[ 3 ] !== 1 ) { - curElem = hook === "backgroundColor" ? elem.parentNode : elem; - while ( - ( backgroundColor === "" || backgroundColor === "transparent" ) && - curElem && curElem.style - ) { - try { - backgroundColor = jQuery.css( curElem, "backgroundColor" ); - curElem = curElem.parentNode; - } catch ( e ) { - } - } - - value = value.blend( backgroundColor && backgroundColor !== "transparent" ? - backgroundColor : - "_default" ); - } - value = value.toRgbaString(); } - try { - elem.style[ hook ] = value; - } catch ( e ) { - - // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit' - } + elem.style[ hook ] = value; } }; jQuery.fx.step[ hook ] = function( fx ) { @@ -14756,7 +14718,7 @@ colors = jQuery.Color.names = { /*! - * jQuery UI Effects 1.13.3 + * jQuery UI Effects 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -14819,26 +14781,14 @@ function camelCase( string ) { function getElementStyles( elem ) { var key, len, - style = elem.ownerDocument.defaultView ? - elem.ownerDocument.defaultView.getComputedStyle( elem, null ) : - elem.currentStyle, + style = elem.ownerDocument.defaultView.getComputedStyle( elem ), styles = {}; - if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) { - len = style.length; - while ( len-- ) { - key = style[ len ]; - if ( typeof style[ key ] === "string" ) { - styles[ camelCase( key ) ] = style[ key ]; - } - } - - // Support: Opera, IE <9 - } else { - for ( key in style ) { - if ( typeof style[ key ] === "string" ) { - styles[ key ] = style[ key ]; - } + len = style.length; + while ( len-- ) { + key = style[ len ]; + if ( typeof style[ key ] === "string" ) { + styles[ camelCase( key ) ] = style[ key ]; } } @@ -14863,15 +14813,6 @@ function styleDifference( oldStyle, newStyle ) { return diff; } -// Support: jQuery <1.8 -if ( !$.fn.addBack ) { - $.fn.addBack = function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - }; -} - $.effects.animateClass = function( value, duration, easing, callback ) { var o = $.speed( duration, easing, callback ); @@ -15011,7 +14952,7 @@ if ( $.expr && $.expr.pseudos && $.expr.pseudos.animated ) { } )( $.expr.pseudos.animated ); } -if ( $.uiBackCompat !== false ) { +if ( $.uiBackCompat === true ) { $.extend( $.effects, { // Saves a set of properties in a data storage @@ -15140,7 +15081,7 @@ if ( $.uiBackCompat !== false ) { } $.extend( $.effects, { - version: "1.13.3", + version: "1.14.1", define: function( name, mode, effect ) { if ( !effect ) { @@ -15497,7 +15438,7 @@ $.fn.extend( { // as toggle can be either show or hide depending on element state args.mode = modes.shift(); - if ( $.uiBackCompat !== false && !defaultMode ) { + if ( $.uiBackCompat === true && !defaultMode ) { if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { // Call the core method to track "olddisplay" properly @@ -15708,7 +15649,7 @@ var effect = $.effects; /*! - * jQuery UI Effects Blind 1.13.3 + * jQuery UI Effects Blind 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15763,7 +15704,7 @@ var effectsEffectBlind = $.effects.define( "blind", "hide", function( options, d /*! - * jQuery UI Effects Bounce 1.13.3 + * jQuery UI Effects Bounce 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15858,7 +15799,7 @@ var effectsEffectBounce = $.effects.define( "bounce", function( options, done ) /*! - * jQuery UI Effects Clip 1.13.3 + * jQuery UI Effects Clip 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15908,7 +15849,7 @@ var effectsEffectClip = $.effects.define( "clip", "hide", function( options, don /*! - * jQuery UI Effects Drop 1.13.3 + * jQuery UI Effects Drop 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15962,7 +15903,7 @@ var effectsEffectDrop = $.effects.define( "drop", "hide", function( options, don /*! - * jQuery UI Effects Explode 1.13.3 + * jQuery UI Effects Explode 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16058,7 +15999,7 @@ var effectsEffectExplode = $.effects.define( "explode", "hide", function( option /*! - * jQuery UI Effects Fade 1.13.3 + * jQuery UI Effects Fade 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16090,7 +16031,7 @@ var effectsEffectFade = $.effects.define( "fade", "toggle", function( options, d /*! - * jQuery UI Effects Fold 1.13.3 + * jQuery UI Effects Fold 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16164,7 +16105,7 @@ var effectsEffectFold = $.effects.define( "fold", "hide", function( options, don /*! - * jQuery UI Effects Highlight 1.13.3 + * jQuery UI Effects Highlight 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16206,7 +16147,7 @@ var effectsEffectHighlight = $.effects.define( "highlight", "show", function( op /*! - * jQuery UI Effects Size 1.13.3 + * jQuery UI Effects Size 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16384,7 +16325,7 @@ var effectsEffectSize = $.effects.define( "size", function( options, done ) { /*! - * jQuery UI Effects Scale 1.13.3 + * jQuery UI Effects Scale 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16424,7 +16365,7 @@ var effectsEffectScale = $.effects.define( "scale", function( options, done ) { /*! - * jQuery UI Effects Puff 1.13.3 + * jQuery UI Effects Puff 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16450,7 +16391,7 @@ var effectsEffectPuff = $.effects.define( "puff", "hide", function( options, don /*! - * jQuery UI Effects Pulsate 1.13.3 + * jQuery UI Effects Pulsate 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16499,7 +16440,7 @@ var effectsEffectPulsate = $.effects.define( "pulsate", "show", function( option /*! - * jQuery UI Effects Shake 1.13.3 + * jQuery UI Effects Shake 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16558,7 +16499,7 @@ var effectsEffectShake = $.effects.define( "shake", function( options, done ) { /*! - * jQuery UI Effects Slide 1.13.3 + * jQuery UI Effects Slide 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16619,7 +16560,7 @@ var effectsEffectSlide = $.effects.define( "slide", "show", function( options, d /*! - * jQuery UI Effects Transfer 1.13.3 + * jQuery UI Effects Transfer 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16635,7 +16576,7 @@ var effectsEffectSlide = $.effects.define( "slide", "show", function( options, d var effect; -if ( $.uiBackCompat !== false ) { +if ( $.uiBackCompat === true ) { effect = $.effects.define( "transfer", function( options, done ) { $( this ).transfer( options, done ); } ); @@ -16644,7 +16585,7 @@ var effectsEffectTransfer = effect; /*! - * jQuery UI Focusable 1.13.3 + * jQuery UI Focusable 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16693,20 +16634,10 @@ $.ui.focusable = function( element, hasTabindex ) { focusableIfVisible = hasTabindex; } - return focusableIfVisible && $( element ).is( ":visible" ) && visible( $( element ) ); + return focusableIfVisible && $( element ).is( ":visible" ) && + $( element ).css( "visibility" ) === "visible"; }; -// Support: IE 8 only -// IE 8 doesn't resolve inherit to visible/hidden for computed values -function visible( element ) { - var visibility = element.css( "visibility" ); - while ( visibility === "inherit" ) { - element = element.parent(); - visibility = element.css( "visibility" ); - } - return visibility === "visible"; -} - $.extend( $.expr.pseudos, { focusable: function( element ) { return $.ui.focusable( element, $.attr( element, "tabindex" ) != null ); @@ -16716,17 +16647,8 @@ $.extend( $.expr.pseudos, { var focusable = $.ui.focusable; - -// Support: IE8 Only -// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop -// with a string, so we need to find the proper form. -var form = $.fn._form = function() { - return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form ); -}; - - /*! - * jQuery UI Form Reset Mixin 1.13.3 + * jQuery UI Form Reset Mixin 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16754,7 +16676,7 @@ var formResetMixin = $.ui.formResetMixin = { }, _bindFormResetHandler: function() { - this.form = this.element._form(); + this.form = $( this.element.prop( "form" ) ); if ( !this.form.length ) { return; } @@ -16788,7 +16710,7 @@ var formResetMixin = $.ui.formResetMixin = { /*! - * jQuery UI Support for jQuery core 1.8.x and newer 1.13.3 + * jQuery UI Legacy jQuery Core patches 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16797,50 +16719,17 @@ var formResetMixin = $.ui.formResetMixin = { * */ -//>>label: jQuery 1.8+ Support +//>>label: Legacy jQuery Core patches //>>group: Core -//>>description: Support version 1.8.x and newer of jQuery core +//>>description: Backport `.even()`, `.odd()` and `$.escapeSelector` to older jQuery Core versions (deprecated) -// Support: jQuery 1.9.x or older -// $.expr[ ":" ] is deprecated. -if ( !$.expr.pseudos ) { - $.expr.pseudos = $.expr[ ":" ]; -} - -// Support: jQuery 1.11.x or older -// $.unique has been renamed to $.uniqueSort -if ( !$.uniqueSort ) { - $.uniqueSort = $.unique; -} - // Support: jQuery 2.2.x or older. // This method has been defined in jQuery 3.0.0. // Code from https://github.com/jquery/jquery/blob/e539bac79e666bba95bba86d690b4e609dca2286/src/selector/escapeSelector.js if ( !$.escapeSelector ) { - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; - - var fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }; - - $.escapeSelector = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); + $.escapeSelector = function( id ) { + return CSS.escape( id + "" ); }; } @@ -16863,7 +16752,7 @@ if ( !$.fn.even || !$.fn.odd ) { ; /*! - * jQuery UI Keycode 1.13.3 + * jQuery UI Keycode 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16898,7 +16787,7 @@ var keycode = $.ui.keyCode = { /*! - * jQuery UI Labels 1.13.3 + * jQuery UI Labels 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16924,9 +16813,8 @@ var labels = $.fn.labels = function() { return this.pushStack( this[ 0 ].labels ); } - // Support: IE <= 11, FF <= 37, Android <= 2.3 only - // Above browsers do not support control.labels. Everything below is to support them - // as well as document fragments. control.labels does not work on document fragments + // If `control.labels` is empty - e.g. inside of document fragments - find + // the labels manually labels = this.eq( 0 ).parents( "label" ); // Look for the label based on the id @@ -16941,7 +16829,7 @@ var labels = $.fn.labels = function() { ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() ); // Create a selector for the label based on the id - selector = "label[for='" + $.escapeSelector( id ) + "']"; + selector = "label[for='" + CSS.escape( id ) + "']"; labels = labels.add( ancestors.find( selector ).addBack( selector ) ); @@ -16953,7 +16841,7 @@ var labels = $.fn.labels = function() { /*! - * jQuery UI Scroll Parent 1.13.3 + * jQuery UI Scroll Parent 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16987,7 +16875,7 @@ var scrollParent = $.fn.scrollParent = function( includeHidden ) { /*! - * jQuery UI Tabbable 1.13.3 + * jQuery UI Tabbable 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -17011,7 +16899,7 @@ var tabbable = $.extend( $.expr.pseudos, { /*! - * jQuery UI Unique ID 1.13.3 + * jQuery UI Unique ID 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -17049,7 +16937,7 @@ var uniqueId = $.fn.extend( { /*! - * jQuery UI Accordion 1.13.3 + * jQuery UI Accordion 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -17070,7 +16958,7 @@ var uniqueId = $.fn.extend( { var widgetsAccordion = $.widget( "ui.accordion", { - version: "1.13.3", + version: "1.14.1", options: { active: 0, animate: {}, @@ -17082,7 +16970,17 @@ var widgetsAccordion = $.widget( "ui.accordion", { collapsible: false, event: "click", header: function( elem ) { - return elem.find( "> li > :first-child" ).add( elem.find( "> :not(li)" ).even() ); + return elem + .find( "> li > :first-child" ) + .add( + elem.find( "> :not(li)" ) + + // Support: jQuery <3.5 only + // We could use `.even()` but that's unavailable in older jQuery. + .filter( function( i ) { + return i % 2 === 0; + } ) + ); }, heightStyle: "auto", icons: { @@ -17217,13 +17115,7 @@ var widgetsAccordion = $.widget( "ui.accordion", { this._super( value ); this.element.attr( "aria-disabled", value ); - - // Support: IE8 Only - // #5332 / #6059 - opacity doesn't cascade to positioned elements in IE - // so we need to add the disabled class to the headers and panels this._toggleClass( null, "ui-state-disabled", !!value ); - this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled", - !!value ); }, _keydown: function( event ) { @@ -17641,47 +17533,13 @@ var widgetsAccordion = $.widget( "ui.accordion", { this._removeClass( prev, "ui-accordion-header-active" ) ._addClass( prev, "ui-accordion-header-collapsed" ); - // Work around for rendering bug in IE (#5421) - if ( toHide.length ) { - toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; - } this._trigger( "activate", null, data ); } } ); - -var safeActiveElement = $.ui.safeActiveElement = function( document ) { - var activeElement; - - // Support: IE 9 only - // IE9 throws an "Unspecified error" accessing document.activeElement from an diff --git a/resources/views/partials/locations-bulk-actions.blade.php b/resources/views/partials/locations-bulk-actions.blade.php index 228a68dfba..b19f4d3299 100644 --- a/resources/views/partials/locations-bulk-actions.blade.php +++ b/resources/views/partials/locations-bulk-actions.blade.php @@ -1,11 +1,13 @@ @can('delete', \App\Models\Location::class)

    - {{ Form::open([ - 'method' => 'POST', - 'route' => ['locations.bulkdelete.show'], - 'class' => 'form-inline', - 'id' => 'locationsBulkForm']) }} - +
    + @csrf
    - - - - - - -
    - @endcan -@endif - {{ Form::close() }} -
    \ No newline at end of file + @if (request('status')!='deleted') + + + @csrf + +
    + + + +
    + + @endif + +
    +@endcan diff --git a/resources/views/reports/accessories.blade.php b/resources/views/reports/accessories.blade.php index 997e5fedff..9f6fd0e1a4 100644 --- a/resources/views/reports/accessories.blade.php +++ b/resources/views/reports/accessories.blade.php @@ -13,13 +13,13 @@
    -
    -
    - -
    diff --git a/resources/views/reports/activity.blade.php b/resources/views/reports/activity.blade.php index a7e6f9f15b..b9687beca5 100644 --- a/resources/views/reports/activity.blade.php +++ b/resources/views/reports/activity.blade.php @@ -7,10 +7,13 @@ @stop @section('header_right') - {{ Form::open(['method' => 'post', 'class' => 'form-horizontal']) }} +
    {{csrf_field()}} - - {{ Form::close() }} + +
    @stop {{-- Page content --}} @@ -22,10 +25,13 @@
    - - - - - - - - - - - - - - - - - - -
    {{ trans('general.date') }}{{ trans('general.admin') }}{{ trans('general.action') }}{{ trans('general.file_name') }}{{ trans('general.type') }}{{ trans('admin/hardware/table.serial') }}{{ trans('general.item') }}{{ trans('general.to') }}{{ trans('general.notes') }}{{ trans('general.changed') }}{{ trans('admin/settings/general.login_ip') }}{{ trans('admin/settings/general.login_user_agent') }}{{ trans('general.action_source') }}
    diff --git a/resources/views/reports/asset.blade.php b/resources/views/reports/asset.blade.php index b70d69efe9..90ef276cfc 100644 --- a/resources/views/reports/asset.blade.php +++ b/resources/views/reports/asset.blade.php @@ -22,6 +22,9 @@
    - -
    -
    {{ trans('general.location') }} - +
    {{ trans('admin/hardware/form.default_location') }} {{ trans('admin/asset_maintenances/table.is_warranty') }}{{ trans('general.admin') }}{{ trans('general.created_by') }} {{ trans('admin/asset_maintenances/form.notes') }}
    - -
  • diff --git a/resources/views/reports/audit.blade.php b/resources/views/reports/audit.blade.php index 3d16b0714b..93d9f86a0a 100644 --- a/resources/views/reports/audit.blade.php +++ b/resources/views/reports/audit.blade.php @@ -19,6 +19,8 @@ data-pagination="true" data-id-table="auditReport" data-search="true" + data-search-highlight="true" + data-show-print="true" data-side-pagination="server" data-show-columns="true" data-show-export="true" @@ -35,14 +37,13 @@ {{ trans('admin/hardware/table.image') }} - {{ trans('general.audit') }} - {{ trans('general.admin') }} - {{ trans('general.item') }} - {{ trans('general.location') }} + {{ trans('general.created_by') }} + {{ trans('general.item') }} + {{ trans('general.location') }} + {{ trans('general.last_audit') }} {{ trans('general.next_audit_date') }} {{ trans('general.days_to_next_audit') }} - - {{ trans('general.notes') }} + {{ trans('general.notes') }} diff --git a/resources/views/reports/custom.blade.php b/resources/views/reports/custom.blade.php index ebc212b5c7..28d8ab0318 100644 --- a/resources/views/reports/custom.blade.php +++ b/resources/views/reports/custom.blade.php @@ -2,13 +2,30 @@ {{-- Page Title --}} @section('title') -{{ trans('general.custom_report') }} + @if (request()->routeIs('report-templates.edit')) + {{ trans('general.update') }} {{ $template->name }} + @elseif(request()->routeIs('report-templates.show')) + {{ trans('general.custom_report') }}: {{ $template->name }} + @else + {{ trans('general.custom_report') }} + @endif @parent @stop @section('header_right') - - {{ trans('general.back') }} + @if (request()->routeIs('report-templates.edit')) + + {{ trans('general.back') }} + + @elseif (request()->routeIs('report-templates.show')) + + {{ trans('general.back') }} + + @else + + {{ trans('general.back') }} + + @endif @stop @@ -16,174 +33,213 @@ @section('content')
    -
    +
    - {{ Form::open(['method' => 'post', 'class' => 'form-horizontal']) }} - {{csrf_field()}} +
    + {{csrf_field()}} -
    -
    -

    {{ trans('general.customize_report') }}

    +
    +
    + @if (request()->routeIs('reports/custom') || request()->routeIs('report-templates.show')) +

    + {{ trans('general.customize_report') }} +

    + + @endif + + @if (request()->routeIs('report-templates.edit')) +
    +
    +
    + +
    + +
    + {!! $errors->first('name', '') !!} +
    +
    +
    + @endif +
    -
    +
    -