diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..4a484913 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitattributes b/.gitattributes index a175aa0e..f5b72bd3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,51 +1,51 @@ -# Set default behaviour, in case users don't have core.autocrlf set. -#* text=input - -# Explicitly declare text files we want to always be normalized and converted -# to native line endings on checkout. -*.php text -*.txt text -*.ini text -*.dist text -*.twig text -*.html text -*.htm text -*.json text -*.log text -*.yml text -*.rb text -*.csv text -*.xml text -*.ci text -*.js text -*.css text -*.cache text -*.meta text -*.phar text -app/console text -console text -vendors text -.gitignore text -behat text -Capfile text -deps text -*.lock text -*.md text -*.markdown text -.htaccess text -*.sh text -*.pl text -*.cgi text - -# Denote all files that are truly binary and should not be modified. -*.png binary -*.gif binary -*.jpg binary -*.db binary -*.jar binary -*.ico binary -.gitkeep binary -*.gz binary -*.zip binary -*.7z binary +# Set default behaviour, in case users don't have core.autocrlf set. +#* text=input + +# Explicitly declare text files we want to always be normalized and converted +# to native line endings on checkout. +*.php text +*.txt text +*.ini text +*.dist text +*.twig text +*.html text +*.htm text +*.json text +*.log text +*.yml text +*.rb text +*.csv text +*.xml text +*.ci text +*.js text +*.css text +*.cache text +*.meta text +*.phar text +app/console text +console text +vendors text +.gitignore text +behat text +Capfile text +deps text +*.lock text +*.md text +*.markdown text +.htaccess text +*.sh text +*.pl text +*.cgi text + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.gif binary +*.jpg binary +*.db binary +*.jar binary +*.ico binary +.gitkeep binary +*.gz binary +*.zip binary +*.7z binary *.ttf binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index b1746bcd..0758dd7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ test/ sync.ffs_db -node_modules \ No newline at end of file +node_modules +_Test/ +app/api/test.php +/.project \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 136eae53..4b1146cf 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,16 +1,16 @@ -{ - "bitwise": true, - "browser": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "immed": true, - "jquery": true, - "latedef": true, - "newcap": true, - "noarg": true, - "node": true, - "strict": false, - "trailing": true -} +{ + "bitwise": true, + "browser": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "esnext": true, + "immed": true, + "jquery": true, + "latedef": true, + "newcap": true, + "noarg": true, + "node": true, + "strict": false, + "trailing": true +} diff --git a/Gruntfile.js b/Gruntfile.js index 25f939a3..9ea1d32a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,6 +22,9 @@ module.exports = function(grunt) { files: { 'assets/css/runeui.css': [ 'assets/less/runeui.less' + ], + 'assets/css/musicapristina.css': [ + 'assets/less/musicapristina.less' ] } } diff --git a/LICENSE b/LICENSE index 425179c4..a8014105 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,674 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/} - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - RuneAudio Copyright (C) 2013-2014 RuneAudio Team - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -{http://www.gnu.org/licenses/}. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -{http://www.gnu.org/philosophy/why-not-lgpl.html}. +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/} + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + RuneAudio Copyright (C) 2013-2014 RuneAudio Team + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +{http://www.gnu.org/licenses/}. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +{http://www.gnu.org/philosophy/why-not-lgpl.html}. diff --git a/_TEST/index.html b/_TEST/index.html new file mode 100644 index 00000000..b5394da7 --- /dev/null +++ b/_TEST/index.html @@ -0,0 +1,56 @@ + + + + + + + + + + +
+
+ + + + + + \ No newline at end of file diff --git a/app/api/audio_ctl.php b/app/api/audio_ctl.php new file mode 100644 index 00000000..17213317 --- /dev/null +++ b/app/api/audio_ctl.php @@ -0,0 +1,103 @@ +. + * + * file: audio_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ + +// Check for POST +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // get the data that was POSTed + $postData = file_get_contents("php://input"); + // convert to an associative array + $json = json_decode($postData, true); + + // switch audio output + if (isset($json['ao'])) { + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdcfg', 'action' => 'switchao', 'args' => $json['ao'])); + $template->XXX = "ao"; + } + + // update MPD configuration + if (isset($json['conf'])) { + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdcfg', 'action' => 'update', 'args' => $json['conf'])); + $template->YYY = "conf"; + } + + if (isset($json['orionprofile'])) { + // submit worker job + $redis->get('orionprofile') == $json['orionprofile'] || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'orionprofile', 'args' => $json['orionprofile'])); + $template->ZZZ = "orion"; + } + + // waitSyWrk($redis, $jobID); + +} else { + + $template->conf = $redis->hGetAll('mpdconf'); + $template->orionprofile = $redis->get('orionprofile'); + + $i2smodule = $redis->get('i2smodule'); + $acards = $redis->hGetAll('acards'); + foreach ($acards as $card => $data) { + $acard_data = json_decode($data); + // debug + // echo $card."\n"; + // print_r($acard_data); + if ($i2smodule !== 'none') { + $acards_details = $redis->hGet('acards_details', $i2smodule); + } else { + $acards_details = $redis->hGet('acards_details', $card); + } + if (!empty($acards_details)) { + $details = json_decode($acards_details); + // debug + // echo "acards_details\n"; + // print_r($details); + if ($details->sysname === $card) { + if ($details->type === 'integrated_sub') { + $sub_interfaces = $redis->sMembers($card); + foreach ($sub_interfaces as $int) { + $sub_int_details = json_decode($int); + // TODO !!! check + $audio_cards[] = $sub_int_details; + } + } + if ($details->extlabel !== 'none') { + $acard_data->extlabel = $details->extlabel; + } + } + } + $audio_cards[] = $acard_data; + } + osort($audio_cards, 'extlabel'); + $template->acards = $audio_cards; + $template->ao = $redis->get('ao'); +} \ No newline at end of file diff --git a/app/api/coverart_ctl.php b/app/api/coverart_ctl.php new file mode 100644 index 00000000..f844057c --- /dev/null +++ b/app/api/coverart_ctl.php @@ -0,0 +1,208 @@ +. + * + * file: file: app/coverart_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ +// direct output bypass template system +$tplfile = 0; +runelog("\n--------------------- coverart (start) ---------------------"); +// turn off output buffering +ob_implicit_flush(0); +// --------------------- MPD --------------------- +if ($activePlayer === 'MPD') { + // output switch + $output = 0; + include('getid3/audioinfo.class.php'); + // get Last.FM api-key + $lastfm_apikey = $redis->get('lastfm_apikey'); + // get HTTP proxy settings + $proxy = $redis->hGetall('proxy'); + // connect to MPD daemon + $mpd2 = openMpdSocket('/run/mpd.sock', 0); + // fetch MPD status + $status = _parseStatusResponse(MpdStatus($mpd2)); + $curTrack = getTrackInfo($mpd2, $status['song']); + $mpdRoot = "/mnt/MPD/"; + $trackMpdPath = findPLposPath($status['song'], $mpd2); + $currentpath = $mpdRoot.$trackMpdPath; + closeMpdSocket($mpd2); + // debug + runelog("MPD current path", $currentpath); + $request_uri = urldecode($_SERVER['REQUEST_URI']); + runelog("HTTP GET request_uri (urldecoded)", $request_uri); + $request_folder = substr(substr($request_uri, 0, strrpos($request_uri, "/")), 10); + runelog("HTTP GET (request_folder)", $request_folder); + $request_coverfile = substr($request_uri, strrpos($request_uri, "/") + 1); + runelog("HTTP GET (request_coverfile)", $request_coverfile); + $current_mpd_folder = substr(substr($currentpath, 0, strrpos($currentpath, "/")), 9); + runelog("MPD (current_mpd_folder)", $current_mpd_folder); +// --------------------- Spotify --------------------- +} elseif ($redis->get('activePlayer') === 'Spotify') { + runelog('rune_PL_wrk: open SPOP socket'); + $spop = openSpopSocket('localhost', 6602, 1); +} +if ((substr($request_coverfile, 0, 2) === '?v' OR $current_mpd_folder === $request_folder) && $activePlayer === 'MPD') { + // extact song details + if (isset($curTrack[0]['Title'])) { + $status['currentartist'] = $curTrack[0]['Artist']; + $status['currentsong'] = $curTrack[0]['Title']; + $status['currentalbum'] = $curTrack[0]['Album']; + $status['fileext'] = parseFileStr($curTrack[0]['file'], '.'); + } + //Extract info from current audio file (using ZendMedia library) + /* if ($status['fileext'] === 'flac') { + require_once('Zend/Media/Flac.php'); + $flac = new Zend_Media_Flac($currentpath); + if ($flac->hasMetadataBlock(Zend_Media_Flac::PICTURE)) { + // debug + runelog("coverart match: embedded (ZendMedia lib)"); + header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1. + header('Pragma: no-cache'); // HTTP 1.0. + header('Expires: 0'); // Proxies. + header('Content-Type: '.$flac->getPicture()->getMimeType()); + echo $flac->getPicture()->getData(); + $output = 1; + } + } */ + //Extract info from current audio file (using GetID3 library) + if ($output === 0) { + $au = new AudioInfo(); + $auinfo = $au->Info($currentpath); + // 1. try to find embedded coverart + if (!empty($auinfo['comments']['picture'][0]['data'])) { + // debug + runelog("coverart match: embedded (GetID3 lib)"); + header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1. + header('Pragma: no-cache'); // HTTP 1.0. + header('Expires: 0'); // Proxies. + header('Content-Type: ' .$auinfo['comments']['picture'][0]['image_mime']); + echo $auinfo['comments']['picture'][0]['data']; + $output = 1; + } + } + // 2. try to find local coverart + if ($output === 0) { + $local_cover_root = substr($currentpath, 0, strrpos($currentpath, "/")); + $local_cover_path[] = $local_cover_root.'/folder.jpg'; + $local_cover_path[] = $local_cover_root.'/cover.jpg'; + $local_cover_path[] = $local_cover_root.'/folder.png'; + $local_cover_path[] = $local_cover_root.'/cover.png'; + foreach ($local_cover_path as $path) { + if (file_exists($path)) { + $local_cover_path = $path; + $output = 1; + break; + } + } + // debug + runelog("coverart: local (path): ", $local_cover_path); + if ($output === 1) { + // debug + runelog("coverart match: cover-local"); + header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1. + header('Pragma: no-cache'); // HTTP 1.0. + header('Expires: 0'); // Proxies. + header('Content-Type: ' .mime_content_type($local_cover_path)); + readfile($local_cover_path); + } + } + // 3.0 try to find coverart on Last.FM (Album) + if ($output === 0) { + $cover_url = ui_lastFM_coverart($status['currentartist'], $status['currentalbum'], $lastfm_apikey, $proxy); + if (!empty($cover_url)) { + // debug + runelog("coverart match: lastfm (query 1) coverURL=", $cover_url); + $lastfm_img = curlGet($cover_url, $proxy); + $bufferinfo = new finfo(FILEINFO_MIME); + $lastfm_img_mime = $bufferinfo->buffer($lastfm_img); + } else { + // 3.1 try to find coverart on Last.FM (Artist) + $cover_url = ui_lastFM_coverart($status['currentartist'], '', $lastfm_apikey, $proxy); + if (!empty($cover_url)) { + // debug + runelog("coverart match: lastfm (query 2) coverURL=", $cover_url); + if (!empty($cover_url)) { + $lastfm_img = curlGet($cover_url, $proxy); + $lastfm_img_mime = $bufferinfo->buffer($lastfm_img); + } + } + } + if (!empty($lastfm_img)) { + header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1. + header('Pragma: no-cache'); // HTTP 1.0. + header('Expires: 0'); // Proxies. + header('Content-Type: '.$lastfm_img_mime); + echo $lastfm_img; + $output = 1; + } + } + // 4. serve DEFAULT rune-cover image + if ($output === 0) { + // debug + runelog("coverart match: cover-default"); + header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1. + header('Pragma: no-cache'); // HTTP 1.0. + header('Expires: 0'); // Proxies. + header('Content-Type: ' .mime_content_type($_SERVER['HOME'].'/assets/img/cover-default.png')); + readfile($_SERVER['HOME'].'/assets/img/cover-default.png'); + $output = 1; + } +} else { + if ($activePlayer === 'Spotify') { + $count = 1; + do { + sendSpopCommand($spop, 'image'); + unset($spotify_cover); + $spotify_cover = readSpopResponse($spop); + $spotify_cover = json_decode($spotify_cover); + usleep(500000); + runelog('coverart (spotify): retry n: '.$count, $spotify_cover->status); + if ($spotify_cover->status === 'ok') { + $spotify_cover = base64_decode($spotify_cover->data); + break; + } + $count++; + } while ($count !== 10); + $bufferinfo = new finfo(FILEINFO_MIME); + $spotify_cover_mime = $bufferinfo->buffer($spotify_cover); + header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1. + header('Pragma: no-cache'); // HTTP 1.0. + header('Expires: 0'); // Proxies. + header('Content-Type: '.$spotify_cover_mime); + echo $spotify_cover; + } else { + // redirect to /covers NGiNX location + $local_cover_url = 'http://'.$_SERVER["SERVER_ADDR"].'/covers/'.$request_folder.'/'.$request_coverfile; + runelog("coverart: redirect to local-coverart (url): ", $local_cover_url); + header('Location: '.$local_cover_url, true, 301); + } +} +runelog("\n--------------------- coverart (end) ---------------------"); diff --git a/app/api/credits_ctl.php b/app/api/credits_ctl.php new file mode 100644 index 00000000..87eeddb6 --- /dev/null +++ b/app/api/credits_ctl.php @@ -0,0 +1,37 @@ +. + * + * file: app/credits_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ + + $template->buildversion = $redis->get('buildversion'); + $template->release = $redis->get('release'); + \ No newline at end of file diff --git a/app/api/debug_ctl.php b/app/api/debug_ctl.php new file mode 100644 index 00000000..8b254114 --- /dev/null +++ b/app/api/debug_ctl.php @@ -0,0 +1,43 @@ +. + * + * file: app/debug_ctl.php + * version: 1.3 + * + */ +// ob_start(); +// echo debug_data($redis); +// $debugdata = ob_get_clean(); + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + +} else { + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'debug')); + waitSyWrk($redis, $jobID); + $template->debug = $redis->get('debugdata'); +} \ No newline at end of file diff --git a/app/api/dev_ctl.php b/app/api/dev_ctl.php new file mode 100644 index 00000000..b361e581 --- /dev/null +++ b/app/api/dev_ctl.php @@ -0,0 +1,94 @@ +. + * + * file: app/dev_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ +// inspect POST +if (isset($_POST)) { + // ----- DEV MODE ----- + if (isset($_POST['mode'])) { + if ($_POST['mode']['dev']['enable'] == 1) { + // create worker job (start udevil) + $redis->get('dev') == 1 || $redis->set('dev', 1); + $redis->get('debug') == 1 || $redis->set('debug', 1); + } else { + // create worker job (stop udevil) + $redis->get('dev') == 0 || $redis->set('dev', 0); + } + // ----- DEBUG ----- + if ($_POST['mode']['debug']['enable'] == 1) { + // create worker job (start udevil) + $redis->get('debug') == 1 || $redis->set('debug', 1); + } else { + // create worker job (stop udevil) + $redis->get('debug') == 0 || $redis->set('debug', 0); + } + } + // ----- OPCACHE ----- + if (isset($_POST['opcache'])) { + if ($_POST['opcache']['enable'] == 1) { + // create worker job (enable php opcache) + $redis->get('opcache') == 1 || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'opcache', 'action' => 'enable')); + } else { + // create worker job (disable php opcache) + $redis->get('opcache') == 0 || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'opcache', 'action' => 'disable')); + } + } + if (isset($_POST['syscmd'])) { + // ----- BLANK PLAYERID ----- + if ($_POST['syscmd'] === 'blankplayerid') $redis->set('playerid',''); + // ----- CLEARIMG ----- + if ($_POST['syscmd'] === 'clearimg') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'clearimg')); + // ----- CHECK FS PERMISSIONS ----- + if ($_POST['syscmd'] === 'syschmod') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'sysAcl')); + // ----- RESTART MPD ----- + if ($_POST['syscmd'] === 'mpdrestart') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdrestart')); + // ----- RESET NET CONFIG ----- + if ($_POST['syscmd'] === 'netconfreset') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'netcfg', 'action' => 'reset')); + // ----- RESET MPD CONFIG ----- + if ($_POST['syscmd'] === 'mpdconfreset') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdcfg', 'action' => 'reset')); + // ----- RESTART PHP-FPM ----- + if ($_POST['syscmd'] === 'phprestart') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'phprestart')); + // ----- GIT PULL ----- + if ($_POST['syscmd'] === 'gitpull') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'gitpull')); + // ----- RESTART WORKERS ----- + if (isset($_POST['syscmd']['wrkrestart'])) $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wrkrestart', 'args' => $_POST['syscmd']['wrkrestart'])); + } +} +waitSyWrk($redis, $jobID); +$template->debug = $redis->get('debug'); +$template->playerid = $redis->get('playerid'); +$template->opcache = $redis->get('opcache'); +$template->gitbranch = $redis->hGet('git', 'branch'); +// debug +// var_dump($template->dev); +// var_dump($template->debug); +// var_dump($template->opcache); diff --git a/app/api/login_ctl.php b/app/api/login_ctl.php new file mode 100644 index 00000000..3f9377c0 --- /dev/null +++ b/app/api/login_ctl.php @@ -0,0 +1,34 @@ +. + * + * file: app/login_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ + \ No newline at end of file diff --git a/app/api/mpd_ctl.php b/app/api/mpd_ctl.php new file mode 100644 index 00000000..8bc42733 --- /dev/null +++ b/app/api/mpd_ctl.php @@ -0,0 +1,146 @@ +. + * + * file: mpd_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + + // get the data that was POSTed + $postData = file_get_contents("php://input"); + // convert to an associative array + $json = json_decode($postData, true); + + // switch audio output + //if (isset($json['ao'])) { + // $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdcfg', 'action' => 'switchao', 'args' => $json['ao'])); + // $template->AAA = "ao"; + //} + // reset MPD configuration + if (isset($json['reset'])) { + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdcfg', 'action' => 'reset')); + $template->BBB = "reset"; + } + // update MPD configuration + if (isset($json['conf'])) { + foreach ($json['conf'] as $mpdfield => $data) { + if ($data === TRUE) { + $json['conf'][$mpdfield] = 'yes'; + } else if ($data === FALSE) { + $json['conf'][$mpdfield] = 'no'; + } + } + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdcfg', 'action' => 'update', 'args' => $json['conf'])); + $template->CCC = "conf"; + } + // manual MPD configuration + if (isset($json['mpdconf'])) { + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdcfgman', 'args' => $json['mpdconf'])); + $template->DDD = "mpd conf"; + } + // waitSyWrk($redis, $jobID); +} else { + $ao = ''; + $aoValue = $redis->get('ao'); + + + // check integrity of /etc/network/interfaces + if(!hashCFG('check_mpd', $redis)) { + $template->mpdconf = file_get_contents('/etc/mpd.conf'); + // set manual config template + $template->content = "mpd_manual"; + } else { + // At the moment, the UI needs to convert the + // file name being present to a 'yes' + $mpdconf = $redis->hGetAll('mpdconf'); + if (isset($mpdconf['state_file'])) { + $mpdconf['state_file'] = TRUE; + } else { + $mpdconf['state_file'] = FALSE; + } + foreach ($mpdconf as $mpdfield => $data) { + if ($data === 'yes') { + $mpdconf[$mpdfield] = TRUE; + } else if ($data === 'no') { + $mpdconf[$mpdfield] = FALSE; + } + } + $template->conf = $mpdconf; + $i2smodule = $redis->get('i2smodule'); + // debug + // echo $i2smodule."\n"; + $acards = $redis->hGetAll('acards'); + // debug + // print_r($acards); + foreach ($acards as $card => $data) { + $acard_data = json_decode($data); + // debug + // echo $card."\n"; + // print_r($acard_data); + if ($i2smodule !== 'none') { + $acards_details = $redis->hGet('acards_details', $i2smodule); + } else { + $acards_details = $redis->hGet('acards_details', $card); + } + if (!empty($acards_details)) { + $details = json_decode($acards_details); + // debug + // echo "acards_details\n"; + // print_r($details); + if ($details->sysname === $card) { + if ($details->type === 'integrated_sub') { + $sub_interfaces = $redis->sMembers($card); + foreach ($sub_interfaces as $int) { + $sub_int_details = json_decode($int); + // TODO !!! check + $audio_cards[] = $sub_int_details; + } + } + if ($details->extlabel !== 'none') { + $acard_data->extlabel = $details->extlabel; + } + } + } + + // look to see if the selected card matches this one in the loop + if ($acard_data->name == $aoValue) { + $ao = $acard_data->extlabel; + } + $audio_cards[] = $acard_data; + } + osort($audio_cards, 'extlabel'); + // debug + // print_r($audio_cards); + $template->acards = $audio_cards; + $template->ao = $ao; + } + +} diff --git a/app/api/network_ctl.php b/app/api/network_ctl.php new file mode 100644 index 00000000..2a455b7e --- /dev/null +++ b/app/api/network_ctl.php @@ -0,0 +1,149 @@ +. + * + * file: network_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ + +$segments = explode('/', $template->uri()); +$count = count($segments); +$lastsegment = strlen($segments[$count - 1]); +if ($lastsegment===0) { + $count = $count -1; +} +$uri_length = $count; + +// /api/network = 3 +// /api/network/ = 3 +// /api/network/eth0 = 4 +// /api/network/eth0/ = 4 + + + +// Check for POST +if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // get the data that was POSTed + $postData = file_get_contents("php://input"); + // convert to an associative array + $json = json_decode($postData, true); + + if (isset($json['nic'])) { + $redis->get($json['nic']['name']) == json_encode($nic) || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'netcfg', 'action' => 'config', 'args' => $json['nic'])); + } + if (isset($json['refresh'])) { + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'netcfg', 'action' => 'refresh')); + } + if (isset($json['wifiprofile'])) { + switch ($json['wifiprofile']['action']) { + case 'add': + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wificfg', 'action' => 'add', 'args' => $json['wifiprofile'])); + break; + case 'edit': + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wificfg', 'action' => 'edit', 'args' => $json['wifiprofile'])); + break; + case 'delete': + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wificfg', 'action' => 'delete', 'args' => $json['wifiprofile'])); + break; + case 'disconnect': + $jobID[] = wrk_control($redis, 'newjob', $data = array( 'wrkcmd' => 'wificfg', 'action' => 'disconnect', 'args' => $json['wifiprofile'] )); + break; + } + } + // if (isset($json['wifidelete'])) { + // $jobID[] = wrk_control($redis,'newjob', $data = array( 'wrkcmd' => 'wificfg', 'action' => 'delete', 'args' => $json['wifidelete'] )); + // } + if (isset($json['wpa_cli'])) { + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wificfg', 'action' => 'wpa_cli', 'args' => $json['wpa_cli'])); + } + +} else { + // GET + + if ($uri_length == 4) { + // INTERFACE SETUP - /api/network/eth0/ or /api/network/wlan0/ + $nicID = $template->uri(3); // i.e. 'eth0' or 'wlan0' + // retrieve current nic status data (detected from the system) + $nic_connection = $redis->hGet('nics', $nicID); + $template->nic = json_decode($nic_connection); + $template->nic->wireless = ($template->nic->wireless == 1); + $template->nic->dns2 = ($template->nic->dns2 == null) ? '' : $template->nic->dns2; + // fetch current (stored) nic configuration data + if ($redis->get($nicID)) { + $template->profile = json_decode($redis->get($nicID)); + $template->profile->dhcp = ($template->profile->dhcp == 1); + // ok nic configuration not stored, but check if it is configured + } else if ($nic_connection == null) { + // last case, nic not found. return an error + $msg = 'Oops. Looks like you are trying to edit a card that no longer exists. The broken url is: '.$template->uri(); + $template->errormsg = $msg; + http_response_code(400); // HTTP : Bad Request + } + + $prof = json_decode($redis->get($nicID)); + $nic = json_decode($nic_connection); + // return: + + // SSID + // UNAME + // PASS + // ENCR + + // DHCP + // IP + // GW + // DNS1 + // DNS2 + + $return->dhcp = $nic->dhcp; + // $return->connected = true; // boolean to tell if I'm actually connected + $return->ip = $nic->ip; // Get the IP from the NIC, or the Assigned Static IP if none on NIC, or "" if DHCP & No Connection + $return->mask = $nic->mask; // Get the IP from the NIC, or the Assigned Static IP if none on NIC, or "" if DHCP & No Connection + $return->gw = $nic->ip; + $return->dns1 = $nic->ip; + $return->dns2 = $nic->ip; + + // on client + // placeholder = oldData.ip + // value = data.ip + } else if ($uri_length == 3) { + // MAIN SECTION - /api/network/ + $nics = []; + $nics2 = []; + foreach ($redis->hGetAll('nics') as $interface => $details) { + $nic = json_decode($details); + $nic->wireless = ($nic->wireless == '1'); + $nic->id = $interface; + $nics[] = $nic; + } + $template->nics = $nics; + + } + +} \ No newline at end of file diff --git a/app/api/playback_ctl.php b/app/api/playback_ctl.php new file mode 100644 index 00000000..296ad06a --- /dev/null +++ b/app/api/playback_ctl.php @@ -0,0 +1,53 @@ +. + * + * file: app/playback_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ +$template->activePlayer = $redis->get('activePlayer'); +if ($redis->get('coverart') == 1) { + $template->coverart = 1; + $template->colspan = 4; +} else { + $template->coverart = 0; + $template->colspan = 6; +} +if ($redis->get('volume') == 1 && $template->activePlayer !== 'Spotify') { + $template->volume['color'] = '#0095D8'; + $template->volume['readonly'] = 'false'; +} else { + //$_volumeColor = '#002c40'; + $template->volume['color'] = '#1A242F'; + $template->volume['readonly'] = 'true'; + $template->volume['disabled'] = 1; + $template->volume['divclass'] = 'nomixer'; +} +$template->dev = $redis->get; +$template->spotify = $redis->hGet('spotify', 'enable'); diff --git a/app/api/settings_ctl.php b/app/api/settings_ctl.php new file mode 100644 index 00000000..6451b054 --- /dev/null +++ b/app/api/settings_ctl.php @@ -0,0 +1,253 @@ +. + * + * file: app/settings_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ + +// Simple class for managing the items in an HTML Select +class NameValuePair { + public $name = ''; + public $value = ''; + + public function __construct($n, $v) { + $this->name = $n; + $this->value = $v; + } +} + +$environment = []; +$timezones = []; +$kernel = []; +$features = []; +$system = []; + +// Check for POST +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // get the data that was POSTed + $postData = file_get_contents("php://input"); + // convert to an associative array + $json = json_decode($postData, true); + + // ----- Environment Section ----- + if (isset($json['environment'])) { + $environment = $json['environment']; + + // ----- HOSTNAME ----- + if (empty($environment['hostname'])) { + $args = 'runeaudio'; + } else { + $args = $environment['hostname']; + } + $redis->get('hostname') == $args || $jobID[] = wrk_control($redis, 'newjob', $data = array( 'wrkcmd' => 'hostname', 'args' => $args )); + + // ----- TIME SETTINGS ----- + if (empty($environment['ntpserver'])) { + $args = 'pool.ntp.org'; + } else { + $args = $environment['ntpserver']; + } + $template->ZZZZ = $redis->get('ntpserver') == $args; + $template->XXXX = $args; + $redis->get('ntpserver') == $args || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'ntpserver', 'args' => $args)); + + + $args = $environment['timezone']; + $redis->get('timezone') == $args || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'timezone', 'args' => $args)); + + $template->XXXX1 = $args; + + } + + // ----- KERNEL ----- + if (isset($json['kernel'])) { + // submit worker job + if ($redis->get('kernel') !== $json['kernel']) { + $job = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'kernelswitch', 'args' => $json['kernel'])); + $notification = new stdClass(); + $notification->title = 'Kernel switch'; + $notification->text = 'Kernel switch started...'; + wrk_notify($redis, 'startjob', $notification, $job); + $jobID[] = $job; + } + } + if (isset($json['orionprofile'])) { + // submit worker job + $redis->get('orionprofile') == $json['orionprofile'] || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'orionprofile', 'args' => $json['orionprofile'])); + } + if (isset($json['i2smodule'])) { + // submit worker job + if ($redis->get('i2smodule') !== $json['i2smodule']) { + $notification = new stdClass(); + if ($json['i2smodule'] !== 'none') { + $notification->title = 'Loading I²S kernel module'; + } else { + $notification->title = 'Unloading I²S kernel module'; + } + $job = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'i2smodule', 'args' => $json['i2smodule'])); + $notification->text = 'Please wait'; + wrk_notify($redis, 'startjob', $notification, $job); + $jobID[] = $job; + } + + // autoswitch optimized kernel profile for BerryNOS mini DAC + if ($json['i2smodule'] === 'berrynosmini') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'orionprofile', 'args' => 'OrionV3_berrynosmini')); + // autoswitch optimized kernel profile for IQaudIO Pi-DAC + if ($json['i2smodule'] === 'iqaudiopidac') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'orionprofile', 'args' => 'OrionV3_iqaudio')); + } + // ----- FEATURES ----- + if (isset($json['features'])) { + if ($json['features']['airplay']['enable']) { + if (($redis->hGet('airplay','enable') === '0') OR $redis->hGet('airplay','name') !== $json['features']['airplay']['name']) { + // create worker job (start shairport) + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'airplay', 'action' => 'start', 'args' => $json['features']['airplay']['name'])); + } + } else { + // create worker job (stop shairport) + $redis->hGet('airplay','enable') === '0' || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'airplay', 'action' => 'stop', 'args' => $json['features']['airplay']['name'])); + } + if ($json['features']['dlna']['enable'] == 1) { + if ($redis->hGet('dlna','enable') !== $json['features']['dlna']['enable'] OR $redis->hGet('dlna','name') !== $json['features']['dlna']['name']) { + // create worker job (start upmpdcli) + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'dlna', 'action' => 'start', 'args' => $json['features']['dlna']['name'])); + } + } else { + // create worker job (stop upmpdcli) + $redis->hGet('dlna','enable') === '0' || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'dlna', 'action' => 'stop', 'args' => $json['features']['dlna']['name'])); + } + if ($json['features']['udevil'] == 1) { + // create worker job (start udevil) + $redis->get('udevil') == 1 || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'udevil', 'action' => 'start')); + } else { + // create worker job (stop udevil) + $redis->get('udevil') == 0 || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'udevil', 'action' => 'stop')); + } + if ($json['features']['coverart'] == 1) { + $redis->get('coverart') == 1 || $redis->set('coverart', 1); + } else { + $redis->get('coverart') == 0 || $redis->set('coverart', 0); + } + if ($json['features']['globalrandom'] == 1) { + $redis->get('globalrandom') == 1 || $redis->set('globalrandom', 1); + } else { + $redis->get('globalrandom') == 0 || $redis->set('globalrandom', 0); + } + if ($json['features']['lastfm']['enable'] == 1) { + // create worker job (start mpdscribble) + if (($json['features']['lastfm']['user'] != $redis->hGet('lastfm', 'user') OR $json['features']['lastfm']['pass'] != $redis->hGet('lastfm', 'pass')) OR $redis->hGet('lastfm', 'enable') != $json['features']['lastfm']['enable']) { + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'lastfm', 'action' => 'start', 'args' => $json['features']['lastfm'])); + } + } else { + // create worker job (stop mpdscribble) + $redis->hGet('lastfm','enable') == 0 || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'lastfm', 'action' => 'stop')); + } + if ($json['features']['spotify']['enable'] == 1) { + // create worker job (start mpdscribble) + if (($json['features']['spotify']['user'] != $redis->hGet('spotify', 'user') OR $json['features']['spotify']['pass'] != $redis->hGet('spotify', 'pass')) OR $redis->hGet('spotify', 'enable') != $json['features']['spotify']['enable']) { + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'spotify', 'action' => 'start', 'args' => $json['features']['spotify'])); + } + } else { + // create worker job (stop spotify) + $redis->hGet('spotify','enable') == 0 || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'spotify', 'action' => 'stop')); + } + } + //// ----- SYSTEM COMMANDS ----- + //if (isset($json['syscmd'])){ + // if ($json['syscmd'] === 'reboot') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'reboot')); + // if ($json['syscmd'] === 'poweroff') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'poweroff')); + // if ($json['syscmd'] === 'mpdrestart') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'mpdrestart')); + // if ($json['syscmd'] === 'backup') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'backup')); + //} + + $template->YYYY = $jobID; + // waitSyWrk($redis,$jobID); + +} else { + + if (!$template->uri(3)) { + // MAIN SECTION - /api/settings/ + + // environment section + $environment['hostname'] = $redis->get('hostname'); + $environment['ntpserver'] = $redis->get('ntpserver'); + $environment['timezone'] = $redis->get('timezone'); + $template->environment = $environment; + + // kernel section + $kernel['kernel'] = $redis->get('kernel'); + $kernel['i2smodule'] = $redis->get('i2smodule'); + $kernel['orionprofile'] = $redis->get('orionprofile'); + $template->kernel = $kernel; + + // features section + $features['airplay'] = $redis->hGetAll('airplay'); + $features['airplay']['enable'] = ($features['airplay']['enable'] === '1'); + $features['dlna'] = $redis->hGetAll('dlna'); + $features['dlna']['enable'] = ($features['dlna']['enable'] === '1'); + $features['udevil'] = $redis->get('udevil'); + $features['udevil'] = ($features['udevil'] === '1'); + $features['coverart'] = $redis->get('coverart'); + $features['coverart'] = ($features['coverart'] === '1'); + $features['globalrandom'] = $redis->get('globalrandom'); + $features['globalrandom'] = ($features['globalrandom'] === '1'); + $features['lastfm'] = $redis->hGetAll('lastfm'); + $features['lastfm']['enable'] = ($features['lastfm']['enable'] === '1'); + $features['lastfm']['authenticated'] = TRUE; // [TODO] make it real + $features['proxy'] = $redis->hGetAll('proxy'); + $features['proxy']['enable'] = ($features['proxy']['enable'] === '1'); + $features['spotify'] = $redis->hGetAll('spotify'); + $features['spotify']['enable'] = ($features['spotify']['enable'] === '1'); + $features['spotify']['authenticated'] = FALSE; // [TODO] make it real + $features['hwplatformid'] = $redis->get('hwplatformid'); + $template->features = $features; + + // cmedia fix + $template->cmediafix = ($redis->get('cmediafix') === '1'); + + } else { + // SUBSECTIONS + + if ($template->uri(3, 'timezones')) { + // TIMEZONES - /api/settings/timezones/ + foreach (ui_timezone() as $t) { + $timezones[] = new NameValuePair($t['zone'].' - '.$t['diff_from_GMT'], $t['zone']); + } + $template->timezones = $timezones; + } else if ($template->uri(3, 'sysinfo')) { + // SYSTEM INFO - /api/settings/sysinfo/ + $system['kernel'] = file_get_contents('/proc/version'); + $system['time'] = implode('\n', sysCmd('date')); + $system['uptime'] = date('d:H:i:s', strtok(file_get_contents('/proc/uptime'), ' ' )); + $system['HWplatform'] = $redis->get('hwplatform')." (".$redis->get('hwplatformid').")"; + $system['playerID'] = $redis->get('playerid'); + $template->system = $system; + } + } +} \ No newline at end of file diff --git a/app/api/sources_ctl.php b/app/api/sources_ctl.php new file mode 100644 index 00000000..a1a2a1e7 --- /dev/null +++ b/app/api/sources_ctl.php @@ -0,0 +1,167 @@ +. + * + * file: app/sources_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + + // get the data that was POSTed + $postData = file_get_contents("php://input"); + // convert to an associative array + $json = json_decode($postData, true); + + if ($json['updatempd'] == true) { + sendMpdCommand($mpd, 'update'); + return; + } + + if ($json['mountall'] == true) { + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'sourcecfg', 'action' => 'mountall' )); + //waitSyWrk($redis, $jobID); + return ; + } + + if ($json['source-umount']) { + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'sourcecfg', 'action' => 'delete', 'args' => $json['mount'])); + //waitSyWrk($redis, $jobID); + return; + } + + if ($json['usb-umount']) { + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'sourcecfg', 'action' => 'umountusb', 'args' => $json['usb-umount'])); + //waitSyWrk($redis, $jobID); + return; + } + + if ($json['mount']) { + $json['mount']['remotedir'] = str_replace('\\', '/', $json['mount']['remotedir']); + if ($json['mount']['rsize'] == '') $json['mount']['rsize'] = 16384; + if ($json['mount']['wsize'] == '') $json['mount']['wsize'] = 17408; + if ($json['mount']['options'] == '') { + if ($json['mount']['type'] === 'cifs' OR $json['mount']['type'] === 'osx') { + $json['mount']['options'] = "cache=none,noserverino,ro"; + } else { + $json['mount']['options'] = "nfsvers=3,ro"; + } + } + if ($json['mount']['id'] == '0') { + // Add + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'sourcecfg', 'action' => 'add', 'args' => $json['mount'])); + } else { + // Edit + $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'sourcecfg', 'action' => 'edit', 'args' => $json['mount'])); + } + } + + // TODO : reset + // if ($json['action'] == 'reset') $jobID = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'sourcecfgman', 'action' => 'reset' )); + + //waitSyWrk($redis, $jobID); + +} else { + + // GET + + $id = $template->arg; // null when the id = 0 + $source = netMounts($redis, 'read'); + + if ($id === NULL) { + // Getting the Sources list + + // Disk Mounts + + if($source !== true) { + foreach ($source as $mp) { + if (wrk_checkStrSysfile('/proc/mounts', '/mnt/MPD/NAS/'.$mp['name'])) { + $mp['status'] = true; + } else { + $mp['status'] = false; + } + $mounts[]=$mp; + } + } + $template->mounts = $mounts; + + // USB Mounts + $usbmounts = $redis->hGetAll('usbmounts'); + foreach ($usbmounts as $usbmount) { + $template->usbmounts[] = json_decode($usbmount); + } + // we still want the property sent to the UI + if (isset($template->usbmounts)===FALSE) { + $template->usbmounts = NULL; + } + + + } else if ($id === '0') { + // GET to setup a New Source + $mount = new stdClass(); + $mount->id = 0; + $mount->name = ''; + $mount->address = ''; + $mount->type = ''; + $mount->remotedir = ''; + $mount->username = ''; + $mount->password = ''; + + $template->mount = $mount; + + } else if ($id > 0) { + // GET to Edit Existing + //$template->mount = NULL; + foreach ($source as $mp) { + if ($mp['id'] == $id) { + $template->mount = $mp; + } + } + + + if (isset($template->mount)===FALSE) { + // we were sent a bad ID in the URL + $template->errormsg = 'The selected mount does not exist.'; // [TODO] pass the localized string ID + http_response_code(400); // HTTP : Bad Request + } + } + + //if (isset($template->action)) { + // if (isset($template->arg)) { + // foreach ($source as $mp) { + // if ($mp['id'] == $template->arg) { + // $template->mount = $mp; + // } + // } + // $template->title = 'Edit network mount'; + // } else { + // $template->title = 'Add new network mount'; + // } + //} + +} \ No newline at end of file diff --git a/app/api/system_ctl.php b/app/api/system_ctl.php new file mode 100644 index 00000000..d69b73ff --- /dev/null +++ b/app/api/system_ctl.php @@ -0,0 +1,51 @@ +. + * + * file: system_ctl.php + * version: 1.3 + * coder: Kevin Welsh + * + */ +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // get the data that was POSTed + $postData = file_get_contents("php://input"); + // convert to an associative array + $json = json_decode($postData, true); + + if ($json['reboot']) { + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'reboot')); + } else if ($json['poweroff']) { + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'poweroff')); + } else if($json['syscmd'] === 'backup') { + $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'backup')); + pushFile($redis->hGet('w_msg', $jobID[0])); + $redis->hDel('w_msg', $jobID[0]); + } + + // waitSyWrk($redis,$jobID); +}; diff --git a/app/api/tun_ctl.php b/app/api/tun_ctl.php new file mode 100644 index 00000000..93ca34a4 --- /dev/null +++ b/app/api/tun_ctl.php @@ -0,0 +1,42 @@ +. + * + * file: app/tun_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ +// disable main template output +$tplfile = 0; +$proxy = $redis->hGetall('proxy'); +if ($proxy['enable'] === '1') { + echo curlGet(substr($_SERVER["REQUEST_URI"], 5), $proxy); +} else { + echo curlGet(substr($_SERVER["REQUEST_URI"], 5)); +} + \ No newline at end of file diff --git a/app/api_ctl.php b/app/api_ctl.php new file mode 100644 index 00000000..008d7b93 --- /dev/null +++ b/app/api_ctl.php @@ -0,0 +1,3 @@ +action.'_ctl.php'); diff --git a/app/config/_os/etc/ca-certificates/conf.d/runeaudio.conf b/app/config/_os/etc/ca-certificates/conf.d/runeaudio.conf index 9fe0cabf..981f4298 100644 --- a/app/config/_os/etc/ca-certificates/conf.d/runeaudio.conf +++ b/app/config/_os/etc/ca-certificates/conf.d/runeaudio.conf @@ -1,2 +1,2 @@ -runeaudio/gd_intermediate.crt - +runeaudio/gd_intermediate.crt + diff --git a/app/config/_os/etc/fstab.rpi b/app/config/_os/etc/fstab.rpi index 8b49769c..4d524442 100644 --- a/app/config/_os/etc/fstab.rpi +++ b/app/config/_os/etc/fstab.rpi @@ -1,8 +1,8 @@ -# /etc/fstab: static file system information. -# -#/dev/mmcblk0p3 /               ext4    noatime,discard,data=writeback,journal_async_commit,nouser_xattr,barrier=0,errors=remount-ro 0       1 -/dev/mmcblk0p3 / ext4 noatime,nouser_xattr,errors=remount-ro 0 1 -/dev/mmcblk0p1 /boot vfat utf8 0 0 -Ramdisk /run/shm tmpfs defaults,size=256M,noexec,nodev,nosuid 0 0 -logs /var/log tmpfs nodev,nosuid,noatime,mode=1777,size=5M 0 0 +# /etc/fstab: static file system information. +# +#/dev/mmcblk0p3 /               ext4    noatime,discard,data=writeback,journal_async_commit,nouser_xattr,barrier=0,errors=remount-ro 0       1 +/dev/mmcblk0p3 / ext4 noatime,nouser_xattr,errors=remount-ro 0 1 +/dev/mmcblk0p1 /boot vfat utf8 0 0 +Ramdisk /run/shm tmpfs defaults,size=256M,noexec,nodev,nosuid 0 0 +logs /var/log tmpfs nodev,nosuid,noatime,mode=1777,size=5M 0 0 rune-logs /var/log/runeaudio tmpfs nodev,nosuid,noatime,mode=1777,size=20M 0 0 \ No newline at end of file diff --git a/app/config/_os/etc/issue b/app/config/_os/etc/issue index 91e0a6a9..7edccf73 100644 --- a/app/config/_os/etc/issue +++ b/app/config/_os/etc/issue @@ -1,5 +1,5 @@ ---------------------------------------------------------- -Copyright (C) 2013-2014 RuneAudio Team -Andrea Coiutti & Simone De Gregori & Carmelo San Giovanni ---------------------------------------------------------- - +--------------------------------------------------------- +Copyright (C) 2013-2014 RuneAudio Team +Andrea Coiutti & Simone De Gregori & Carmelo San Giovanni +--------------------------------------------------------- + diff --git a/app/config/_os/etc/minidlna.conf b/app/config/_os/etc/minidlna.conf index ccd5f86b..f1d3952e 100644 --- a/app/config/_os/etc/minidlna.conf +++ b/app/config/_os/etc/minidlna.conf @@ -1,96 +1,96 @@ -# This is the configuration file for the MiniDLNA daemon, a DLNA/UPnP-AV media -# server. -# -# Unless otherwise noted, the commented out options show their default value. -# -# On Debian, you can also refer to the minidlna.conf(5) man page for -# documentation about this file. - - -# Path to the directory you want scanned for media files. -# -# This option can be specified more than once if you want multiple directories -# scanned. -# -# If you want to restrict a media_dir to a specific content type, you can -# prepend the directory name with a letter representing the type (A, P or V), -# followed by a comma, as so: -# * "A" for audio (eg. media_dir=A,/var/lib/minidlna/music) -# * "P" for pictures (eg. media_dir=P,/var/lib/minidlna/pictures) -# * "V" for video (eg. media_dir=V,/var/lib/minidlna/videos) -# -# WARNING: After changing this option, you need to rebuild the database. Either -# run minidlna with the '-R' option, or delete the 'files.db' file -# from the db_dir directory (see below). -# On Debian, you can run, as root, 'service minidlna force-reload' instead. -#media_dir=/var/lib/mpd/music - -# Path to the directory that should hold the database and album art cache. -#db_dir=/var/lib/minidlna -db_dir=/run/minidlna - -# Path to the directory that should hold the log file. -#log_dir=/var/log - -# Minimum level of importance of messages to be logged. -# Must be one of "off", "fatal", "error", "warn", "info" or "debug". -# "off" turns of logging entirely, "fatal" is the highest level of importance -# and "debug" the lowest. -#log_level=warn - -# Use a different container as the root of the directory tree presented to -# clients. The possible values are: -# * "." - standard container -# * "B" - "Browse Directory" -# * "M" - "Music" -# * "P" - "Pictures" -# * "V" - "Video" -# if you specify "B" and client device is audio-only then "Music/Folders" will be used as root -#root_container=. - -# Network interface(s) to bind to (e.g. eth0), comma delimited. -#network_interface= - -# IPv4 address to listen on (e.g. 192.0.2.1). -#listening_ip= - -# Port number for HTTP traffic (descriptions, SOAP, media transfer). -port=8200 - -# URL presented to clients. -# The default is the IP address of the server on port 80. -presentation_url=http://192.168.10.110:80 - -# Name that the DLNA server presents to clients. -#friendly_name=RuneAudio - -# Serial number the server reports to clients. -serial=12345678 - -# Model name the server reports to clients. -#model_name=Windows Media Connect compatible (MiniDLNA) - -# Model number the server reports to clients. -model_number=1 - -# Automatic discovery of new files in the media_dir directory. -inotify=no - -# List of file names to look for when searching for album art. Names should be -# delimited with a forward slash ("/"). -#album_art_names=Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg/AlbumArt.jpg/albumart.jpg/Album.jpg/album.jpg/Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg - -# Strictly adhere to DLNA standards. -# This allows server-side downscaling of very large JPEG images, which may -# decrease JPEG serving performance on (at least) Sony DLNA products. -#strict_dlna=no - -# Support for streaming .jpg and .mp3 files to a TiVo supporting HMO. -#enable_tivo=no - -# Notify interval, in seconds. -#notify_interval=895 - -# Path to the MiniSSDPd socket, for MiniSSDPd support. -#minissdpdsocket=/run/minissdpd.sock - +# This is the configuration file for the MiniDLNA daemon, a DLNA/UPnP-AV media +# server. +# +# Unless otherwise noted, the commented out options show their default value. +# +# On Debian, you can also refer to the minidlna.conf(5) man page for +# documentation about this file. + + +# Path to the directory you want scanned for media files. +# +# This option can be specified more than once if you want multiple directories +# scanned. +# +# If you want to restrict a media_dir to a specific content type, you can +# prepend the directory name with a letter representing the type (A, P or V), +# followed by a comma, as so: +# * "A" for audio (eg. media_dir=A,/var/lib/minidlna/music) +# * "P" for pictures (eg. media_dir=P,/var/lib/minidlna/pictures) +# * "V" for video (eg. media_dir=V,/var/lib/minidlna/videos) +# +# WARNING: After changing this option, you need to rebuild the database. Either +# run minidlna with the '-R' option, or delete the 'files.db' file +# from the db_dir directory (see below). +# On Debian, you can run, as root, 'service minidlna force-reload' instead. +#media_dir=/var/lib/mpd/music + +# Path to the directory that should hold the database and album art cache. +#db_dir=/var/lib/minidlna +db_dir=/run/minidlna + +# Path to the directory that should hold the log file. +#log_dir=/var/log + +# Minimum level of importance of messages to be logged. +# Must be one of "off", "fatal", "error", "warn", "info" or "debug". +# "off" turns of logging entirely, "fatal" is the highest level of importance +# and "debug" the lowest. +#log_level=warn + +# Use a different container as the root of the directory tree presented to +# clients. The possible values are: +# * "." - standard container +# * "B" - "Browse Directory" +# * "M" - "Music" +# * "P" - "Pictures" +# * "V" - "Video" +# if you specify "B" and client device is audio-only then "Music/Folders" will be used as root +#root_container=. + +# Network interface(s) to bind to (e.g. eth0), comma delimited. +#network_interface= + +# IPv4 address to listen on (e.g. 192.0.2.1). +#listening_ip= + +# Port number for HTTP traffic (descriptions, SOAP, media transfer). +port=8200 + +# URL presented to clients. +# The default is the IP address of the server on port 80. +presentation_url=http://192.168.10.110:80 + +# Name that the DLNA server presents to clients. +#friendly_name=RuneAudio + +# Serial number the server reports to clients. +serial=12345678 + +# Model name the server reports to clients. +#model_name=Windows Media Connect compatible (MiniDLNA) + +# Model number the server reports to clients. +model_number=1 + +# Automatic discovery of new files in the media_dir directory. +inotify=no + +# List of file names to look for when searching for album art. Names should be +# delimited with a forward slash ("/"). +#album_art_names=Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg/AlbumArt.jpg/albumart.jpg/Album.jpg/album.jpg/Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg + +# Strictly adhere to DLNA standards. +# This allows server-side downscaling of very large JPEG images, which may +# decrease JPEG serving performance on (at least) Sony DLNA products. +#strict_dlna=no + +# Support for streaming .jpg and .mp3 files to a TiVo supporting HMO. +#enable_tivo=no + +# Notify interval, in seconds. +#notify_interval=895 + +# Path to the MiniSSDPd socket, for MiniSSDPd support. +#minissdpdsocket=/run/minissdpd.sock + diff --git a/app/config/_os/etc/modprobe.d/snd-soc-pcm512x.conf b/app/config/_os/etc/modprobe.d/snd-soc-pcm512x.conf index 7a503594..da352be1 100644 --- a/app/config/_os/etc/modprobe.d/snd-soc-pcm512x.conf +++ b/app/config/_os/etc/modprobe.d/snd-soc-pcm512x.conf @@ -1 +1 @@ -blacklist snd-soc-pcm512x +blacklist snd-soc-pcm512x diff --git a/app/config/_os/etc/motd b/app/config/_os/etc/motd index 3d7e3514..38a58f8e 100644 --- a/app/config/_os/etc/motd +++ b/app/config/_os/etc/motd @@ -1,12 +1,17 @@ -=============== RuneOS distribution =============== - ____ _ _ _ - | _ \ _ _ _ __ ___ / \ _ _ __| (_) ___ - | |_) | | | | '_ \ / _ \ / _ \| | | |/ _` | |/ _ \ - | _ <| |_| | | | | __// ___ \ |_| | (_| | | (_) | - |_| \_\\__,_|_| |_|\___/_/ \_\__,_|\__,_|_|\___/ - -================ www.runeaudio.com ================ -RuneOs: 0.3-alpha (build 20140709) -RuneUI: 1.3-alpha -Hw-env: RaspberryPi - + +======================= RuneOS distribution ======================= + + _ ___ _ _ _ + /\/\ _ _ ___(_) ___ __ _ / _ \_ __(_)___| |_(_)_ __ __ _ + / \| | | / __| |/ __/ _` | / /_)/ '__| / __| __| | '_ \ / _` | +/ /\/\ \ |_| \__ \ | (_| (_| | / ___/| | | \__ \ |_| | | | | (_| | +\/ \/\__,_|___/_|\___\__,_| \/ |_| |_|___/\__|_|_| |_|\__,_| + + + +====================== www.musicapristina.com ===================== + +RuneOs: 0.3-alpha (build 20140709) +RuneUI: 1.3-alpha +Hw-env: Musica Pristina A Cappella II + diff --git a/app/config/_os/etc/mpd.conf b/app/config/_os/etc/mpd.conf index a86d7947..4f976b8f 100644 --- a/app/config/_os/etc/mpd.conf +++ b/app/config/_os/etc/mpd.conf @@ -1,56 +1,56 @@ -################################### -# Auto generated mpd.conf file -# please DO NOT edit it manually! -# Use RuneUI MPD config section -################################### - -follow_outside_symlinks "yes" -follow_inside_symlinks "yes" -db_file "/var/lib/mpd/mpd.db" -sticker_file "/var/lib/mpd/sticker.sql" -log_file "/var/log/runeaudio/mpd.log" -pid_file "/var/run/mpd/pid" -music_directory "/mnt/MPD" -playlist_directory "/var/lib/mpd/playlists" -state_file "/var/lib/mpd/mpdstate" -user "mpd" -bind_to_address "any" -port "6600" -bind_to_address "/run/mpd.sock" -log_level "verbose" -zeroconf_enabled "yes" -zeroconf_name "runeaudio" -volume_normalization "no" -audio_buffer_size "2048" -buffer_before_play "20%" -filesystem_charset "UTF-8" -id3v1_encoding "UTF-8" -gapless_mp3_playback "yes" -auto_update "no" -mixer_type "software" -group "audio" -max_connections "20" - -decoder { - plugin "ffmpeg" - enabled "no" -} - -input { - plugin "curl" -} - -audio_output { -enabled "yes" -type "alsa" -name "AnalogOut" -device "hw:0,0" -dsd_usb "yes" -} - -audio_output { -enabled "no" -type "alsa" -device "hw:2,0" -name "HDMI" -} +################################### +# Auto generated mpd.conf file +# please DO NOT edit it manually! +# Use RuneUI MPD config section +################################### + +follow_outside_symlinks "yes" +follow_inside_symlinks "yes" +db_file "/var/lib/mpd/mpd.db" +sticker_file "/var/lib/mpd/sticker.sql" +log_file "/var/log/runeaudio/mpd.log" +pid_file "/var/run/mpd/pid" +music_directory "/mnt/MPD" +playlist_directory "/var/lib/mpd/playlists" +state_file "/var/lib/mpd/mpdstate" +user "mpd" +bind_to_address "any" +port "6600" +bind_to_address "/run/mpd.sock" +log_level "verbose" +zeroconf_enabled "yes" +zeroconf_name "runeaudio" +volume_normalization "no" +audio_buffer_size "2048" +buffer_before_play "20%" +filesystem_charset "UTF-8" +id3v1_encoding "UTF-8" +gapless_mp3_playback "yes" +auto_update "no" +mixer_type "software" +group "audio" +max_connections "20" + +decoder { + plugin "ffmpeg" + enabled "no" +} + +input { + plugin "curl" +} + +audio_output { +enabled "yes" +type "alsa" +name "AnalogOut" +device "hw:0,0" +dsd_usb "yes" +} + +audio_output { +enabled "no" +type "alsa" +device "hw:2,0" +name "HDMI" +} diff --git a/app/config/_os/etc/mpd.conf_utilite b/app/config/_os/etc/mpd.conf_utilite index a86d7947..4f976b8f 100644 --- a/app/config/_os/etc/mpd.conf_utilite +++ b/app/config/_os/etc/mpd.conf_utilite @@ -1,56 +1,56 @@ -################################### -# Auto generated mpd.conf file -# please DO NOT edit it manually! -# Use RuneUI MPD config section -################################### - -follow_outside_symlinks "yes" -follow_inside_symlinks "yes" -db_file "/var/lib/mpd/mpd.db" -sticker_file "/var/lib/mpd/sticker.sql" -log_file "/var/log/runeaudio/mpd.log" -pid_file "/var/run/mpd/pid" -music_directory "/mnt/MPD" -playlist_directory "/var/lib/mpd/playlists" -state_file "/var/lib/mpd/mpdstate" -user "mpd" -bind_to_address "any" -port "6600" -bind_to_address "/run/mpd.sock" -log_level "verbose" -zeroconf_enabled "yes" -zeroconf_name "runeaudio" -volume_normalization "no" -audio_buffer_size "2048" -buffer_before_play "20%" -filesystem_charset "UTF-8" -id3v1_encoding "UTF-8" -gapless_mp3_playback "yes" -auto_update "no" -mixer_type "software" -group "audio" -max_connections "20" - -decoder { - plugin "ffmpeg" - enabled "no" -} - -input { - plugin "curl" -} - -audio_output { -enabled "yes" -type "alsa" -name "AnalogOut" -device "hw:0,0" -dsd_usb "yes" -} - -audio_output { -enabled "no" -type "alsa" -device "hw:2,0" -name "HDMI" -} +################################### +# Auto generated mpd.conf file +# please DO NOT edit it manually! +# Use RuneUI MPD config section +################################### + +follow_outside_symlinks "yes" +follow_inside_symlinks "yes" +db_file "/var/lib/mpd/mpd.db" +sticker_file "/var/lib/mpd/sticker.sql" +log_file "/var/log/runeaudio/mpd.log" +pid_file "/var/run/mpd/pid" +music_directory "/mnt/MPD" +playlist_directory "/var/lib/mpd/playlists" +state_file "/var/lib/mpd/mpdstate" +user "mpd" +bind_to_address "any" +port "6600" +bind_to_address "/run/mpd.sock" +log_level "verbose" +zeroconf_enabled "yes" +zeroconf_name "runeaudio" +volume_normalization "no" +audio_buffer_size "2048" +buffer_before_play "20%" +filesystem_charset "UTF-8" +id3v1_encoding "UTF-8" +gapless_mp3_playback "yes" +auto_update "no" +mixer_type "software" +group "audio" +max_connections "20" + +decoder { + plugin "ffmpeg" + enabled "no" +} + +input { + plugin "curl" +} + +audio_output { +enabled "yes" +type "alsa" +name "AnalogOut" +device "hw:0,0" +dsd_usb "yes" +} + +audio_output { +enabled "no" +type "alsa" +device "hw:2,0" +name "HDMI" +} diff --git a/app/config/_os/etc/mpdscribble.conf b/app/config/_os/etc/mpdscribble.conf index dba16c86..7090d930 100644 --- a/app/config/_os/etc/mpdscribble.conf +++ b/app/config/_os/etc/mpdscribble.conf @@ -1,55 +1,55 @@ -## mpdscribble - an audioscrobbler for the Music Player Daemon. -## http://mpd.wikia.com/wiki/Client:mpdscribble - -# HTTP proxy URL. -#proxy = http://the.proxy.server:3128 - -# The location of the pid file. mpdscribble saves its process id there. -pidfile = /run/mpdscribble.pid - -# Change to this system user after daemonization. -#daemon_user = mpdscribble - -# The location of the mpdscribble log file. The special value -# "syslog" makes mpdscribble use the local syslog daemon. On most -# systems, log messages will appear in /var/log/daemon.log then. -# "-" means log to stderr (the current terminal). -#log = syslog -log = /var/log/runeaudio/mpdscribble.log - -# How verbose mpdscribble's logging should be. Default is 1. -verbose = 2 - -# How often should mpdscribble save the journal file? [seconds] -#journal_interval = 600 - -# The host running MPD, possibly protected by a password -# ([PASSWORD@]HOSTNAME). Defaults to $MPD_HOST or localhost. -host = localhost - -# The port that the MPD listens on and mpdscribble should try to -# connect to. Defaults to $MPD_PORT or 6600. -port = 6600 - -[last.fm] -url = http://post.audioscrobbler.com/ -username = userid -password = password -# The file where mpdscribble should store its Last.fm journal in case -# you do not have a connection to the Last.fm server. -#journal = /run/mpdscribble/lastfm.journal - -#[libre.fm] -#url = http://turtle.libre.fm/ -#username = -#password = my_password -#journal = /run/mpdscribble/librefm.journal - -#[jamendo] -#url = http://postaudioscrobbler.jamendo.com/ -#username = -#password = my_password -#journal = /run/mpdscribble/jamendo.journal - -#[file] -#file = /run/mpdscribble/log +## mpdscribble - an audioscrobbler for the Music Player Daemon. +## http://mpd.wikia.com/wiki/Client:mpdscribble + +# HTTP proxy URL. +#proxy = http://the.proxy.server:3128 + +# The location of the pid file. mpdscribble saves its process id there. +pidfile = /run/mpdscribble.pid + +# Change to this system user after daemonization. +#daemon_user = mpdscribble + +# The location of the mpdscribble log file. The special value +# "syslog" makes mpdscribble use the local syslog daemon. On most +# systems, log messages will appear in /var/log/daemon.log then. +# "-" means log to stderr (the current terminal). +#log = syslog +log = /var/log/runeaudio/mpdscribble.log + +# How verbose mpdscribble's logging should be. Default is 1. +verbose = 2 + +# How often should mpdscribble save the journal file? [seconds] +#journal_interval = 600 + +# The host running MPD, possibly protected by a password +# ([PASSWORD@]HOSTNAME). Defaults to $MPD_HOST or localhost. +host = localhost + +# The port that the MPD listens on and mpdscribble should try to +# connect to. Defaults to $MPD_PORT or 6600. +port = 6600 + +[last.fm] +url = http://post.audioscrobbler.com/ +username = userid +password = password +# The file where mpdscribble should store its Last.fm journal in case +# you do not have a connection to the Last.fm server. +#journal = /run/mpdscribble/lastfm.journal + +#[libre.fm] +#url = http://turtle.libre.fm/ +#username = +#password = my_password +#journal = /run/mpdscribble/librefm.journal + +#[jamendo] +#url = http://postaudioscrobbler.jamendo.com/ +#username = +#password = my_password +#journal = /run/mpdscribble/jamendo.journal + +#[file] +#file = /run/mpdscribble/log diff --git a/app/config/_os/etc/netconfig b/app/config/_os/etc/netconfig index 4cc30c52..71ccce69 100644 --- a/app/config/_os/etc/netconfig +++ b/app/config/_os/etc/netconfig @@ -1,19 +1,19 @@ -# -# The network configuration file. This file is currently only used in -# conjunction with the TI-RPC code in the libtirpc library. -# -# Entries consist of: -# -# \ -# -# -# The and fields are always empty in this -# implementation. -# -udp tpi_clts v inet udp - - -tcp tpi_cots_ord v inet tcp - - -#udp6 tpi_clts v inet6 udp - - -#tcp6 tpi_cots_ord v inet6 tcp - - -rawip tpi_raw - inet - - - -local tpi_cots_ord - loopback - - - -unix tpi_cots_ord - loopback - - - +# +# The network configuration file. This file is currently only used in +# conjunction with the TI-RPC code in the libtirpc library. +# +# Entries consist of: +# +# \ +# +# +# The and fields are always empty in this +# implementation. +# +udp tpi_clts v inet udp - - +tcp tpi_cots_ord v inet tcp - - +#udp6 tpi_clts v inet6 udp - - +#tcp6 tpi_cots_ord v inet6 tcp - - +rawip tpi_raw - inet - - - +local tpi_cots_ord - loopback - - - +unix tpi_cots_ord - loopback - - - diff --git a/app/config/_os/etc/netctl/eth0-dhcp b/app/config/_os/etc/netctl/eth0-dhcp index 89d4e9d3..7fbac09b 100644 --- a/app/config/_os/etc/netctl/eth0-dhcp +++ b/app/config/_os/etc/netctl/eth0-dhcp @@ -1,7 +1,7 @@ -Description='dhcp ethernet connection' -Interface=eth0 -Connection=ethernet -AutoWired=yes -IP=dhcp -DHCPReleaseOnStop=yes -ExecUpPost='/usr/bin/ntpd -gq || true' +Description='dhcp ethernet connection' +Interface=eth0 +Connection=ethernet +AutoWired=yes +IP=dhcp +DHCPReleaseOnStop=yes +ExecUpPost='/usr/bin/ntpd -gq || true' diff --git a/app/config/_os/etc/netctl/eth0-static b/app/config/_os/etc/netctl/eth0-static index 4ea24a7d..19b305bd 100644 --- a/app/config/_os/etc/netctl/eth0-static +++ b/app/config/_os/etc/netctl/eth0-static @@ -1,8 +1,8 @@ -Description='static ethernet connection' -Interface=eth0 -Connection=ethernet -AutoWired=yes -IP=static -Address=('192.168.1.123/24') -Gateway='192.168.1.1' -DNS=('8.8.8.8' '8.8.4.4') +Description='static ethernet connection' +Interface=eth0 +Connection=ethernet +AutoWired=yes +IP=static +Address=('192.168.1.123/24') +Gateway='192.168.1.1' +DNS=('8.8.8.8' '8.8.4.4') diff --git a/app/config/_os/etc/netctl/wlan0-wpa_supplicant-multissid-dhcp b/app/config/_os/etc/netctl/wlan0-wpa_supplicant-multissid-dhcp index 616bdac2..13d32e50 100644 --- a/app/config/_os/etc/netctl/wlan0-wpa_supplicant-multissid-dhcp +++ b/app/config/_os/etc/netctl/wlan0-wpa_supplicant-multissid-dhcp @@ -1,6 +1,6 @@ -Description='encrypted wireless connection' -Interface=wlan0 -Connection=wireless -Security=wpa-config -WPAConfigFile='/etc/wpa_supplicant/wpa_supplicant.conf' +Description='encrypted wireless connection' +Interface=wlan0 +Connection=wireless +Security=wpa-config +WPAConfigFile='/etc/wpa_supplicant/wpa_supplicant.conf' IP=dhcp \ No newline at end of file diff --git a/app/config/_os/etc/netctl/wlan0-wpa_supplicant-multissid-static b/app/config/_os/etc/netctl/wlan0-wpa_supplicant-multissid-static index 8e6b8b1e..27bfa346 100644 --- a/app/config/_os/etc/netctl/wlan0-wpa_supplicant-multissid-static +++ b/app/config/_os/etc/netctl/wlan0-wpa_supplicant-multissid-static @@ -1,11 +1,11 @@ -Description='A simple WPA encrypted wireless connection' -Interface=wlan0 -Connection=wireless -ForceConnect=yes -SkipNoCarrier=yes -Security=wpa-config -WPAConfigFile='/etc/wpa_supplicant/wpa_supplicant.conf' -IP=static -Address=('172.31.2.108/24') -Gateway='172.31.2.254' -DNS=('8.8.8.8' '8.8.4.4') +Description='A simple WPA encrypted wireless connection' +Interface=wlan0 +Connection=wireless +ForceConnect=yes +SkipNoCarrier=yes +Security=wpa-config +WPAConfigFile='/etc/wpa_supplicant/wpa_supplicant.conf' +IP=static +Address=('172.31.2.108/24') +Gateway='172.31.2.254' +DNS=('8.8.8.8' '8.8.4.4') diff --git a/app/config/_os/etc/nginx/nginx-dev.conf b/app/config/_os/etc/nginx/nginx-dev.conf index cb09ea7c..1debc237 100644 --- a/app/config/_os/etc/nginx/nginx-dev.conf +++ b/app/config/_os/etc/nginx/nginx-dev.conf @@ -1,179 +1,179 @@ -user root users; -worker_processes 1; - -#error_log /var/log/runeaudio/runeui.log; -error_log /var/log/runeaudio/runeui.log debug; -#error_log /var/log/runeaudio/runeui.log info; - - -events { - worker_connections 1024; - use epoll; -} - - -http { - include mime.types; - #default_type application/octet-stream; - #access_log /var/log/runeaudio/runeui.log main; - sendfile on; - keepalive_timeout 0; - gzip off; - proxy_buffering off; - fastcgi_keep_conn on; - fastcgi_buffers 8 16k; - fastcgi_buffer_size 32k; - - # push directives - push_stream_shared_memory_size 16M; - #push_stream_channel_inactivity_time 0; - push_stream_channel_info_on_publish off; - #push_stream_timeout_with_body on; - - # DISPLAY section [/] - server { - listen 80 deferred; - add_header X-UA-Compatible "IE=Edge,chrome=1"; - access_log /var/log/runeaudio/runeui_access.log; - - location / { - root /var/www; - index index.php index.html index.htm; - try_files $uri /index.php; - } - - location ~* (.+)\.(?:\d+)\.(js|css|png|jpg|jpeg|gif|ico)$ { - try_files $uri $1.$2; - } - - location /pub { - # activate publisher (admin) mode for this location - push_stream_publisher admin; - # query string based channel id - push_stream_channels_path $arg_id; - } - - location ~ /lp/(.*) { - # activate subscriber (long-polling) - push_stream_subscriber long-polling; - # positional channel path - push_stream_channels_path $1; - # message template - push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":[~text~],\"tag\":~tag~,\"time\":\"~time~\"}"; - #push_stream_last_received_message_tag $arg_tag; - #push_stream_last_received_message_time $arg_time; - # connection timeout - #push_stream_longpolling_connection_ttl 60s; - #push_stream_ping_message_interval 30s; - } - - location ~ /ws/(.*) { - # activate subscriber (websocket) - push_stream_subscriber websocket; - # positional channel path - push_stream_channels_path $1; - # message template - push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":[~text~]}"; - #push_stream_websocket_allow_publish on; - # ping frequency - push_stream_ping_message_interval 10s; - } - - location /stats { - # activate channels statistics - push_stream_channels_statistics; - push_stream_channels_path $1; - # query string based channel id - set $push_stream_channel_id $arg_id; - } - - # proxy RUELS - location /db { - proxy_pass http://localhost:81/; - } - - location /command { - proxy_pass http://localhost:82/; - } - - location /test { - try_files $uri /test/index.php; - } - - location /clear { - try_files $uri /command/cachectl.php?action=reset; - } - - # rewrite RULES - rewrite /css/(.*) /assets/css/$1 break; - rewrite /less/(.*) /assets/less/$1 break; - rewrite /js/(.*) /assets/js/$1 break; - rewrite /img/(.*) /assets/img/$1 break; - rewrite /fonts/(.*) /assets/fonts/$1 break; - - # redirect server error pages to the static page /50x.html - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - # php5-fpm - location ~ \.php$ { - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; - root /var/www; - fastcgi_pass unix:/var/run/php-display.sock; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_read_timeout 3600; - } - - } # end DISPLAY section [/] - - # DB section [/db] - server { - listen 81 deferred; - access_log /var/log/runeaudio/runeui.log; - location / { - root /var/www/db; - index index.php; - } - # php5-fpm - location ~ \.php$ { - root /var/www/db; - #proxy_buffer_size 128k; - #proxy_buffers 4 256k; - #proxy_busy_buffers_size 256k; - fastcgi_pass unix:/var/run/php-db.sock; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_read_timeout 3600; - } - } # end DB section [/db] - - # - server { - listen 82 deferred; - access_log /var/log/runeaudio/runeui.log; - location / { - root /var/www/command; - index index.php; - } - # php5-fpm - location ~ \.php$ { - root /var/www/command; - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; - fastcgi_pass unix:/var/run/php-command.sock; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_read_timeout 3600; - } - } # end COMMAND section [/command] - -} # end http block - +user root users; +worker_processes 1; + +#error_log /var/log/runeaudio/runeui.log; +error_log /var/log/runeaudio/runeui.log debug; +#error_log /var/log/runeaudio/runeui.log info; + + +events { + worker_connections 1024; + use epoll; +} + + +http { + include mime.types; + #default_type application/octet-stream; + #access_log /var/log/runeaudio/runeui.log main; + sendfile on; + keepalive_timeout 0; + gzip off; + proxy_buffering off; + fastcgi_keep_conn on; + fastcgi_buffers 8 16k; + fastcgi_buffer_size 32k; + + # push directives + push_stream_shared_memory_size 16M; + #push_stream_channel_inactivity_time 0; + push_stream_channel_info_on_publish off; + #push_stream_timeout_with_body on; + + # DISPLAY section [/] + server { + listen 80 deferred; + add_header X-UA-Compatible "IE=Edge,chrome=1"; + access_log /var/log/runeaudio/runeui_access.log; + + location / { + root /var/www; + index index.php index.html index.htm; + try_files $uri /index.php; + } + + location ~* (.+)\.(?:\d+)\.(js|css|png|jpg|jpeg|gif|ico)$ { + try_files $uri $1.$2; + } + + location /pub { + # activate publisher (admin) mode for this location + push_stream_publisher admin; + # query string based channel id + push_stream_channels_path $arg_id; + } + + location ~ /lp/(.*) { + # activate subscriber (long-polling) + push_stream_subscriber long-polling; + # positional channel path + push_stream_channels_path $1; + # message template + push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":[~text~],\"tag\":~tag~,\"time\":\"~time~\"}"; + #push_stream_last_received_message_tag $arg_tag; + #push_stream_last_received_message_time $arg_time; + # connection timeout + #push_stream_longpolling_connection_ttl 60s; + #push_stream_ping_message_interval 30s; + } + + location ~ /ws/(.*) { + # activate subscriber (websocket) + push_stream_subscriber websocket; + # positional channel path + push_stream_channels_path $1; + # message template + push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":[~text~]}"; + #push_stream_websocket_allow_publish on; + # ping frequency + push_stream_ping_message_interval 10s; + } + + location /stats { + # activate channels statistics + push_stream_channels_statistics; + push_stream_channels_path $1; + # query string based channel id + set $push_stream_channel_id $arg_id; + } + + # proxy RUELS + location /db { + proxy_pass http://localhost:81/; + } + + location /command { + proxy_pass http://localhost:82/; + } + + location /test { + try_files $uri /test/index.php; + } + + location /clear { + try_files $uri /command/cachectl.php?action=reset; + } + + # rewrite RULES + rewrite /css/(.*) /assets/css/$1 break; + rewrite /less/(.*) /assets/less/$1 break; + rewrite /js/(.*) /assets/js/$1 break; + rewrite /img/(.*) /assets/img/$1 break; + rewrite /fonts/(.*) /assets/fonts/$1 break; + + # redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + # php5-fpm + location ~ \.php$ { + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + root /var/www; + fastcgi_pass unix:/var/run/php-display.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_read_timeout 3600; + } + + } # end DISPLAY section [/] + + # DB section [/db] + server { + listen 81 deferred; + access_log /var/log/runeaudio/runeui.log; + location / { + root /var/www/db; + index index.php; + } + # php5-fpm + location ~ \.php$ { + root /var/www/db; + #proxy_buffer_size 128k; + #proxy_buffers 4 256k; + #proxy_busy_buffers_size 256k; + fastcgi_pass unix:/var/run/php-db.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_read_timeout 3600; + } + } # end DB section [/db] + + # + server { + listen 82 deferred; + access_log /var/log/runeaudio/runeui.log; + location / { + root /var/www/command; + index index.php; + } + # php5-fpm + location ~ \.php$ { + root /var/www/command; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + fastcgi_pass unix:/var/run/php-command.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_read_timeout 3600; + } + } # end COMMAND section [/command] + +} # end http block + diff --git a/app/config/_os/etc/nginx/nginx.conf b/app/config/_os/etc/nginx/nginx.conf index aa5e94b6..484c5154 100644 --- a/app/config/_os/etc/nginx/nginx.conf +++ b/app/config/_os/etc/nginx/nginx.conf @@ -1,214 +1,214 @@ -user root users; -worker_processes 1; - -#error_log /var/log/runeaudio/runeui.log; -##error_log /var/log/runeaudio/runeui.log debug; -#error_log /var/log/runeaudio/runeui.log info; - - -events { - worker_connections 1024; - use epoll; -} - - -http { - include mime.types; - #default_type application/octet-stream; - #access_log /var/log/runeaudio/runeui.log main; - sendfile on; - keepalive_timeout 0; - proxy_buffering off; - fastcgi_keep_conn on; - fastcgi_buffers 8 16k; - fastcgi_buffer_size 32k; - - # push directives - push_stream_shared_memory_size 16M; - #push_stream_channel_inactivity_time 0; - push_stream_channel_info_on_publish off; - #push_stream_timeout_with_body on; - - # start DISPLAY section [/] - server { - listen 80 deferred; - add_header X-UA-Compatible "IE=Edge,chrome=1"; - #access_log /var/log/runeaudio/runeui_access.log; - - location / { - root /var/www; - index index.php index.html index.htm; - try_files $uri /index.php; - } - - location ~* (.+)\.(?:\d+)\.(js|css|png|jpg|jpeg|gif|ico)$ { - try_files $uri $1.$2; - } - - location /pub { - # activate publisher (admin) mode for this location - push_stream_publisher admin; - # query string based channel id - push_stream_channels_path $arg_id; - } - - location ~ /lp/(.*) { - # activate subscriber (long-polling) - push_stream_subscriber long-polling; - # positional channel path - push_stream_channels_path $1; - # message template - push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":[~text~],\"tag\":~tag~,\"time\":\"~time~\"}"; - #push_stream_last_received_message_tag $arg_tag; - #push_stream_last_received_message_time $arg_time; - # connection timeout - #push_stream_longpolling_connection_ttl 60s; - #push_stream_ping_message_interval 30s; - } - - location ~ /ws/(.*) { - # activate subscriber (websocket) - push_stream_subscriber websocket; - # positional channel path - push_stream_channels_path $1; - # message template - push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":[~text~]}"; - #push_stream_websocket_allow_publish on; - # ping frequency - push_stream_ping_message_interval 10s; - } - - location ~ /pl_lp/(.*) { - # activate subscriber (long-polling) - push_stream_subscriber long-polling; - # positional channel path - push_stream_channels_path $1; - # message template - push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":\"~text~\",\"tag\":~tag~,\"time\":\"~time~\"}"; - #push_stream_last_received_message_tag $arg_tag; - #push_stream_last_received_message_time $arg_time; - # connection timeout - #push_stream_longpolling_connection_ttl 60s; - #push_stream_ping_message_interval 30s; - } - - location ~ /pl_ws/(.*) { - # activate subscriber (websocket) - push_stream_subscriber websocket; - # positional channel path - push_stream_channels_path $1; - # message template - push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":\"~text~\"}"; - #push_stream_websocket_allow_publish on; - # ping frequency - push_stream_ping_message_interval 10s; - } - - location /stats { - # activate channels statistics - push_stream_channels_statistics; - push_stream_channels_path $1; - # query string based channel id - set $push_stream_channel_id $arg_id; - } - - # proxy RUELS - location /db { - proxy_pass http://127.0.0.1:81/; - } - - location /command { - proxy_pass http://127.0.0.1:82/; - } - - location /covers { - proxy_pass http://localhost:83/; - } - - location /clear { - try_files $uri /command/cachectl.php?action=reset; - } - - # rewrite RULES - rewrite /css/(.*) /assets/css/$1 break; - rewrite /less/(.*) /assets/less/$1 break; - rewrite /js/(.*) /assets/js/$1 break; - rewrite /img/(.*) /assets/img/$1 break; - rewrite /fonts/(.*) /assets/fonts/$1 break; - - # redirect server error pages to the static page /50x.html - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - - # php5-fpm - location ~ \.php$ { - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; - root /var/www; - fastcgi_pass unix:/var/run/php-display.sock; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_read_timeout 3600; - } - - } # end DISPLAY section [/] - - # start DB section [/db] - server { - listen 81 deferred; - #access_log /var/log/runeaudio/runeui.log; - location / { - root /var/www/db; - index index.php; - } - # php5-fpm - location ~ \.php$ { - root /var/www/db; - #proxy_buffer_size 128k; - #proxy_buffers 4 256k; - #proxy_busy_buffers_size 256k; - fastcgi_pass unix:/var/run/php-db.sock; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_read_timeout 3600; - } - } # end DB section [/db] - - # start COMMAND section [/command] - server { - listen 82 deferred; - #access_log /var/log/runeaudio/runeui.log; - location / { - root /var/www/command; - index index.php; - } - # php5-fpm - location ~ \.php$ { - root /var/www/command; - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; - fastcgi_pass unix:/var/run/php-command.sock; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $request_filename; - fastcgi_read_timeout 3600; - } - } # end COMMAND section [/command] - - # start COVERS section [/covers] - server { - listen 83; - root /mnt/MPD; - location ~* \.(jpg|png|jpeg|bmp) { - expires 7d; - } - } # end COVERS section - -} # end http block - +user root users; +worker_processes 1; + +#error_log /var/log/runeaudio/runeui.log; +##error_log /var/log/runeaudio/runeui.log debug; +#error_log /var/log/runeaudio/runeui.log info; + + +events { + worker_connections 1024; + use epoll; +} + + +http { + include mime.types; + #default_type application/octet-stream; + #access_log /var/log/runeaudio/runeui.log main; + sendfile on; + keepalive_timeout 0; + proxy_buffering off; + fastcgi_keep_conn on; + fastcgi_buffers 8 16k; + fastcgi_buffer_size 32k; + + # push directives + push_stream_shared_memory_size 16M; + #push_stream_channel_inactivity_time 0; + push_stream_channel_info_on_publish off; + #push_stream_timeout_with_body on; + + # start DISPLAY section [/] + server { + listen 80 deferred; + add_header X-UA-Compatible "IE=Edge,chrome=1"; + #access_log /var/log/runeaudio/runeui_access.log; + + location / { + root /var/www; + index index.php index.html index.htm; + try_files $uri /index.php; + } + + location ~* (.+)\.(?:\d+)\.(js|css|png|jpg|jpeg|gif|ico)$ { + try_files $uri $1.$2; + } + + location /pub { + # activate publisher (admin) mode for this location + push_stream_publisher admin; + # query string based channel id + push_stream_channels_path $arg_id; + } + + location ~ /lp/(.*) { + # activate subscriber (long-polling) + push_stream_subscriber long-polling; + # positional channel path + push_stream_channels_path $1; + # message template + push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":[~text~],\"tag\":~tag~,\"time\":\"~time~\"}"; + #push_stream_last_received_message_tag $arg_tag; + #push_stream_last_received_message_time $arg_time; + # connection timeout + #push_stream_longpolling_connection_ttl 60s; + #push_stream_ping_message_interval 30s; + } + + location ~ /ws/(.*) { + # activate subscriber (websocket) + push_stream_subscriber websocket; + # positional channel path + push_stream_channels_path $1; + # message template + push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":[~text~]}"; + #push_stream_websocket_allow_publish on; + # ping frequency + push_stream_ping_message_interval 10s; + } + + location ~ /pl_lp/(.*) { + # activate subscriber (long-polling) + push_stream_subscriber long-polling; + # positional channel path + push_stream_channels_path $1; + # message template + push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":\"~text~\",\"tag\":~tag~,\"time\":\"~time~\"}"; + #push_stream_last_received_message_tag $arg_tag; + #push_stream_last_received_message_time $arg_time; + # connection timeout + #push_stream_longpolling_connection_ttl 60s; + #push_stream_ping_message_interval 30s; + } + + location ~ /pl_ws/(.*) { + # activate subscriber (websocket) + push_stream_subscriber websocket; + # positional channel path + push_stream_channels_path $1; + # message template + push_stream_message_template "{\"id\":~id~,\"channel\":\"~channel~\",\"text\":\"~text~\"}"; + #push_stream_websocket_allow_publish on; + # ping frequency + push_stream_ping_message_interval 10s; + } + + location /stats { + # activate channels statistics + push_stream_channels_statistics; + push_stream_channels_path $1; + # query string based channel id + set $push_stream_channel_id $arg_id; + } + + # proxy RUELS + location /db { + proxy_pass http://127.0.0.1:81/; + } + + location /command { + proxy_pass http://127.0.0.1:82/; + } + + location /covers { + proxy_pass http://localhost:83/; + } + + location /clear { + try_files $uri /command/cachectl.php?action=reset; + } + + # rewrite RULES + rewrite /css/(.*) /assets/css/$1 break; + rewrite /less/(.*) /assets/less/$1 break; + rewrite /js/(.*) /assets/js/$1 break; + rewrite /img/(.*) /assets/img/$1 break; + rewrite /fonts/(.*) /assets/fonts/$1 break; + + # redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + # php5-fpm + location ~ \.php$ { + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + root /var/www; + fastcgi_pass unix:/var/run/php-display.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_read_timeout 3600; + } + + } # end DISPLAY section [/] + + # start DB section [/db] + server { + listen 81 deferred; + #access_log /var/log/runeaudio/runeui.log; + location / { + root /var/www/db; + index index.php; + } + # php5-fpm + location ~ \.php$ { + root /var/www/db; + #proxy_buffer_size 128k; + #proxy_buffers 4 256k; + #proxy_busy_buffers_size 256k; + fastcgi_pass unix:/var/run/php-db.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_read_timeout 3600; + } + } # end DB section [/db] + + # start COMMAND section [/command] + server { + listen 82 deferred; + #access_log /var/log/runeaudio/runeui.log; + location / { + root /var/www/command; + index index.php; + } + # php5-fpm + location ~ \.php$ { + root /var/www/command; + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + fastcgi_pass unix:/var/run/php-command.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $request_filename; + fastcgi_read_timeout 3600; + } + } # end COMMAND section [/command] + + # start COVERS section [/covers] + server { + listen 83; + root /mnt/MPD; + location ~* \.(jpg|png|jpeg|bmp) { + expires 7d; + } + } # end COVERS section + +} # end http block + diff --git a/app/config/_os/etc/php/fpm.d/command.conf b/app/config/_os/etc/php/fpm.d/command.conf index f6efcb8e..c2b64005 100644 --- a/app/config/_os/etc/php/fpm.d/command.conf +++ b/app/config/_os/etc/php/fpm.d/command.conf @@ -1,9 +1,9 @@ -[command] -user = http -group = http -listen = /var/run/php-command.sock -;listen = 127.0.0.1:9002 -pm = static -pm.start_servers = 1 -pm.max_children = 1 -chdir = / +[command] +user = http +group = http +listen = /var/run/php-command.sock +;listen = 127.0.0.1:9002 +pm = static +pm.start_servers = 1 +pm.max_children = 1 +chdir = / diff --git a/app/config/_os/etc/php/fpm.d/db.conf b/app/config/_os/etc/php/fpm.d/db.conf index 7d084a3e..37fea774 100644 --- a/app/config/_os/etc/php/fpm.d/db.conf +++ b/app/config/_os/etc/php/fpm.d/db.conf @@ -1,9 +1,9 @@ -[db] -user = http -group = http -listen = /var/run/php-db.sock -;listen = 127.0.0.1:9001 -pm = static -pm.start_servers = 1 -pm.max_children = 1 -chdir = / +[db] +user = http +group = http +listen = /var/run/php-db.sock +;listen = 127.0.0.1:9001 +pm = static +pm.start_servers = 1 +pm.max_children = 1 +chdir = / diff --git a/app/config/_os/etc/php/fpm.d/display.conf b/app/config/_os/etc/php/fpm.d/display.conf index 77843d84..2e9f9d0c 100644 --- a/app/config/_os/etc/php/fpm.d/display.conf +++ b/app/config/_os/etc/php/fpm.d/display.conf @@ -1,14 +1,14 @@ -[display] -user = http -group = http -listen = /var/run/php-display.sock -pm = static -pm.max_children = 1 -pm.start_servers = 1 -chdir = / -;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com -;php_flag[display_errors] = off -;php_admin_value[error_log] = /var/log/fpm-php.www.log -;php_admin_flag[log_errors] = on -;php_admin_value[memory_limit] = 32M - +[display] +user = http +group = http +listen = /var/run/php-display.sock +pm = static +pm.max_children = 1 +pm.start_servers = 1 +chdir = / +;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com +;php_flag[display_errors] = off +;php_admin_value[error_log] = /var/log/fpm-php.www.log +;php_admin_flag[log_errors] = on +;php_admin_value[memory_limit] = 32M + diff --git a/app/config/_os/etc/php/php-fpm.conf b/app/config/_os/etc/php/php-fpm.conf index 33326fc3..3389189a 100644 --- a/app/config/_os/etc/php/php-fpm.conf +++ b/app/config/_os/etc/php/php-fpm.conf @@ -1,117 +1,117 @@ -;;;;;;;;;;;;;;;;;;;;; -; FPM Configuration ; -;;;;;;;;;;;;;;;;;;;;; - -; All relative paths in this configuration file are relative to PHP's install -; prefix (/usr). This prefix can be dynamically changed by using the -; '-p' argument from the command line. - -; Include one or more files. If glob(3) exists, it is used to include a bunch of -; files from a glob(3) pattern. This directive can be used everywhere in the -; file. -; Relative path can also be used. They will be prefixed by: -; - the global prefix if it's been set (-p argument) -; - /usr otherwise -include=/etc/php/fpm.d/*.conf - -;;;;;;;;;;;;;;;;;; -; Global Options ; -;;;;;;;;;;;;;;;;;; - -[global] -; Pid file -; Note: the default prefix is /var -; Default Value: none -pid = /run/php-fpm/php-fpm.pid - -; Error log file -; If it's set to "syslog", log is sent to syslogd instead of being written -; in a local file. -; Note: the default prefix is /var -; Default Value: log/php-fpm.log -;error_log = log/php-fpm.log - -; syslog_facility is used to specify what type of program is logging the -; message. This lets syslogd specify that messages from different facilities -; will be handled differently. -; See syslog(3) for possible values (ex daemon equiv LOG_DAEMON) -; Default Value: daemon -;syslog.facility = daemon - -; syslog_ident is prepended to every message. If you have multiple FPM -; instances running on the same server, you can change the default value -; which must suit common needs. -; Default Value: php-fpm -;syslog.ident = php-fpm - -; Log level -; Possible Values: alert, error, warning, notice, debug -; Default Value: notice -;log_level = notice - -; If this number of child processes exit with SIGSEGV or SIGBUS within the time -; interval set by emergency_restart_interval then FPM will restart. A value -; of '0' means 'Off'. -; Default Value: 0 -;emergency_restart_threshold = 0 - -; Interval of time used by emergency_restart_interval to determine when -; a graceful restart will be initiated. This can be useful to work around -; accidental corruptions in an accelerator's shared memory. -; Available Units: s(econds), m(inutes), h(ours), or d(ays) -; Default Unit: seconds -; Default Value: 0 -;emergency_restart_interval = 0 - -; Time limit for child processes to wait for a reaction on signals from master. -; Available units: s(econds), m(inutes), h(ours), or d(ays) -; Default Unit: seconds -; Default Value: 0 -;process_control_timeout = 0 - -; The maximum number of processes FPM will fork. This has been design to control -; the global number of processes when using dynamic PM within a lot of pools. -; Use it with caution. -; Note: A value of 0 indicates no limit -; Default Value: 0 -; process.max = 128 - -; Specify the nice(2) priority to apply to the master process (only if set) -; The value can vary from -19 (highest priority) to 20 (lower priority) -; Note: - It will only work if the FPM master process is launched as root -; - The pool process will inherit the master process priority -; unless it specified otherwise -; Default Value: no set -; process.priority = -19 - -; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging. -; Default Value: yes -;daemonize = yes - -; Set open file descriptor rlimit for the master process. -; Default Value: system defined value -;rlimit_files = 1024 - -; Set max core size rlimit for the master process. -; Possible Values: 'unlimited' or an integer greater or equal to 0 -; Default Value: system defined value -;rlimit_core = 0 - -; Specify the event mechanism FPM will use. The following is available: -; - select (any POSIX os) -; - poll (any POSIX os) -; - epoll (linux >= 2.5.44) -; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0) -; - /dev/poll (Solaris >= 7) -; - port (Solaris >= 10) -; Default Value: not set (auto detection) -;events.mechanism = epoll - -; When FPM is build with systemd integration, specify the interval, -; in second, between health report notification to systemd. -; Set to 0 to disable. -; Available Units: s(econds), m(inutes), h(ours) -; Default Unit: seconds -; Default value: 10 -;systemd_interval = 10 - +;;;;;;;;;;;;;;;;;;;;; +; FPM Configuration ; +;;;;;;;;;;;;;;;;;;;;; + +; All relative paths in this configuration file are relative to PHP's install +; prefix (/usr). This prefix can be dynamically changed by using the +; '-p' argument from the command line. + +; Include one or more files. If glob(3) exists, it is used to include a bunch of +; files from a glob(3) pattern. This directive can be used everywhere in the +; file. +; Relative path can also be used. They will be prefixed by: +; - the global prefix if it's been set (-p argument) +; - /usr otherwise +include=/etc/php/fpm.d/*.conf + +;;;;;;;;;;;;;;;;;; +; Global Options ; +;;;;;;;;;;;;;;;;;; + +[global] +; Pid file +; Note: the default prefix is /var +; Default Value: none +pid = /run/php-fpm/php-fpm.pid + +; Error log file +; If it's set to "syslog", log is sent to syslogd instead of being written +; in a local file. +; Note: the default prefix is /var +; Default Value: log/php-fpm.log +;error_log = log/php-fpm.log + +; syslog_facility is used to specify what type of program is logging the +; message. This lets syslogd specify that messages from different facilities +; will be handled differently. +; See syslog(3) for possible values (ex daemon equiv LOG_DAEMON) +; Default Value: daemon +;syslog.facility = daemon + +; syslog_ident is prepended to every message. If you have multiple FPM +; instances running on the same server, you can change the default value +; which must suit common needs. +; Default Value: php-fpm +;syslog.ident = php-fpm + +; Log level +; Possible Values: alert, error, warning, notice, debug +; Default Value: notice +;log_level = notice + +; If this number of child processes exit with SIGSEGV or SIGBUS within the time +; interval set by emergency_restart_interval then FPM will restart. A value +; of '0' means 'Off'. +; Default Value: 0 +;emergency_restart_threshold = 0 + +; Interval of time used by emergency_restart_interval to determine when +; a graceful restart will be initiated. This can be useful to work around +; accidental corruptions in an accelerator's shared memory. +; Available Units: s(econds), m(inutes), h(ours), or d(ays) +; Default Unit: seconds +; Default Value: 0 +;emergency_restart_interval = 0 + +; Time limit for child processes to wait for a reaction on signals from master. +; Available units: s(econds), m(inutes), h(ours), or d(ays) +; Default Unit: seconds +; Default Value: 0 +;process_control_timeout = 0 + +; The maximum number of processes FPM will fork. This has been design to control +; the global number of processes when using dynamic PM within a lot of pools. +; Use it with caution. +; Note: A value of 0 indicates no limit +; Default Value: 0 +; process.max = 128 + +; Specify the nice(2) priority to apply to the master process (only if set) +; The value can vary from -19 (highest priority) to 20 (lower priority) +; Note: - It will only work if the FPM master process is launched as root +; - The pool process will inherit the master process priority +; unless it specified otherwise +; Default Value: no set +; process.priority = -19 + +; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging. +; Default Value: yes +;daemonize = yes + +; Set open file descriptor rlimit for the master process. +; Default Value: system defined value +;rlimit_files = 1024 + +; Set max core size rlimit for the master process. +; Possible Values: 'unlimited' or an integer greater or equal to 0 +; Default Value: system defined value +;rlimit_core = 0 + +; Specify the event mechanism FPM will use. The following is available: +; - select (any POSIX os) +; - poll (any POSIX os) +; - epoll (linux >= 2.5.44) +; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0) +; - /dev/poll (Solaris >= 7) +; - port (Solaris >= 10) +; Default Value: not set (auto detection) +;events.mechanism = epoll + +; When FPM is build with systemd integration, specify the interval, +; in second, between health report notification to systemd. +; Set to 0 to disable. +; Available Units: s(econds), m(inutes), h(ours) +; Default Unit: seconds +; Default value: 10 +;systemd_interval = 10 + diff --git a/app/config/_os/etc/php/php-fpm.conf.default b/app/config/_os/etc/php/php-fpm.conf.default index 223b6335..96a327b0 100644 --- a/app/config/_os/etc/php/php-fpm.conf.default +++ b/app/config/_os/etc/php/php-fpm.conf.default @@ -1,519 +1,519 @@ -;;;;;;;;;;;;;;;;;;;;; -; FPM Configuration ; -;;;;;;;;;;;;;;;;;;;;; - -; All relative paths in this configuration file are relative to PHP's install -; prefix (/usr). This prefix can be dynamically changed by using the -; '-p' argument from the command line. - -; Include one or more files. If glob(3) exists, it is used to include a bunch of -; files from a glob(3) pattern. This directive can be used everywhere in the -; file. -; Relative path can also be used. They will be prefixed by: -; - the global prefix if it's been set (-p argument) -; - /usr otherwise -;include=/etc/php/fpm.d/*.conf - -;;;;;;;;;;;;;;;;;; -; Global Options ; -;;;;;;;;;;;;;;;;;; - -[global] -; Pid file -; Note: the default prefix is /var -; Default Value: none -pid = /run/php-fpm/php-fpm.pid - -; Error log file -; If it's set to "syslog", log is sent to syslogd instead of being written -; in a local file. -; Note: the default prefix is /var -; Default Value: log/php-fpm.log -;error_log = log/php-fpm.log - -; syslog_facility is used to specify what type of program is logging the -; message. This lets syslogd specify that messages from different facilities -; will be handled differently. -; See syslog(3) for possible values (ex daemon equiv LOG_DAEMON) -; Default Value: daemon -;syslog.facility = daemon - -; syslog_ident is prepended to every message. If you have multiple FPM -; instances running on the same server, you can change the default value -; which must suit common needs. -; Default Value: php-fpm -;syslog.ident = php-fpm - -; Log level -; Possible Values: alert, error, warning, notice, debug -; Default Value: notice -;log_level = notice - -; If this number of child processes exit with SIGSEGV or SIGBUS within the time -; interval set by emergency_restart_interval then FPM will restart. A value -; of '0' means 'Off'. -; Default Value: 0 -;emergency_restart_threshold = 0 - -; Interval of time used by emergency_restart_interval to determine when -; a graceful restart will be initiated. This can be useful to work around -; accidental corruptions in an accelerator's shared memory. -; Available Units: s(econds), m(inutes), h(ours), or d(ays) -; Default Unit: seconds -; Default Value: 0 -;emergency_restart_interval = 0 - -; Time limit for child processes to wait for a reaction on signals from master. -; Available units: s(econds), m(inutes), h(ours), or d(ays) -; Default Unit: seconds -; Default Value: 0 -;process_control_timeout = 0 - -; The maximum number of processes FPM will fork. This has been design to control -; the global number of processes when using dynamic PM within a lot of pools. -; Use it with caution. -; Note: A value of 0 indicates no limit -; Default Value: 0 -; process.max = 128 - -; Specify the nice(2) priority to apply to the master process (only if set) -; The value can vary from -19 (highest priority) to 20 (lower priority) -; Note: - It will only work if the FPM master process is launched as root -; - The pool process will inherit the master process priority -; unless it specified otherwise -; Default Value: no set -; process.priority = -19 - -; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging. -; Default Value: yes -;daemonize = yes - -; Set open file descriptor rlimit for the master process. -; Default Value: system defined value -;rlimit_files = 1024 - -; Set max core size rlimit for the master process. -; Possible Values: 'unlimited' or an integer greater or equal to 0 -; Default Value: system defined value -;rlimit_core = 0 - -; Specify the event mechanism FPM will use. The following is available: -; - select (any POSIX os) -; - poll (any POSIX os) -; - epoll (linux >= 2.5.44) -; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0) -; - /dev/poll (Solaris >= 7) -; - port (Solaris >= 10) -; Default Value: not set (auto detection) -;events.mechanism = epoll - -; When FPM is build with systemd integration, specify the interval, -; in second, between health report notification to systemd. -; Set to 0 to disable. -; Available Units: s(econds), m(inutes), h(ours) -; Default Unit: seconds -; Default value: 10 -;systemd_interval = 10 - -;;;;;;;;;;;;;;;;;;;; -; Pool Definitions ; -;;;;;;;;;;;;;;;;;;;; - -; Multiple pools of child processes may be started with different listening -; ports and different management options. The name of the pool will be -; used in logs and stats. There is no limitation on the number of pools which -; FPM can handle. Your system will tell you anyway :) - -; Start a new pool named 'www'. -; the variable $pool can we used in any directive and will be replaced by the -; pool name ('www' here) -[www] - -; Per pool prefix -; It only applies on the following directives: -; - 'slowlog' -; - 'listen' (unixsocket) -; - 'chroot' -; - 'chdir' -; - 'php_values' -; - 'php_admin_values' -; When not set, the global prefix (or /usr) applies instead. -; Note: This directive can also be relative to the global prefix. -; Default Value: none -;prefix = /path/to/pools/$pool - -; Unix user/group of processes -; Note: The user is mandatory. If the group is not set, the default user's group -; will be used. -user = http -group = http - -; The address on which to accept FastCGI requests. -; Valid syntaxes are: -; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific address on -; a specific port; -; 'port' - to listen on a TCP socket to all addresses on a -; specific port; -; '/path/to/unix/socket' - to listen on a unix socket. -; Note: This value is mandatory. -;listen = 127.0.0.1:9000 -listen = /run/php-fpm/php-fpm.sock - -; Set listen(2) backlog. -; Default Value: 65535 (-1 on FreeBSD and OpenBSD) -;listen.backlog = 65535 - -; Set permissions for unix socket, if one is used. In Linux, read/write -; permissions must be set in order to allow connections from a web server. Many -; BSD-derived systems allow connections regardless of permissions. -; Default Values: user and group are set as the running user -; mode is set to 0666 -listen.owner = http -listen.group = http -listen.mode = 0660 - -; List of ipv4 addresses of FastCGI clients which are allowed to connect. -; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original -; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address -; must be separated by a comma. If this value is left blank, connections will be -; accepted from any ip address. -; Default Value: any -;listen.allowed_clients = 127.0.0.1 - -; Specify the nice(2) priority to apply to the pool processes (only if set) -; The value can vary from -19 (highest priority) to 20 (lower priority) -; Note: - It will only work if the FPM master process is launched as root -; - The pool processes will inherit the master process priority -; unless it specified otherwise -; Default Value: no set -; priority = -19 - -; Choose how the process manager will control the number of child processes. -; Possible Values: -; static - a fixed number (pm.max_children) of child processes; -; dynamic - the number of child processes are set dynamically based on the -; following directives. With this process management, there will be -; always at least 1 children. -; pm.max_children - the maximum number of children that can -; be alive at the same time. -; pm.start_servers - the number of children created on startup. -; pm.min_spare_servers - the minimum number of children in 'idle' -; state (waiting to process). If the number -; of 'idle' processes is less than this -; number then some children will be created. -; pm.max_spare_servers - the maximum number of children in 'idle' -; state (waiting to process). If the number -; of 'idle' processes is greater than this -; number then some children will be killed. -; ondemand - no children are created at startup. Children will be forked when -; new requests will connect. The following parameter are used: -; pm.max_children - the maximum number of children that -; can be alive at the same time. -; pm.process_idle_timeout - The number of seconds after which -; an idle process will be killed. -; Note: This value is mandatory. -pm = dynamic - -; The number of child processes to be created when pm is set to 'static' and the -; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. -; This value sets the limit on the number of simultaneous requests that will be -; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. -; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP -; CGI. The below defaults are based on a server without much resources. Don't -; forget to tweak pm.* to fit your needs. -; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' -; Note: This value is mandatory. -pm.max_children = 5 - -; The number of child processes created on startup. -; Note: Used only when pm is set to 'dynamic' -; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 -pm.start_servers = 2 - -; The desired minimum number of idle server processes. -; Note: Used only when pm is set to 'dynamic' -; Note: Mandatory when pm is set to 'dynamic' -pm.min_spare_servers = 1 - -; The desired maximum number of idle server processes. -; Note: Used only when pm is set to 'dynamic' -; Note: Mandatory when pm is set to 'dynamic' -pm.max_spare_servers = 3 - -; The number of seconds after which an idle process will be killed. -; Note: Used only when pm is set to 'ondemand' -; Default Value: 10s -;pm.process_idle_timeout = 10s; - -; The number of requests each child process should execute before respawning. -; This can be useful to work around memory leaks in 3rd party libraries. For -; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. -; Default Value: 0 -;pm.max_requests = 500 - -; The URI to view the FPM status page. If this value is not set, no URI will be -; recognized as a status page. It shows the following informations: -; pool - the name of the pool; -; process manager - static, dynamic or ondemand; -; start time - the date and time FPM has started; -; start since - number of seconds since FPM has started; -; accepted conn - the number of request accepted by the pool; -; listen queue - the number of request in the queue of pending -; connections (see backlog in listen(2)); -; max listen queue - the maximum number of requests in the queue -; of pending connections since FPM has started; -; listen queue len - the size of the socket queue of pending connections; -; idle processes - the number of idle processes; -; active processes - the number of active processes; -; total processes - the number of idle + active processes; -; max active processes - the maximum number of active processes since FPM -; has started; -; max children reached - number of times, the process limit has been reached, -; when pm tries to start more children (works only for -; pm 'dynamic' and 'ondemand'); -; Value are updated in real time. -; Example output: -; pool: www -; process manager: static -; start time: 01/Jul/2011:17:53:49 +0200 -; start since: 62636 -; accepted conn: 190460 -; listen queue: 0 -; max listen queue: 1 -; listen queue len: 42 -; idle processes: 4 -; active processes: 11 -; total processes: 15 -; max active processes: 12 -; max children reached: 0 -; -; By default the status page output is formatted as text/plain. Passing either -; 'html', 'xml' or 'json' in the query string will return the corresponding -; output syntax. Example: -; http://www.foo.bar/status -; http://www.foo.bar/status?json -; http://www.foo.bar/status?html -; http://www.foo.bar/status?xml -; -; By default the status page only outputs short status. Passing 'full' in the -; query string will also return status for each pool process. -; Example: -; http://www.foo.bar/status?full -; http://www.foo.bar/status?json&full -; http://www.foo.bar/status?html&full -; http://www.foo.bar/status?xml&full -; The Full status returns for each process: -; pid - the PID of the process; -; state - the state of the process (Idle, Running, ...); -; start time - the date and time the process has started; -; start since - the number of seconds since the process has started; -; requests - the number of requests the process has served; -; request duration - the duration in µs of the requests; -; request method - the request method (GET, POST, ...); -; request URI - the request URI with the query string; -; content length - the content length of the request (only with POST); -; user - the user (PHP_AUTH_USER) (or '-' if not set); -; script - the main script called (or '-' if not set); -; last request cpu - the %cpu the last request consumed -; it's always 0 if the process is not in Idle state -; because CPU calculation is done when the request -; processing has terminated; -; last request memory - the max amount of memory the last request consumed -; it's always 0 if the process is not in Idle state -; because memory calculation is done when the request -; processing has terminated; -; If the process is in Idle state, then informations are related to the -; last request the process has served. Otherwise informations are related to -; the current request being served. -; Example output: -; ************************ -; pid: 31330 -; state: Running -; start time: 01/Jul/2011:17:53:49 +0200 -; start since: 63087 -; requests: 12808 -; request duration: 1250261 -; request method: GET -; request URI: /test_mem.php?N=10000 -; content length: 0 -; user: - -; script: /home/fat/web/docs/php/test_mem.php -; last request cpu: 0.00 -; last request memory: 0 -; -; Note: There is a real-time FPM status monitoring sample web page available -; It's available in: ${prefix}/share/fpm/status.html -; -; Note: The value must start with a leading slash (/). The value can be -; anything, but it may not be a good idea to use the .php extension or it -; may conflict with a real PHP file. -; Default Value: not set -;pm.status_path = /status - -; The ping URI to call the monitoring page of FPM. If this value is not set, no -; URI will be recognized as a ping page. This could be used to test from outside -; that FPM is alive and responding, or to -; - create a graph of FPM availability (rrd or such); -; - remove a server from a group if it is not responding (load balancing); -; - trigger alerts for the operating team (24/7). -; Note: The value must start with a leading slash (/). The value can be -; anything, but it may not be a good idea to use the .php extension or it -; may conflict with a real PHP file. -; Default Value: not set -;ping.path = /ping - -; This directive may be used to customize the response of a ping request. The -; response is formatted as text/plain with a 200 response code. -; Default Value: pong -;ping.response = pong - -; The access log file -; Default: not set -;access.log = log/$pool.access.log - -; The access log format. -; The following syntax is allowed -; %%: the '%' character -; %C: %CPU used by the request -; it can accept the following format: -; - %{user}C for user CPU only -; - %{system}C for system CPU only -; - %{total}C for user + system CPU (default) -; %d: time taken to serve the request -; it can accept the following format: -; - %{seconds}d (default) -; - %{miliseconds}d -; - %{mili}d -; - %{microseconds}d -; - %{micro}d -; %e: an environment variable (same as $_ENV or $_SERVER) -; it must be associated with embraces to specify the name of the env -; variable. Some exemples: -; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e -; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e -; %f: script filename -; %l: content-length of the request (for POST request only) -; %m: request method -; %M: peak of memory allocated by PHP -; it can accept the following format: -; - %{bytes}M (default) -; - %{kilobytes}M -; - %{kilo}M -; - %{megabytes}M -; - %{mega}M -; %n: pool name -; %o: output header -; it must be associated with embraces to specify the name of the header: -; - %{Content-Type}o -; - %{X-Powered-By}o -; - %{Transfert-Encoding}o -; - .... -; %p: PID of the child that serviced the request -; %P: PID of the parent of the child that serviced the request -; %q: the query string -; %Q: the '?' character if query string exists -; %r: the request URI (without the query string, see %q and %Q) -; %R: remote IP address -; %s: status (response code) -; %t: server time the request was received -; it can accept a strftime(3) format: -; %d/%b/%Y:%H:%M:%S %z (default) -; %T: time the log has been written (the request has finished) -; it can accept a strftime(3) format: -; %d/%b/%Y:%H:%M:%S %z (default) -; %u: remote user -; -; Default: "%R - %u %t \"%m %r\" %s" -;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" - -; The log file for slow requests -; Default Value: not set -; Note: slowlog is mandatory if request_slowlog_timeout is set -;slowlog = log/$pool.log.slow - -; The timeout for serving a single request after which a PHP backtrace will be -; dumped to the 'slowlog' file. A value of '0s' means 'off'. -; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) -; Default Value: 0 -;request_slowlog_timeout = 0 - -; The timeout for serving a single request after which the worker process will -; be killed. This option should be used when the 'max_execution_time' ini option -; does not stop script execution for some reason. A value of '0' means 'off'. -; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) -; Default Value: 0 -;request_terminate_timeout = 0 - -; Set open file descriptor rlimit. -; Default Value: system defined value -;rlimit_files = 1024 - -; Set max core size rlimit. -; Possible Values: 'unlimited' or an integer greater or equal to 0 -; Default Value: system defined value -;rlimit_core = 0 - -; Chroot to this directory at the start. This value must be defined as an -; absolute path. When this value is not set, chroot is not used. -; Note: you can prefix with '$prefix' to chroot to the pool prefix or one -; of its subdirectories. If the pool prefix is not set, the global prefix -; will be used instead. -; Note: chrooting is a great security feature and should be used whenever -; possible. However, all PHP paths will be relative to the chroot -; (error_log, sessions.save_path, ...). -; Default Value: not set -;chroot = - -; Chdir to this directory at the start. -; Note: relative path can be used. -; Default Value: current directory or / when chroot -;chdir = /srv/http - -; Redirect worker stdout and stderr into main error log. If not set, stdout and -; stderr will be redirected to /dev/null according to FastCGI specs. -; Note: on highloaded environement, this can cause some delay in the page -; process time (several ms). -; Default Value: no -;catch_workers_output = yes - -; Limits the extensions of the main script FPM will allow to parse. This can -; prevent configuration mistakes on the web server side. You should only limit -; FPM to .php extensions to prevent malicious users to use other extensions to -; exectute php code. -; Note: set an empty value to allow all extensions. -; Default Value: .php -;security.limit_extensions = .php .php3 .php4 .php5 - -; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from -; the current environment. -; Default Value: clean env -;env[HOSTNAME] = $HOSTNAME -;env[PATH] = /usr/local/bin:/usr/bin:/bin -;env[TMP] = /tmp -;env[TMPDIR] = /tmp -;env[TEMP] = /tmp - -; Additional php.ini defines, specific to this pool of workers. These settings -; overwrite the values previously defined in the php.ini. The directives are the -; same as the PHP SAPI: -; php_value/php_flag - you can set classic ini defines which can -; be overwritten from PHP call 'ini_set'. -; php_admin_value/php_admin_flag - these directives won't be overwritten by -; PHP call 'ini_set' -; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no. - -; Defining 'extension' will load the corresponding shared extension from -; extension_dir. Defining 'disable_functions' or 'disable_classes' will not -; overwrite previously defined php.ini values, but will append the new value -; instead. - -; Note: path INI options can be relative and will be expanded with the prefix -; (pool, global or /usr) - -; Default Value: nothing is defined by default except the values in php.ini and -; specified at startup with the -d argument -;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com -;php_flag[display_errors] = off -;php_admin_value[error_log] = /var/log/fpm-php.www.log -;php_admin_flag[log_errors] = on -;php_admin_value[memory_limit] = 32M +;;;;;;;;;;;;;;;;;;;;; +; FPM Configuration ; +;;;;;;;;;;;;;;;;;;;;; + +; All relative paths in this configuration file are relative to PHP's install +; prefix (/usr). This prefix can be dynamically changed by using the +; '-p' argument from the command line. + +; Include one or more files. If glob(3) exists, it is used to include a bunch of +; files from a glob(3) pattern. This directive can be used everywhere in the +; file. +; Relative path can also be used. They will be prefixed by: +; - the global prefix if it's been set (-p argument) +; - /usr otherwise +;include=/etc/php/fpm.d/*.conf + +;;;;;;;;;;;;;;;;;; +; Global Options ; +;;;;;;;;;;;;;;;;;; + +[global] +; Pid file +; Note: the default prefix is /var +; Default Value: none +pid = /run/php-fpm/php-fpm.pid + +; Error log file +; If it's set to "syslog", log is sent to syslogd instead of being written +; in a local file. +; Note: the default prefix is /var +; Default Value: log/php-fpm.log +;error_log = log/php-fpm.log + +; syslog_facility is used to specify what type of program is logging the +; message. This lets syslogd specify that messages from different facilities +; will be handled differently. +; See syslog(3) for possible values (ex daemon equiv LOG_DAEMON) +; Default Value: daemon +;syslog.facility = daemon + +; syslog_ident is prepended to every message. If you have multiple FPM +; instances running on the same server, you can change the default value +; which must suit common needs. +; Default Value: php-fpm +;syslog.ident = php-fpm + +; Log level +; Possible Values: alert, error, warning, notice, debug +; Default Value: notice +;log_level = notice + +; If this number of child processes exit with SIGSEGV or SIGBUS within the time +; interval set by emergency_restart_interval then FPM will restart. A value +; of '0' means 'Off'. +; Default Value: 0 +;emergency_restart_threshold = 0 + +; Interval of time used by emergency_restart_interval to determine when +; a graceful restart will be initiated. This can be useful to work around +; accidental corruptions in an accelerator's shared memory. +; Available Units: s(econds), m(inutes), h(ours), or d(ays) +; Default Unit: seconds +; Default Value: 0 +;emergency_restart_interval = 0 + +; Time limit for child processes to wait for a reaction on signals from master. +; Available units: s(econds), m(inutes), h(ours), or d(ays) +; Default Unit: seconds +; Default Value: 0 +;process_control_timeout = 0 + +; The maximum number of processes FPM will fork. This has been design to control +; the global number of processes when using dynamic PM within a lot of pools. +; Use it with caution. +; Note: A value of 0 indicates no limit +; Default Value: 0 +; process.max = 128 + +; Specify the nice(2) priority to apply to the master process (only if set) +; The value can vary from -19 (highest priority) to 20 (lower priority) +; Note: - It will only work if the FPM master process is launched as root +; - The pool process will inherit the master process priority +; unless it specified otherwise +; Default Value: no set +; process.priority = -19 + +; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging. +; Default Value: yes +;daemonize = yes + +; Set open file descriptor rlimit for the master process. +; Default Value: system defined value +;rlimit_files = 1024 + +; Set max core size rlimit for the master process. +; Possible Values: 'unlimited' or an integer greater or equal to 0 +; Default Value: system defined value +;rlimit_core = 0 + +; Specify the event mechanism FPM will use. The following is available: +; - select (any POSIX os) +; - poll (any POSIX os) +; - epoll (linux >= 2.5.44) +; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0) +; - /dev/poll (Solaris >= 7) +; - port (Solaris >= 10) +; Default Value: not set (auto detection) +;events.mechanism = epoll + +; When FPM is build with systemd integration, specify the interval, +; in second, between health report notification to systemd. +; Set to 0 to disable. +; Available Units: s(econds), m(inutes), h(ours) +; Default Unit: seconds +; Default value: 10 +;systemd_interval = 10 + +;;;;;;;;;;;;;;;;;;;; +; Pool Definitions ; +;;;;;;;;;;;;;;;;;;;; + +; Multiple pools of child processes may be started with different listening +; ports and different management options. The name of the pool will be +; used in logs and stats. There is no limitation on the number of pools which +; FPM can handle. Your system will tell you anyway :) + +; Start a new pool named 'www'. +; the variable $pool can we used in any directive and will be replaced by the +; pool name ('www' here) +[www] + +; Per pool prefix +; It only applies on the following directives: +; - 'slowlog' +; - 'listen' (unixsocket) +; - 'chroot' +; - 'chdir' +; - 'php_values' +; - 'php_admin_values' +; When not set, the global prefix (or /usr) applies instead. +; Note: This directive can also be relative to the global prefix. +; Default Value: none +;prefix = /path/to/pools/$pool + +; Unix user/group of processes +; Note: The user is mandatory. If the group is not set, the default user's group +; will be used. +user = http +group = http + +; The address on which to accept FastCGI requests. +; Valid syntaxes are: +; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific address on +; a specific port; +; 'port' - to listen on a TCP socket to all addresses on a +; specific port; +; '/path/to/unix/socket' - to listen on a unix socket. +; Note: This value is mandatory. +;listen = 127.0.0.1:9000 +listen = /run/php-fpm/php-fpm.sock + +; Set listen(2) backlog. +; Default Value: 65535 (-1 on FreeBSD and OpenBSD) +;listen.backlog = 65535 + +; Set permissions for unix socket, if one is used. In Linux, read/write +; permissions must be set in order to allow connections from a web server. Many +; BSD-derived systems allow connections regardless of permissions. +; Default Values: user and group are set as the running user +; mode is set to 0666 +listen.owner = http +listen.group = http +listen.mode = 0660 + +; List of ipv4 addresses of FastCGI clients which are allowed to connect. +; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original +; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address +; must be separated by a comma. If this value is left blank, connections will be +; accepted from any ip address. +; Default Value: any +;listen.allowed_clients = 127.0.0.1 + +; Specify the nice(2) priority to apply to the pool processes (only if set) +; The value can vary from -19 (highest priority) to 20 (lower priority) +; Note: - It will only work if the FPM master process is launched as root +; - The pool processes will inherit the master process priority +; unless it specified otherwise +; Default Value: no set +; priority = -19 + +; Choose how the process manager will control the number of child processes. +; Possible Values: +; static - a fixed number (pm.max_children) of child processes; +; dynamic - the number of child processes are set dynamically based on the +; following directives. With this process management, there will be +; always at least 1 children. +; pm.max_children - the maximum number of children that can +; be alive at the same time. +; pm.start_servers - the number of children created on startup. +; pm.min_spare_servers - the minimum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is less than this +; number then some children will be created. +; pm.max_spare_servers - the maximum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is greater than this +; number then some children will be killed. +; ondemand - no children are created at startup. Children will be forked when +; new requests will connect. The following parameter are used: +; pm.max_children - the maximum number of children that +; can be alive at the same time. +; pm.process_idle_timeout - The number of seconds after which +; an idle process will be killed. +; Note: This value is mandatory. +pm = dynamic + +; The number of child processes to be created when pm is set to 'static' and the +; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. +; This value sets the limit on the number of simultaneous requests that will be +; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. +; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP +; CGI. The below defaults are based on a server without much resources. Don't +; forget to tweak pm.* to fit your needs. +; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' +; Note: This value is mandatory. +pm.max_children = 5 + +; The number of child processes created on startup. +; Note: Used only when pm is set to 'dynamic' +; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 +pm.start_servers = 2 + +; The desired minimum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +pm.min_spare_servers = 1 + +; The desired maximum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +pm.max_spare_servers = 3 + +; The number of seconds after which an idle process will be killed. +; Note: Used only when pm is set to 'ondemand' +; Default Value: 10s +;pm.process_idle_timeout = 10s; + +; The number of requests each child process should execute before respawning. +; This can be useful to work around memory leaks in 3rd party libraries. For +; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. +; Default Value: 0 +;pm.max_requests = 500 + +; The URI to view the FPM status page. If this value is not set, no URI will be +; recognized as a status page. It shows the following informations: +; pool - the name of the pool; +; process manager - static, dynamic or ondemand; +; start time - the date and time FPM has started; +; start since - number of seconds since FPM has started; +; accepted conn - the number of request accepted by the pool; +; listen queue - the number of request in the queue of pending +; connections (see backlog in listen(2)); +; max listen queue - the maximum number of requests in the queue +; of pending connections since FPM has started; +; listen queue len - the size of the socket queue of pending connections; +; idle processes - the number of idle processes; +; active processes - the number of active processes; +; total processes - the number of idle + active processes; +; max active processes - the maximum number of active processes since FPM +; has started; +; max children reached - number of times, the process limit has been reached, +; when pm tries to start more children (works only for +; pm 'dynamic' and 'ondemand'); +; Value are updated in real time. +; Example output: +; pool: www +; process manager: static +; start time: 01/Jul/2011:17:53:49 +0200 +; start since: 62636 +; accepted conn: 190460 +; listen queue: 0 +; max listen queue: 1 +; listen queue len: 42 +; idle processes: 4 +; active processes: 11 +; total processes: 15 +; max active processes: 12 +; max children reached: 0 +; +; By default the status page output is formatted as text/plain. Passing either +; 'html', 'xml' or 'json' in the query string will return the corresponding +; output syntax. Example: +; http://www.foo.bar/status +; http://www.foo.bar/status?json +; http://www.foo.bar/status?html +; http://www.foo.bar/status?xml +; +; By default the status page only outputs short status. Passing 'full' in the +; query string will also return status for each pool process. +; Example: +; http://www.foo.bar/status?full +; http://www.foo.bar/status?json&full +; http://www.foo.bar/status?html&full +; http://www.foo.bar/status?xml&full +; The Full status returns for each process: +; pid - the PID of the process; +; state - the state of the process (Idle, Running, ...); +; start time - the date and time the process has started; +; start since - the number of seconds since the process has started; +; requests - the number of requests the process has served; +; request duration - the duration in µs of the requests; +; request method - the request method (GET, POST, ...); +; request URI - the request URI with the query string; +; content length - the content length of the request (only with POST); +; user - the user (PHP_AUTH_USER) (or '-' if not set); +; script - the main script called (or '-' if not set); +; last request cpu - the %cpu the last request consumed +; it's always 0 if the process is not in Idle state +; because CPU calculation is done when the request +; processing has terminated; +; last request memory - the max amount of memory the last request consumed +; it's always 0 if the process is not in Idle state +; because memory calculation is done when the request +; processing has terminated; +; If the process is in Idle state, then informations are related to the +; last request the process has served. Otherwise informations are related to +; the current request being served. +; Example output: +; ************************ +; pid: 31330 +; state: Running +; start time: 01/Jul/2011:17:53:49 +0200 +; start since: 63087 +; requests: 12808 +; request duration: 1250261 +; request method: GET +; request URI: /test_mem.php?N=10000 +; content length: 0 +; user: - +; script: /home/fat/web/docs/php/test_mem.php +; last request cpu: 0.00 +; last request memory: 0 +; +; Note: There is a real-time FPM status monitoring sample web page available +; It's available in: ${prefix}/share/fpm/status.html +; +; Note: The value must start with a leading slash (/). The value can be +; anything, but it may not be a good idea to use the .php extension or it +; may conflict with a real PHP file. +; Default Value: not set +;pm.status_path = /status + +; The ping URI to call the monitoring page of FPM. If this value is not set, no +; URI will be recognized as a ping page. This could be used to test from outside +; that FPM is alive and responding, or to +; - create a graph of FPM availability (rrd or such); +; - remove a server from a group if it is not responding (load balancing); +; - trigger alerts for the operating team (24/7). +; Note: The value must start with a leading slash (/). The value can be +; anything, but it may not be a good idea to use the .php extension or it +; may conflict with a real PHP file. +; Default Value: not set +;ping.path = /ping + +; This directive may be used to customize the response of a ping request. The +; response is formatted as text/plain with a 200 response code. +; Default Value: pong +;ping.response = pong + +; The access log file +; Default: not set +;access.log = log/$pool.access.log + +; The access log format. +; The following syntax is allowed +; %%: the '%' character +; %C: %CPU used by the request +; it can accept the following format: +; - %{user}C for user CPU only +; - %{system}C for system CPU only +; - %{total}C for user + system CPU (default) +; %d: time taken to serve the request +; it can accept the following format: +; - %{seconds}d (default) +; - %{miliseconds}d +; - %{mili}d +; - %{microseconds}d +; - %{micro}d +; %e: an environment variable (same as $_ENV or $_SERVER) +; it must be associated with embraces to specify the name of the env +; variable. Some exemples: +; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e +; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e +; %f: script filename +; %l: content-length of the request (for POST request only) +; %m: request method +; %M: peak of memory allocated by PHP +; it can accept the following format: +; - %{bytes}M (default) +; - %{kilobytes}M +; - %{kilo}M +; - %{megabytes}M +; - %{mega}M +; %n: pool name +; %o: output header +; it must be associated with embraces to specify the name of the header: +; - %{Content-Type}o +; - %{X-Powered-By}o +; - %{Transfert-Encoding}o +; - .... +; %p: PID of the child that serviced the request +; %P: PID of the parent of the child that serviced the request +; %q: the query string +; %Q: the '?' character if query string exists +; %r: the request URI (without the query string, see %q and %Q) +; %R: remote IP address +; %s: status (response code) +; %t: server time the request was received +; it can accept a strftime(3) format: +; %d/%b/%Y:%H:%M:%S %z (default) +; %T: time the log has been written (the request has finished) +; it can accept a strftime(3) format: +; %d/%b/%Y:%H:%M:%S %z (default) +; %u: remote user +; +; Default: "%R - %u %t \"%m %r\" %s" +;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" + +; The log file for slow requests +; Default Value: not set +; Note: slowlog is mandatory if request_slowlog_timeout is set +;slowlog = log/$pool.log.slow + +; The timeout for serving a single request after which a PHP backtrace will be +; dumped to the 'slowlog' file. A value of '0s' means 'off'. +; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) +; Default Value: 0 +;request_slowlog_timeout = 0 + +; The timeout for serving a single request after which the worker process will +; be killed. This option should be used when the 'max_execution_time' ini option +; does not stop script execution for some reason. A value of '0' means 'off'. +; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) +; Default Value: 0 +;request_terminate_timeout = 0 + +; Set open file descriptor rlimit. +; Default Value: system defined value +;rlimit_files = 1024 + +; Set max core size rlimit. +; Possible Values: 'unlimited' or an integer greater or equal to 0 +; Default Value: system defined value +;rlimit_core = 0 + +; Chroot to this directory at the start. This value must be defined as an +; absolute path. When this value is not set, chroot is not used. +; Note: you can prefix with '$prefix' to chroot to the pool prefix or one +; of its subdirectories. If the pool prefix is not set, the global prefix +; will be used instead. +; Note: chrooting is a great security feature and should be used whenever +; possible. However, all PHP paths will be relative to the chroot +; (error_log, sessions.save_path, ...). +; Default Value: not set +;chroot = + +; Chdir to this directory at the start. +; Note: relative path can be used. +; Default Value: current directory or / when chroot +;chdir = /srv/http + +; Redirect worker stdout and stderr into main error log. If not set, stdout and +; stderr will be redirected to /dev/null according to FastCGI specs. +; Note: on highloaded environement, this can cause some delay in the page +; process time (several ms). +; Default Value: no +;catch_workers_output = yes + +; Limits the extensions of the main script FPM will allow to parse. This can +; prevent configuration mistakes on the web server side. You should only limit +; FPM to .php extensions to prevent malicious users to use other extensions to +; exectute php code. +; Note: set an empty value to allow all extensions. +; Default Value: .php +;security.limit_extensions = .php .php3 .php4 .php5 + +; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from +; the current environment. +; Default Value: clean env +;env[HOSTNAME] = $HOSTNAME +;env[PATH] = /usr/local/bin:/usr/bin:/bin +;env[TMP] = /tmp +;env[TMPDIR] = /tmp +;env[TEMP] = /tmp + +; Additional php.ini defines, specific to this pool of workers. These settings +; overwrite the values previously defined in the php.ini. The directives are the +; same as the PHP SAPI: +; php_value/php_flag - you can set classic ini defines which can +; be overwritten from PHP call 'ini_set'. +; php_admin_value/php_admin_flag - these directives won't be overwritten by +; PHP call 'ini_set' +; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no. + +; Defining 'extension' will load the corresponding shared extension from +; extension_dir. Defining 'disable_functions' or 'disable_classes' will not +; overwrite previously defined php.ini values, but will append the new value +; instead. + +; Note: path INI options can be relative and will be expanded with the prefix +; (pool, global or /usr) + +; Default Value: nothing is defined by default except the values in php.ini and +; specified at startup with the -d argument +;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com +;php_flag[display_errors] = off +;php_admin_value[error_log] = /var/log/fpm-php.www.log +;php_admin_flag[log_errors] = on +;php_admin_value[memory_limit] = 32M diff --git a/app/config/_os/etc/rc.local b/app/config/_os/etc/rc.local index 04b4d122..f3995236 100644 --- a/app/config/_os/etc/rc.local +++ b/app/config/_os/etc/rc.local @@ -1,15 +1,15 @@ -#!/bin/sh -e -# -# rc.local -# -# This script is executed at the end of each multiuser runlevel. -# Make sure that the script will "exit 0" on success or any other -# value on error. -# -# In order to enable or disable this script just change the execution -# bits. -# -# By default this script does nothing. - -exit 0 - +#!/bin/sh -e +# +# rc.local +# +# This script is executed at the end of each multiuser runlevel. +# Make sure that the script will "exit 0" on success or any other +# value on error. +# +# In order to enable or disable this script just change the execution +# bits. +# +# By default this script does nothing. + +exit 0 + diff --git a/app/config/_os/etc/redis.conf b/app/config/_os/etc/redis.conf index c53d197a..234430dd 100644 --- a/app/config/_os/etc/redis.conf +++ b/app/config/_os/etc/redis.conf @@ -1,731 +1,731 @@ -# Redis configuration file example - -# Note on units: when memory size is needed, it is possible to specify -# it in the usual form of 1k 5GB 4M and so forth: -# -# 1k => 1000 bytes -# 1kb => 1024 bytes -# 1m => 1000000 bytes -# 1mb => 1024*1024 bytes -# 1g => 1000000000 bytes -# 1gb => 1024*1024*1024 bytes -# -# units are case insensitive so 1GB 1Gb 1gB are all the same. - -################################## INCLUDES ################################### - -# Include one or more other config files here. This is useful if you -# have a standard template that goes to all Redis server but also need -# to customize a few per-server settings. Include files can include -# other files, so use this wisely. -# -# Notice option "include" won't be rewritten by command "CONFIG REWRITE" -# from admin or Redis Sentinel. Since Redis always uses the last processed -# line as value of a configuration directive, you'd better put includes -# at the beginning of this file to avoid overwriting config change at runtime. -# -# If instead you are interested in using includes to override configuration -# options, it is better to use include as the last line. -# -# include /path/to/local.conf -# include /path/to/other.conf - -################################ GENERAL ##################################### - -# By default Redis does not run as a daemon. Use 'yes' if you need it. -# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. -daemonize yes - -# When running daemonized, Redis writes a pid file in /var/run/redis.pid by -# default. You can specify a custom pid file location here. -pidfile /run/redis/redis.pid - -# Accept connections on the specified port, default is 6379. -# If port 0 is specified Redis will not listen on a TCP socket. -port 6379 - -# TCP listen() backlog. -# -# In high requests-per-second environments you need an high backlog in order -# to avoid slow clients connections issues. Note that the Linux kernel -# will silently truncate it to the value of /proc/sys/net/core/somaxconn so -# make sure to raise both the value of somaxconn and tcp_max_syn_backlog -# in order to get the desired effect. -tcp-backlog 511 - -# By default Redis listens for connections from all the network interfaces -# available on the server. It is possible to listen to just one or multiple -# interfaces using the "bind" configuration directive, followed by one or -# more IP addresses. -# -# Examples: -# -# bind 192.168.1.100 10.0.0.1 -bind 127.0.0.1 - -# Specify the path for the Unix socket that will be used to listen for -# incoming connections. There is no default, so Redis will not listen -# on a unix socket when not specified. -# -unixsocket /tmp/redis.sock -unixsocketperm 777 - -# Close the connection after a client is idle for N seconds (0 to disable) -timeout 0 - -# TCP keepalive. -# -# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence -# of communication. This is useful for two reasons: -# -# 1) Detect dead peers. -# 2) Take the connection alive from the point of view of network -# equipment in the middle. -# -# On Linux, the specified value (in seconds) is the period used to send ACKs. -# Note that to close the connection the double of the time is needed. -# On other kernels the period depends on the kernel configuration. -# -# A reasonable value for this option is 60 seconds. -tcp-keepalive 0 - -# Specify the server verbosity level. -# This can be one of: -# debug (a lot of information, useful for development/testing) -# verbose (many rarely useful info, but not a mess like the debug level) -# notice (moderately verbose, what you want in production probably) -# warning (only very important / critical messages are logged) -loglevel notice - -# Specify the log file name. Also the empty string can be used to force -# Redis to log on the standard output. Note that if you use standard -# output for logging but daemonize, logs will be sent to /dev/null -logfile "" - -# To enable logging to the system logger, just set 'syslog-enabled' to yes, -# and optionally update the other syslog parameters to suit your needs. -# syslog-enabled no - -# Specify the syslog identity. -# syslog-ident redis - -# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. -# syslog-facility local0 - -# Set the number of databases. The default database is DB 0, you can select -# a different one on a per-connection basis using SELECT where -# dbid is a number between 0 and 'databases'-1 -databases 16 - -################################ SNAPSHOTTING ################################ -# -# Save the DB on disk: -# -# save -# -# Will save the DB if both the given number of seconds and the given -# number of write operations against the DB occurred. -# -# In the example below the behaviour will be to save: -# after 900 sec (15 min) if at least 1 key changed -# after 300 sec (5 min) if at least 10 keys changed -# after 60 sec if at least 10000 keys changed -# -# Note: you can disable saving at all commenting all the "save" lines. -# -# It is also possible to remove all the previously configured save -# points by adding a save directive with a single empty string argument -# like in the following example: -# -# save "" - -save 900 1 -save 300 10 -save 60 10000 - -# By default Redis will stop accepting writes if RDB snapshots are enabled -# (at least one save point) and the latest background save failed. -# This will make the user aware (in a hard way) that data is not persisting -# on disk properly, otherwise chances are that no one will notice and some -# disaster will happen. -# -# If the background saving process will start working again Redis will -# automatically allow writes again. -# -# However if you have setup your proper monitoring of the Redis server -# and persistence, you may want to disable this feature so that Redis will -# continue to work as usual even if there are problems with disk, -# permissions, and so forth. -stop-writes-on-bgsave-error yes - -# Compress string objects using LZF when dump .rdb databases? -# For default that's set to 'yes' as it's almost always a win. -# If you want to save some CPU in the saving child set it to 'no' but -# the dataset will likely be bigger if you have compressible values or keys. -rdbcompression yes - -# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. -# This makes the format more resistant to corruption but there is a performance -# hit to pay (around 10%) when saving and loading RDB files, so you can disable it -# for maximum performances. -# -# RDB files created with checksum disabled have a checksum of zero that will -# tell the loading code to skip the check. -rdbchecksum yes - -# The filename where to dump the DB -dbfilename dump.rdb - -# The working directory. -# -# The DB will be written inside this directory, with the filename specified -# above using the 'dbfilename' configuration directive. -# -# The Append Only File will also be created inside this directory. -# -# Note that you must specify a directory here, not a file name. -dir /var/lib/redis/ - -################################# REPLICATION ################################# - -# Master-Slave replication. Use slaveof to make a Redis instance a copy of -# another Redis server. Note that the configuration is local to the slave -# so for example it is possible to configure the slave to save the DB with a -# different interval, or to listen to another port, and so on. -# -# slaveof - -# If the master is password protected (using the "requirepass" configuration -# directive below) it is possible to tell the slave to authenticate before -# starting the replication synchronization process, otherwise the master will -# refuse the slave request. -# -# masterauth - -# When a slave loses its connection with the master, or when the replication -# is still in progress, the slave can act in two different ways: -# -# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will -# still reply to client requests, possibly with out of date data, or the -# data set may just be empty if this is the first synchronization. -# -# 2) if slave-serve-stale-data is set to 'no' the slave will reply with -# an error "SYNC with master in progress" to all the kind of commands -# but to INFO and SLAVEOF. -# -slave-serve-stale-data yes - -# You can configure a slave instance to accept writes or not. Writing against -# a slave instance may be useful to store some ephemeral data (because data -# written on a slave will be easily deleted after resync with the master) but -# may also cause problems if clients are writing to it because of a -# misconfiguration. -# -# Since Redis 2.6 by default slaves are read-only. -# -# Note: read only slaves are not designed to be exposed to untrusted clients -# on the internet. It's just a protection layer against misuse of the instance. -# Still a read only slave exports by default all the administrative commands -# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve -# security of read only slaves using 'rename-command' to shadow all the -# administrative / dangerous commands. -slave-read-only yes - -# Slaves send PINGs to server in a predefined interval. It's possible to change -# this interval with the repl_ping_slave_period option. The default value is 10 -# seconds. -# -# repl-ping-slave-period 10 - -# The following option sets the replication timeout for: -# -# 1) Bulk transfer I/O during SYNC, from the point of view of slave. -# 2) Master timeout from the point of view of slaves (data, pings). -# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings). -# -# It is important to make sure that this value is greater than the value -# specified for repl-ping-slave-period otherwise a timeout will be detected -# every time there is low traffic between the master and the slave. -# -# repl-timeout 60 - -# Disable TCP_NODELAY on the slave socket after SYNC? -# -# If you select "yes" Redis will use a smaller number of TCP packets and -# less bandwidth to send data to slaves. But this can add a delay for -# the data to appear on the slave side, up to 40 milliseconds with -# Linux kernels using a default configuration. -# -# If you select "no" the delay for data to appear on the slave side will -# be reduced but more bandwidth will be used for replication. -# -# By default we optimize for low latency, but in very high traffic conditions -# or when the master and slaves are many hops away, turning this to "yes" may -# be a good idea. -repl-disable-tcp-nodelay no - -# Set the replication backlog size. The backlog is a buffer that accumulates -# slave data when slaves are disconnected for some time, so that when a slave -# wants to reconnect again, often a full resync is not needed, but a partial -# resync is enough, just passing the portion of data the slave missed while -# disconnected. -# -# The biggest the replication backlog, the longer the time the slave can be -# disconnected and later be able to perform a partial resynchronization. -# -# The backlog is only allocated once there is at least a slave connected. -# -# repl-backlog-size 1mb - -# After a master has no longer connected slaves for some time, the backlog -# will be freed. The following option configures the amount of seconds that -# need to elapse, starting from the time the last slave disconnected, for -# the backlog buffer to be freed. -# -# A value of 0 means to never release the backlog. -# -# repl-backlog-ttl 3600 - -# The slave priority is an integer number published by Redis in the INFO output. -# It is used by Redis Sentinel in order to select a slave to promote into a -# master if the master is no longer working correctly. -# -# A slave with a low priority number is considered better for promotion, so -# for instance if there are three slaves with priority 10, 100, 25 Sentinel will -# pick the one with priority 10, that is the lowest. -# -# However a special priority of 0 marks the slave as not able to perform the -# role of master, so a slave with priority of 0 will never be selected by -# Redis Sentinel for promotion. -# -# By default the priority is 100. -slave-priority 100 - -# It is possible for a master to stop accepting writes if there are less than -# N slaves connected, having a lag less or equal than M seconds. -# -# The N slaves need to be in "online" state. -# -# The lag in seconds, that must be <= the specified value, is calculated from -# the last ping received from the slave, that is usually sent every second. -# -# This option does not GUARANTEES that N replicas will accept the write, but -# will limit the window of exposure for lost writes in case not enough slaves -# are available, to the specified number of seconds. -# -# For example to require at least 3 slaves with a lag <= 10 seconds use: -# -# min-slaves-to-write 3 -# min-slaves-max-lag 10 -# -# Setting one or the other to 0 disables the feature. -# -# By default min-slaves-to-write is set to 0 (feature disabled) and -# min-slaves-max-lag is set to 10. - -################################## SECURITY ################################### - -# Require clients to issue AUTH before processing any other -# commands. This might be useful in environments in which you do not trust -# others with access to the host running redis-server. -# -# This should stay commented out for backward compatibility and because most -# people do not need auth (e.g. they run their own servers). -# -# Warning: since Redis is pretty fast an outside user can try up to -# 150k passwords per second against a good box. This means that you should -# use a very strong password otherwise it will be very easy to break. -# -# requirepass foobared - -# Command renaming. -# -# It is possible to change the name of dangerous commands in a shared -# environment. For instance the CONFIG command may be renamed into something -# hard to guess so that it will still be available for internal-use tools -# but not available for general clients. -# -# Example: -# -# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 -# -# It is also possible to completely kill a command by renaming it into -# an empty string: -# -# rename-command CONFIG "" -# -# Please note that changing the name of commands that are logged into the -# AOF file or transmitted to slaves may cause problems. - -################################### LIMITS #################################### - -# Set the max number of connected clients at the same time. By default -# this limit is set to 10000 clients, however if the Redis server is not -# able to configure the process file limit to allow for the specified limit -# the max number of allowed clients is set to the current file limit -# minus 32 (as Redis reserves a few file descriptors for internal uses). -# -# Once the limit is reached Redis will close all the new connections sending -# an error 'max number of clients reached'. -# -# maxclients 10000 - -# Don't use more memory than the specified amount of bytes. -# When the memory limit is reached Redis will try to remove keys -# according to the eviction policy selected (see maxmemory-policy). -# -# If Redis can't remove keys according to the policy, or if the policy is -# set to 'noeviction', Redis will start to reply with errors to commands -# that would use more memory, like SET, LPUSH, and so on, and will continue -# to reply to read-only commands like GET. -# -# This option is usually useful when using Redis as an LRU cache, or to set -# a hard memory limit for an instance (using the 'noeviction' policy). -# -# WARNING: If you have slaves attached to an instance with maxmemory on, -# the size of the output buffers needed to feed the slaves are subtracted -# from the used memory count, so that network problems / resyncs will -# not trigger a loop where keys are evicted, and in turn the output -# buffer of slaves is full with DELs of keys evicted triggering the deletion -# of more keys, and so forth until the database is completely emptied. -# -# In short... if you have slaves attached it is suggested that you set a lower -# limit for maxmemory so that there is some free RAM on the system for slave -# output buffers (but this is not needed if the policy is 'noeviction'). -# -# maxmemory - -# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory -# is reached. You can select among five behaviors: -# -# volatile-lru -> remove the key with an expire set using an LRU algorithm -# allkeys-lru -> remove any key accordingly to the LRU algorithm -# volatile-random -> remove a random key with an expire set -# allkeys-random -> remove a random key, any key -# volatile-ttl -> remove the key with the nearest expire time (minor TTL) -# noeviction -> don't expire at all, just return an error on write operations -# -# Note: with any of the above policies, Redis will return an error on write -# operations, when there are not suitable keys for eviction. -# -# At the date of writing this commands are: set setnx setex append -# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd -# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby -# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby -# getset mset msetnx exec sort -# -# The default is: -# -# maxmemory-policy volatile-lru - -# LRU and minimal TTL algorithms are not precise algorithms but approximated -# algorithms (in order to save memory), so you can select as well the sample -# size to check. For instance for default Redis will check three keys and -# pick the one that was used less recently, you can change the sample size -# using the following configuration directive. -# -# maxmemory-samples 3 - -############################## APPEND ONLY MODE ############################### - -# By default Redis asynchronously dumps the dataset on disk. This mode is -# good enough in many applications, but an issue with the Redis process or -# a power outage may result into a few minutes of writes lost (depending on -# the configured save points). -# -# The Append Only File is an alternative persistence mode that provides -# much better durability. For instance using the default data fsync policy -# (see later in the config file) Redis can lose just one second of writes in a -# dramatic event like a server power outage, or a single write if something -# wrong with the Redis process itself happens, but the operating system is -# still running correctly. -# -# AOF and RDB persistence can be enabled at the same time without problems. -# If the AOF is enabled on startup Redis will load the AOF, that is the file -# with the better durability guarantees. -# -# Please check http://redis.io/topics/persistence for more information. - -appendonly no - -# The name of the append only file (default: "appendonly.aof") - -appendfilename "appendonly.aof" - -# The fsync() call tells the Operating System to actually write data on disk -# instead to wait for more data in the output buffer. Some OS will really flush -# data on disk, some other OS will just try to do it ASAP. -# -# Redis supports three different modes: -# -# no: don't fsync, just let the OS flush the data when it wants. Faster. -# always: fsync after every write to the append only log . Slow, Safest. -# everysec: fsync only one time every second. Compromise. -# -# The default is "everysec", as that's usually the right compromise between -# speed and data safety. It's up to you to understand if you can relax this to -# "no" that will let the operating system flush the output buffer when -# it wants, for better performances (but if you can live with the idea of -# some data loss consider the default persistence mode that's snapshotting), -# or on the contrary, use "always" that's very slow but a bit safer than -# everysec. -# -# More details please check the following article: -# http://antirez.com/post/redis-persistence-demystified.html -# -# If unsure, use "everysec". - -# appendfsync always -appendfsync everysec -# appendfsync no - -# When the AOF fsync policy is set to always or everysec, and a background -# saving process (a background save or AOF log background rewriting) is -# performing a lot of I/O against the disk, in some Linux configurations -# Redis may block too long on the fsync() call. Note that there is no fix for -# this currently, as even performing fsync in a different thread will block -# our synchronous write(2) call. -# -# In order to mitigate this problem it's possible to use the following option -# that will prevent fsync() from being called in the main process while a -# BGSAVE or BGREWRITEAOF is in progress. -# -# This means that while another child is saving, the durability of Redis is -# the same as "appendfsync none". In practical terms, this means that it is -# possible to lose up to 30 seconds of log in the worst scenario (with the -# default Linux settings). -# -# If you have latency problems turn this to "yes". Otherwise leave it as -# "no" that is the safest pick from the point of view of durability. - -no-appendfsync-on-rewrite no - -# Automatic rewrite of the append only file. -# Redis is able to automatically rewrite the log file implicitly calling -# BGREWRITEAOF when the AOF log size grows by the specified percentage. -# -# This is how it works: Redis remembers the size of the AOF file after the -# latest rewrite (if no rewrite has happened since the restart, the size of -# the AOF at startup is used). -# -# This base size is compared to the current size. If the current size is -# bigger than the specified percentage, the rewrite is triggered. Also -# you need to specify a minimal size for the AOF file to be rewritten, this -# is useful to avoid rewriting the AOF file even if the percentage increase -# is reached but it is still pretty small. -# -# Specify a percentage of zero in order to disable the automatic AOF -# rewrite feature. - -auto-aof-rewrite-percentage 100 -auto-aof-rewrite-min-size 64mb - -################################ LUA SCRIPTING ############################### - -# Max execution time of a Lua script in milliseconds. -# -# If the maximum execution time is reached Redis will log that a script is -# still in execution after the maximum allowed time and will start to -# reply to queries with an error. -# -# When a long running script exceed the maximum execution time only the -# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be -# used to stop a script that did not yet called write commands. The second -# is the only way to shut down the server in the case a write commands was -# already issue by the script but the user don't want to wait for the natural -# termination of the script. -# -# Set it to 0 or a negative value for unlimited execution without warnings. -lua-time-limit 5000 - -################################## SLOW LOG ################################### - -# The Redis Slow Log is a system to log queries that exceeded a specified -# execution time. The execution time does not include the I/O operations -# like talking with the client, sending the reply and so forth, -# but just the time needed to actually execute the command (this is the only -# stage of command execution where the thread is blocked and can not serve -# other requests in the meantime). -# -# You can configure the slow log with two parameters: one tells Redis -# what is the execution time, in microseconds, to exceed in order for the -# command to get logged, and the other parameter is the length of the -# slow log. When a new command is logged the oldest one is removed from the -# queue of logged commands. - -# The following time is expressed in microseconds, so 1000000 is equivalent -# to one second. Note that a negative number disables the slow log, while -# a value of zero forces the logging of every command. -slowlog-log-slower-than 10000 - -# There is no limit to this length. Just be aware that it will consume memory. -# You can reclaim memory used by the slow log with SLOWLOG RESET. -slowlog-max-len 128 - -############################# Event notification ############################## - -# Redis can notify Pub/Sub clients about events happening in the key space. -# This feature is documented at http://redis.io/topics/keyspace-events -# -# For instance if keyspace events notification is enabled, and a client -# performs a DEL operation on key "foo" stored in the Database 0, two -# messages will be published via Pub/Sub: -# -# PUBLISH __keyspace@0__:foo del -# PUBLISH __keyevent@0__:del foo -# -# It is possible to select the events that Redis will notify among a set -# of classes. Every class is identified by a single character: -# -# K Keyspace events, published with __keyspace@__ prefix. -# E Keyevent events, published with __keyevent@__ prefix. -# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... -# $ String commands -# l List commands -# s Set commands -# h Hash commands -# z Sorted set commands -# x Expired events (events generated every time a key expires) -# e Evicted events (events generated when a key is evicted for maxmemory) -# A Alias for g$lshzxe, so that the "AKE" string means all the events. -# -# The "notify-keyspace-events" takes as argument a string that is composed -# by zero or multiple characters. The empty string means that notifications -# are disabled at all. -# -# Example: to enable list and generic events, from the point of view of the -# event name, use: -# -# notify-keyspace-events Elg -# -# Example 2: to get the stream of the expired keys subscribing to channel -# name __keyevent@0__:expired use: -# -# notify-keyspace-events Ex -# -# By default all notifications are disabled because most users don't need -# this feature and the feature has some overhead. Note that if you don't -# specify at least one of K or E, no events will be delivered. -notify-keyspace-events "" - -############################### ADVANCED CONFIG ############################### - -# Hashes are encoded using a memory efficient data structure when they have a -# small number of entries, and the biggest entry does not exceed a given -# threshold. These thresholds can be configured using the following directives. -hash-max-ziplist-entries 512 -hash-max-ziplist-value 64 - -# Similarly to hashes, small lists are also encoded in a special way in order -# to save a lot of space. The special representation is only used when -# you are under the following limits: -list-max-ziplist-entries 512 -list-max-ziplist-value 64 - -# Sets have a special encoding in just one case: when a set is composed -# of just strings that happens to be integers in radix 10 in the range -# of 64 bit signed integers. -# The following configuration setting sets the limit in the size of the -# set in order to use this special memory saving encoding. -set-max-intset-entries 512 - -# Similarly to hashes and lists, sorted sets are also specially encoded in -# order to save a lot of space. This encoding is only used when the length and -# elements of a sorted set are below the following limits: -zset-max-ziplist-entries 128 -zset-max-ziplist-value 64 - -# HyperLogLog sparse representation bytes limit. The limit includes the -# 16 bytes header. When an HyperLogLog using the sparse representation crosses -# this limit, it is converted into the dense representation. -# -# A value greater than 16000 is totally useless, since at that point the -# dense representation is more memory efficient. -# -# The suggested value is ~ 3000 in order to have the benefits of -# the space efficient encoding without slowing down too much PFADD, -# which is O(N) with the sparse encoding. The value can be raised to -# ~ 10000 when CPU is not a concern, but space is, and the data set is -# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. -hll-sparse-max-bytes 3000 - -# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in -# order to help rehashing the main Redis hash table (the one mapping top-level -# keys to values). The hash table implementation Redis uses (see dict.c) -# performs a lazy rehashing: the more operation you run into a hash table -# that is rehashing, the more rehashing "steps" are performed, so if the -# server is idle the rehashing is never complete and some more memory is used -# by the hash table. -# -# The default is to use this millisecond 10 times every second in order to -# active rehashing the main dictionaries, freeing memory when possible. -# -# If unsure: -# use "activerehashing no" if you have hard latency requirements and it is -# not a good thing in your environment that Redis can reply form time to time -# to queries with 2 milliseconds delay. -# -# use "activerehashing yes" if you don't have such hard requirements but -# want to free memory asap when possible. -activerehashing yes - -# The client output buffer limits can be used to force disconnection of clients -# that are not reading data from the server fast enough for some reason (a -# common reason is that a Pub/Sub client can't consume messages as fast as the -# publisher can produce them). -# -# The limit can be set differently for the three different classes of clients: -# -# normal -> normal clients including MONITOR clients -# slave -> slave clients -# pubsub -> clients subscribed to at least one pubsub channel or pattern -# -# The syntax of every client-output-buffer-limit directive is the following: -# -# client-output-buffer-limit -# -# A client is immediately disconnected once the hard limit is reached, or if -# the soft limit is reached and remains reached for the specified number of -# seconds (continuously). -# So for instance if the hard limit is 32 megabytes and the soft limit is -# 16 megabytes / 10 seconds, the client will get disconnected immediately -# if the size of the output buffers reach 32 megabytes, but will also get -# disconnected if the client reaches 16 megabytes and continuously overcomes -# the limit for 10 seconds. -# -# By default normal clients are not limited because they don't receive data -# without asking (in a push way), but just after a request, so only -# asynchronous clients may create a scenario where data is requested faster -# than it can read. -# -# Instead there is a default limit for pubsub and slave clients, since -# subscribers and slaves receive data in a push fashion. -# -# Both the hard or the soft limit can be disabled by setting them to zero. -client-output-buffer-limit normal 0 0 0 -client-output-buffer-limit slave 256mb 64mb 60 -client-output-buffer-limit pubsub 32mb 8mb 60 - -# Redis calls an internal function to perform many background tasks, like -# closing connections of clients in timeout, purging expired keys that are -# never requested, and so forth. -# -# Not all tasks are performed with the same frequency, but Redis checks for -# tasks to perform accordingly to the specified "hz" value. -# -# By default "hz" is set to 10. Raising the value will use more CPU when -# Redis is idle, but at the same time will make Redis more responsive when -# there are many keys expiring at the same time, and timeouts may be -# handled with more precision. -# -# The range is between 1 and 500, however a value over 100 is usually not -# a good idea. Most users should use the default of 10 and raise this up to -# 100 only in environments where very low latency is required. -hz 10 - -# When a child rewrites the AOF file, if the following option is enabled -# the file will be fsync-ed every 32 MB of data generated. This is useful -# in order to commit the file to the disk more incrementally and avoid -# big latency spikes. -aof-rewrite-incremental-fsync yes - +# Redis configuration file example + +# Note on units: when memory size is needed, it is possible to specify +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis server but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Notice option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# include /path/to/local.conf +# include /path/to/other.conf + +################################ GENERAL ##################################### + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +daemonize yes + +# When running daemonized, Redis writes a pid file in /var/run/redis.pid by +# default. You can specify a custom pid file location here. +pidfile /run/redis/redis.pid + +# Accept connections on the specified port, default is 6379. +# If port 0 is specified Redis will not listen on a TCP socket. +port 6379 + +# TCP listen() backlog. +# +# In high requests-per-second environments you need an high backlog in order +# to avoid slow clients connections issues. Note that the Linux kernel +# will silently truncate it to the value of /proc/sys/net/core/somaxconn so +# make sure to raise both the value of somaxconn and tcp_max_syn_backlog +# in order to get the desired effect. +tcp-backlog 511 + +# By default Redis listens for connections from all the network interfaces +# available on the server. It is possible to listen to just one or multiple +# interfaces using the "bind" configuration directive, followed by one or +# more IP addresses. +# +# Examples: +# +# bind 192.168.1.100 10.0.0.1 +bind 127.0.0.1 + +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +unixsocket /tmp/redis.sock +unixsocketperm 777 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# TCP keepalive. +# +# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence +# of communication. This is useful for two reasons: +# +# 1) Detect dead peers. +# 2) Take the connection alive from the point of view of network +# equipment in the middle. +# +# On Linux, the specified value (in seconds) is the period used to send ACKs. +# Note that to close the connection the double of the time is needed. +# On other kernels the period depends on the kernel configuration. +# +# A reasonable value for this option is 60 seconds. +tcp-keepalive 0 + +# Specify the server verbosity level. +# This can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel notice + +# Specify the log file name. Also the empty string can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile "" + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +# syslog-enabled no + +# Specify the syslog identity. +# syslog-ident redis + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +# syslog-facility local0 + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +################################ SNAPSHOTTING ################################ +# +# Save the DB on disk: +# +# save +# +# Will save the DB if both the given number of seconds and the given +# number of write operations against the DB occurred. +# +# In the example below the behaviour will be to save: +# after 900 sec (15 min) if at least 1 key changed +# after 300 sec (5 min) if at least 10 keys changed +# after 60 sec if at least 10000 keys changed +# +# Note: you can disable saving at all commenting all the "save" lines. +# +# It is also possible to remove all the previously configured save +# points by adding a save directive with a single empty string argument +# like in the following example: +# +# save "" + +save 900 1 +save 300 10 +save 60 10000 + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in a hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# disaster will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usual even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error yes + +# Compress string objects using LZF when dump .rdb databases? +# For default that's set to 'yes' as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum yes + +# The filename where to dump the DB +dbfilename dump.rdb + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# The Append Only File will also be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir /var/lib/redis/ + +################################# REPLICATION ################################# + +# Master-Slave replication. Use slaveof to make a Redis instance a copy of +# another Redis server. Note that the configuration is local to the slave +# so for example it is possible to configure the slave to save the DB with a +# different interval, or to listen to another port, and so on. +# +# slaveof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the slave to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the slave request. +# +# masterauth + +# When a slave loses its connection with the master, or when the replication +# is still in progress, the slave can act in two different ways: +# +# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will +# still reply to client requests, possibly with out of date data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) if slave-serve-stale-data is set to 'no' the slave will reply with +# an error "SYNC with master in progress" to all the kind of commands +# but to INFO and SLAVEOF. +# +slave-serve-stale-data yes + +# You can configure a slave instance to accept writes or not. Writing against +# a slave instance may be useful to store some ephemeral data (because data +# written on a slave will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default slaves are read-only. +# +# Note: read only slaves are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only slave exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve +# security of read only slaves using 'rename-command' to shadow all the +# administrative / dangerous commands. +slave-read-only yes + +# Slaves send PINGs to server in a predefined interval. It's possible to change +# this interval with the repl_ping_slave_period option. The default value is 10 +# seconds. +# +# repl-ping-slave-period 10 + +# The following option sets the replication timeout for: +# +# 1) Bulk transfer I/O during SYNC, from the point of view of slave. +# 2) Master timeout from the point of view of slaves (data, pings). +# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings). +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-slave-period otherwise a timeout will be detected +# every time there is low traffic between the master and the slave. +# +# repl-timeout 60 + +# Disable TCP_NODELAY on the slave socket after SYNC? +# +# If you select "yes" Redis will use a smaller number of TCP packets and +# less bandwidth to send data to slaves. But this can add a delay for +# the data to appear on the slave side, up to 40 milliseconds with +# Linux kernels using a default configuration. +# +# If you select "no" the delay for data to appear on the slave side will +# be reduced but more bandwidth will be used for replication. +# +# By default we optimize for low latency, but in very high traffic conditions +# or when the master and slaves are many hops away, turning this to "yes" may +# be a good idea. +repl-disable-tcp-nodelay no + +# Set the replication backlog size. The backlog is a buffer that accumulates +# slave data when slaves are disconnected for some time, so that when a slave +# wants to reconnect again, often a full resync is not needed, but a partial +# resync is enough, just passing the portion of data the slave missed while +# disconnected. +# +# The biggest the replication backlog, the longer the time the slave can be +# disconnected and later be able to perform a partial resynchronization. +# +# The backlog is only allocated once there is at least a slave connected. +# +# repl-backlog-size 1mb + +# After a master has no longer connected slaves for some time, the backlog +# will be freed. The following option configures the amount of seconds that +# need to elapse, starting from the time the last slave disconnected, for +# the backlog buffer to be freed. +# +# A value of 0 means to never release the backlog. +# +# repl-backlog-ttl 3600 + +# The slave priority is an integer number published by Redis in the INFO output. +# It is used by Redis Sentinel in order to select a slave to promote into a +# master if the master is no longer working correctly. +# +# A slave with a low priority number is considered better for promotion, so +# for instance if there are three slaves with priority 10, 100, 25 Sentinel will +# pick the one with priority 10, that is the lowest. +# +# However a special priority of 0 marks the slave as not able to perform the +# role of master, so a slave with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +slave-priority 100 + +# It is possible for a master to stop accepting writes if there are less than +# N slaves connected, having a lag less or equal than M seconds. +# +# The N slaves need to be in "online" state. +# +# The lag in seconds, that must be <= the specified value, is calculated from +# the last ping received from the slave, that is usually sent every second. +# +# This option does not GUARANTEES that N replicas will accept the write, but +# will limit the window of exposure for lost writes in case not enough slaves +# are available, to the specified number of seconds. +# +# For example to require at least 3 slaves with a lag <= 10 seconds use: +# +# min-slaves-to-write 3 +# min-slaves-max-lag 10 +# +# Setting one or the other to 0 disables the feature. +# +# By default min-slaves-to-write is set to 0 (feature disabled) and +# min-slaves-max-lag is set to 10. + +################################## SECURITY ################################### + +# Require clients to issue AUTH before processing any other +# commands. This might be useful in environments in which you do not trust +# others with access to the host running redis-server. +# +# This should stay commented out for backward compatibility and because most +# people do not need auth (e.g. they run their own servers). +# +# Warning: since Redis is pretty fast an outside user can try up to +# 150k passwords per second against a good box. This means that you should +# use a very strong password otherwise it will be very easy to break. +# +# requirepass foobared + +# Command renaming. +# +# It is possible to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# hard to guess so that it will still be available for internal-use tools +# but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possible to completely kill a command by renaming it into +# an empty string: +# +# rename-command CONFIG "" +# +# Please note that changing the name of commands that are logged into the +# AOF file or transmitted to slaves may cause problems. + +################################### LIMITS #################################### + +# Set the max number of connected clients at the same time. By default +# this limit is set to 10000 clients, however if the Redis server is not +# able to configure the process file limit to allow for the specified limit +# the max number of allowed clients is set to the current file limit +# minus 32 (as Redis reserves a few file descriptors for internal uses). +# +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# maxclients 10000 + +# Don't use more memory than the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys +# according to the eviction policy selected (see maxmemory-policy). +# +# If Redis can't remove keys according to the policy, or if the policy is +# set to 'noeviction', Redis will start to reply with errors to commands +# that would use more memory, like SET, LPUSH, and so on, and will continue +# to reply to read-only commands like GET. +# +# This option is usually useful when using Redis as an LRU cache, or to set +# a hard memory limit for an instance (using the 'noeviction' policy). +# +# WARNING: If you have slaves attached to an instance with maxmemory on, +# the size of the output buffers needed to feed the slaves are subtracted +# from the used memory count, so that network problems / resyncs will +# not trigger a loop where keys are evicted, and in turn the output +# buffer of slaves is full with DELs of keys evicted triggering the deletion +# of more keys, and so forth until the database is completely emptied. +# +# In short... if you have slaves attached it is suggested that you set a lower +# limit for maxmemory so that there is some free RAM on the system for slave +# output buffers (but this is not needed if the policy is 'noeviction'). +# +# maxmemory + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached. You can select among five behaviors: +# +# volatile-lru -> remove the key with an expire set using an LRU algorithm +# allkeys-lru -> remove any key accordingly to the LRU algorithm +# volatile-random -> remove a random key with an expire set +# allkeys-random -> remove a random key, any key +# volatile-ttl -> remove the key with the nearest expire time (minor TTL) +# noeviction -> don't expire at all, just return an error on write operations +# +# Note: with any of the above policies, Redis will return an error on write +# operations, when there are not suitable keys for eviction. +# +# At the date of writing this commands are: set setnx setex append +# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd +# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby +# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby +# getset mset msetnx exec sort +# +# The default is: +# +# maxmemory-policy volatile-lru + +# LRU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can select as well the sample +# size to check. For instance for default Redis will check three keys and +# pick the one that was used less recently, you can change the sample size +# using the following configuration directive. +# +# maxmemory-samples 3 + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). +# +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. +# +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check http://redis.io/topics/persistence for more information. + +appendonly no + +# The name of the append only file (default: "appendonly.aof") + +appendfilename "appendonly.aof" + +# The fsync() call tells the Operating System to actually write data on disk +# instead to wait for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log . Slow, Safest. +# everysec: fsync only one time every second. Compromise. +# +# The default is "everysec", as that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# More details please check the following article: +# http://antirez.com/post/redis-persistence-demystified.html +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving, the durability of Redis is +# the same as "appendfsync none". In practical terms, this means that it is +# possible to lose up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. + +no-appendfsync-on-rewrite no + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size grows by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (if no rewrite has happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a percentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +# Max execution time of a Lua script in milliseconds. +# +# If the maximum execution time is reached Redis will log that a script is +# still in execution after the maximum allowed time and will start to +# reply to queries with an error. +# +# When a long running script exceed the maximum execution time only the +# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be +# used to stop a script that did not yet called write commands. The second +# is the only way to shut down the server in the case a write commands was +# already issue by the script but the user don't want to wait for the natural +# termination of the script. +# +# Set it to 0 or a negative value for unlimited execution without warnings. +lua-time-limit 5000 + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 128 + +############################# Event notification ############################## + +# Redis can notify Pub/Sub clients about events happening in the key space. +# This feature is documented at http://redis.io/topics/keyspace-events +# +# For instance if keyspace events notification is enabled, and a client +# performs a DEL operation on key "foo" stored in the Database 0, two +# messages will be published via Pub/Sub: +# +# PUBLISH __keyspace@0__:foo del +# PUBLISH __keyevent@0__:del foo +# +# It is possible to select the events that Redis will notify among a set +# of classes. Every class is identified by a single character: +# +# K Keyspace events, published with __keyspace@__ prefix. +# E Keyevent events, published with __keyevent@__ prefix. +# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... +# $ String commands +# l List commands +# s Set commands +# h Hash commands +# z Sorted set commands +# x Expired events (events generated every time a key expires) +# e Evicted events (events generated when a key is evicted for maxmemory) +# A Alias for g$lshzxe, so that the "AKE" string means all the events. +# +# The "notify-keyspace-events" takes as argument a string that is composed +# by zero or multiple characters. The empty string means that notifications +# are disabled at all. +# +# Example: to enable list and generic events, from the point of view of the +# event name, use: +# +# notify-keyspace-events Elg +# +# Example 2: to get the stream of the expired keys subscribing to channel +# name __keyevent@0__:expired use: +# +# notify-keyspace-events Ex +# +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "" + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +# Similarly to hashes, small lists are also encoded in a special way in order +# to save a lot of space. The special representation is only used when +# you are under the following limits: +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happens to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +# HyperLogLog sparse representation bytes limit. The limit includes the +# 16 bytes header. When an HyperLogLog using the sparse representation crosses +# this limit, it is converted into the dense representation. +# +# A value greater than 16000 is totally useless, since at that point the +# dense representation is more memory efficient. +# +# The suggested value is ~ 3000 in order to have the benefits of +# the space efficient encoding without slowing down too much PFADD, +# which is O(N) with the sparse encoding. The value can be raised to +# ~ 10000 when CPU is not a concern, but space is, and the data set is +# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. +hll-sparse-max-bytes 3000 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation Redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into a hash table +# that is rehashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# active rehashing the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply form time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# The limit can be set differently for the three different classes of clients: +# +# normal -> normal clients including MONITOR clients +# slave -> slave clients +# pubsub -> clients subscribed to at least one pubsub channel or pattern +# +# The syntax of every client-output-buffer-limit directive is the following: +# +# client-output-buffer-limit +# +# A client is immediately disconnected once the hard limit is reached, or if +# the soft limit is reached and remains reached for the specified number of +# seconds (continuously). +# So for instance if the hard limit is 32 megabytes and the soft limit is +# 16 megabytes / 10 seconds, the client will get disconnected immediately +# if the size of the output buffers reach 32 megabytes, but will also get +# disconnected if the client reaches 16 megabytes and continuously overcomes +# the limit for 10 seconds. +# +# By default normal clients are not limited because they don't receive data +# without asking (in a push way), but just after a request, so only +# asynchronous clients may create a scenario where data is requested faster +# than it can read. +# +# Instead there is a default limit for pubsub and slave clients, since +# subscribers and slaves receive data in a push fashion. +# +# Both the hard or the soft limit can be disabled by setting them to zero. +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Redis calls an internal function to perform many background tasks, like +# closing connections of clients in timeout, purging expired keys that are +# never requested, and so forth. +# +# Not all tasks are performed with the same frequency, but Redis checks for +# tasks to perform accordingly to the specified "hz" value. +# +# By default "hz" is set to 10. Raising the value will use more CPU when +# Redis is idle, but at the same time will make Redis more responsive when +# there are many keys expiring at the same time, and timeouts may be +# handled with more precision. +# +# The range is between 1 and 500, however a value over 100 is usually not +# a good idea. Most users should use the default of 10 and raise this up to +# 100 only in environments where very low latency is required. +hz 10 + +# When a child rewrites the AOF file, if the following option is enabled +# the file will be fsync-ed every 32 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +aof-rewrite-incremental-fsync yes + diff --git a/app/config/_os/etc/redis.conf.new b/app/config/_os/etc/redis.conf.new index b37f4b5c..37843121 100644 --- a/app/config/_os/etc/redis.conf.new +++ b/app/config/_os/etc/redis.conf.new @@ -1,753 +1,753 @@ -# Redis configuration file example - -# Note on units: when memory size is needed, it is possible to specify -# it in the usual form of 1k 5GB 4M and so forth: -# -# 1k => 1000 bytes -# 1kb => 1024 bytes -# 1m => 1000000 bytes -# 1mb => 1024*1024 bytes -# 1g => 1000000000 bytes -# 1gb => 1024*1024*1024 bytes -# -# units are case insensitive so 1GB 1Gb 1gB are all the same. - -################################## INCLUDES ################################### - -# Include one or more other config files here. This is useful if you -# have a standard template that goes to all Redis server but also need -# to customize a few per-server settings. Include files can include -# other files, so use this wisely. -# -# Notice option "include" won't be rewritten by command "CONFIG REWRITE" -# from admin or Redis Sentinel. Since Redis always uses the last processed -# line as value of a configuration directive, you'd better put includes -# at the beginning of this file to avoid overwriting config change at runtime. -# -# If instead you are interested in using includes to override configuration -# options, it is better to use include as the last line. -# -# include /path/to/local.conf -# include /path/to/other.conf - -################################ GENERAL ##################################### - -# By default Redis does not run as a daemon. Use 'yes' if you need it. -# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. -daemonize no - -# When running daemonized, Redis writes a pid file in /var/run/redis.pid by -# default. You can specify a custom pid file location here. -pidfile /run/redis/redis.pid - -# Accept connections on the specified port, default is 6379. -# If port 0 is specified Redis will not listen on a TCP socket. -#port 6379 -port 0 - -# TCP listen() backlog. -# -# In high requests-per-second environments you need an high backlog in order -# to avoid slow clients connections issues. Note that the Linux kernel -# will silently truncate it to the value of /proc/sys/net/core/somaxconn so -# make sure to raise both the value of somaxconn and tcp_max_syn_backlog -# in order to get the desired effect. -tcp-backlog 511 - -# By default Redis listens for connections from all the network interfaces -# available on the server. It is possible to listen to just one or multiple -# interfaces using the "bind" configuration directive, followed by one or -# more IP addresses. -# -# Examples: -# -# bind 192.168.1.100 10.0.0.1 -bind 127.0.0.1 - -# Specify the path for the Unix socket that will be used to listen for -# incoming connections. There is no default, so Redis will not listen -# on a unix socket when not specified. -# -unixsocket /tmp/redis.sock -unixsocketperm 777 - -# Close the connection after a client is idle for N seconds (0 to disable) -timeout 0 - -# TCP keepalive. -# -# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence -# of communication. This is useful for two reasons: -# -# 1) Detect dead peers. -# 2) Take the connection alive from the point of view of network -# equipment in the middle. -# -# On Linux, the specified value (in seconds) is the period used to send ACKs. -# Note that to close the connection the double of the time is needed. -# On other kernels the period depends on the kernel configuration. -# -# A reasonable value for this option is 60 seconds. -tcp-keepalive 0 - -# Specify the server verbosity level. -# This can be one of: -# debug (a lot of information, useful for development/testing) -# verbose (many rarely useful info, but not a mess like the debug level) -# notice (moderately verbose, what you want in production probably) -# warning (only very important / critical messages are logged) -loglevel warning - -# Specify the log file name. Also the empty string can be used to force -# Redis to log on the standard output. Note that if you use standard -# output for logging but daemonize, logs will be sent to /dev/null -logfile "" - -# To enable logging to the system logger, just set 'syslog-enabled' to yes, -# and optionally update the other syslog parameters to suit your needs. -# syslog-enabled no - -# Specify the syslog identity. -# syslog-ident redis - -# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. -# syslog-facility local0 - -# Set the number of databases. The default database is DB 0, you can select -# a different one on a per-connection basis using SELECT where -# dbid is a number between 0 and 'databases'-1 -databases 1 - -################################ SNAPSHOTTING ################################ -# -# Save the DB on disk: -# -# save -# -# Will save the DB if both the given number of seconds and the given -# number of write operations against the DB occurred. -# -# In the example below the behaviour will be to save: -# after 900 sec (15 min) if at least 1 key changed -# after 300 sec (5 min) if at least 10 keys changed -# after 60 sec if at least 10000 keys changed -# -# Note: you can disable saving at all commenting all the "save" lines. -# -# It is also possible to remove all the previously configured save -# points by adding a save directive with a single empty string argument -# like in the following example: -# -# save "" - -save 900 1 -save 300 10 -save 60 10000 - -# By default Redis will stop accepting writes if RDB snapshots are enabled -# (at least one save point) and the latest background save failed. -# This will make the user aware (in a hard way) that data is not persisting -# on disk properly, otherwise chances are that no one will notice and some -# disaster will happen. -# -# If the background saving process will start working again Redis will -# automatically allow writes again. -# -# However if you have setup your proper monitoring of the Redis server -# and persistence, you may want to disable this feature so that Redis will -# continue to work as usual even if there are problems with disk, -# permissions, and so forth. -stop-writes-on-bgsave-error yes - -# Compress string objects using LZF when dump .rdb databases? -# For default that's set to 'yes' as it's almost always a win. -# If you want to save some CPU in the saving child set it to 'no' but -# the dataset will likely be bigger if you have compressible values or keys. -rdbcompression no - -# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. -# This makes the format more resistant to corruption but there is a performance -# hit to pay (around 10%) when saving and loading RDB files, so you can disable it -# for maximum performances. -# -# RDB files created with checksum disabled have a checksum of zero that will -# tell the loading code to skip the check. -rdbchecksum no - -# The filename where to dump the DB -dbfilename rune.rdb - -# The working directory. -# -# The DB will be written inside this directory, with the filename specified -# above using the 'dbfilename' configuration directive. -# -# The Append Only File will also be created inside this directory. -# -# Note that you must specify a directory here, not a file name. -dir /var/lib/redis/ - -################################# REPLICATION ################################# - -# Master-Slave replication. Use slaveof to make a Redis instance a copy of -# another Redis server. Note that the configuration is local to the slave -# so for example it is possible to configure the slave to save the DB with a -# different interval, or to listen to another port, and so on. -# -# slaveof - -# If the master is password protected (using the "requirepass" configuration -# directive below) it is possible to tell the slave to authenticate before -# starting the replication synchronization process, otherwise the master will -# refuse the slave request. -# -# masterauth - -# When a slave loses its connection with the master, or when the replication -# is still in progress, the slave can act in two different ways: -# -# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will -# still reply to client requests, possibly with out of date data, or the -# data set may just be empty if this is the first synchronization. -# -# 2) if slave-serve-stale-data is set to 'no' the slave will reply with -# an error "SYNC with master in progress" to all the kind of commands -# but to INFO and SLAVEOF. -# -slave-serve-stale-data yes - -# You can configure a slave instance to accept writes or not. Writing against -# a slave instance may be useful to store some ephemeral data (because data -# written on a slave will be easily deleted after resync with the master) but -# may also cause problems if clients are writing to it because of a -# misconfiguration. -# -# Since Redis 2.6 by default slaves are read-only. -# -# Note: read only slaves are not designed to be exposed to untrusted clients -# on the internet. It's just a protection layer against misuse of the instance. -# Still a read only slave exports by default all the administrative commands -# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve -# security of read only slaves using 'rename-command' to shadow all the -# administrative / dangerous commands. -slave-read-only yes - -# Slaves send PINGs to server in a predefined interval. It's possible to change -# this interval with the repl_ping_slave_period option. The default value is 10 -# seconds. -# -# repl-ping-slave-period 10 - -# The following option sets the replication timeout for: -# -# 1) Bulk transfer I/O during SYNC, from the point of view of slave. -# 2) Master timeout from the point of view of slaves (data, pings). -# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings). -# -# It is important to make sure that this value is greater than the value -# specified for repl-ping-slave-period otherwise a timeout will be detected -# every time there is low traffic between the master and the slave. -# -# repl-timeout 60 - -# Disable TCP_NODELAY on the slave socket after SYNC? -# -# If you select "yes" Redis will use a smaller number of TCP packets and -# less bandwidth to send data to slaves. But this can add a delay for -# the data to appear on the slave side, up to 40 milliseconds with -# Linux kernels using a default configuration. -# -# If you select "no" the delay for data to appear on the slave side will -# be reduced but more bandwidth will be used for replication. -# -# By default we optimize for low latency, but in very high traffic conditions -# or when the master and slaves are many hops away, turning this to "yes" may -# be a good idea. -repl-disable-tcp-nodelay no - -# Set the replication backlog size. The backlog is a buffer that accumulates -# slave data when slaves are disconnected for some time, so that when a slave -# wants to reconnect again, often a full resync is not needed, but a partial -# resync is enough, just passing the portion of data the slave missed while -# disconnected. -# -# The biggest the replication backlog, the longer the time the slave can be -# disconnected and later be able to perform a partial resynchronization. -# -# The backlog is only allocated once there is at least a slave connected. -# -# repl-backlog-size 1mb - -# After a master has no longer connected slaves for some time, the backlog -# will be freed. The following option configures the amount of seconds that -# need to elapse, starting from the time the last slave disconnected, for -# the backlog buffer to be freed. -# -# A value of 0 means to never release the backlog. -# -# repl-backlog-ttl 3600 - -# The slave priority is an integer number published by Redis in the INFO output. -# It is used by Redis Sentinel in order to select a slave to promote into a -# master if the master is no longer working correctly. -# -# A slave with a low priority number is considered better for promotion, so -# for instance if there are three slaves with priority 10, 100, 25 Sentinel will -# pick the one with priority 10, that is the lowest. -# -# However a special priority of 0 marks the slave as not able to perform the -# role of master, so a slave with priority of 0 will never be selected by -# Redis Sentinel for promotion. -# -# By default the priority is 100. -slave-priority 100 - -# It is possible for a master to stop accepting writes if there are less than -# N slaves connected, having a lag less or equal than M seconds. -# -# The N slaves need to be in "online" state. -# -# The lag in seconds, that must be <= the specified value, is calculated from -# the last ping received from the slave, that is usually sent every second. -# -# This option does not GUARANTEES that N replicas will accept the write, but -# will limit the window of exposure for lost writes in case not enough slaves -# are available, to the specified number of seconds. -# -# For example to require at least 3 slaves with a lag <= 10 seconds use: -# -# min-slaves-to-write 3 -# min-slaves-max-lag 10 -# -# Setting one or the other to 0 disables the feature. -# -# By default min-slaves-to-write is set to 0 (feature disabled) and -# min-slaves-max-lag is set to 10. - -################################## SECURITY ################################### - -# Require clients to issue AUTH before processing any other -# commands. This might be useful in environments in which you do not trust -# others with access to the host running redis-server. -# -# This should stay commented out for backward compatibility and because most -# people do not need auth (e.g. they run their own servers). -# -# Warning: since Redis is pretty fast an outside user can try up to -# 150k passwords per second against a good box. This means that you should -# use a very strong password otherwise it will be very easy to break. -# -# requirepass foobared - -# Command renaming. -# -# It is possible to change the name of dangerous commands in a shared -# environment. For instance the CONFIG command may be renamed into something -# hard to guess so that it will still be available for internal-use tools -# but not available for general clients. -# -# Example: -# -# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 -# -# It is also possible to completely kill a command by renaming it into -# an empty string: -# -# rename-command CONFIG "" -# -# Please note that changing the name of commands that are logged into the -# AOF file or transmitted to slaves may cause problems. - -################################### LIMITS #################################### - -# Set the max number of connected clients at the same time. By default -# this limit is set to 10000 clients, however if the Redis server is not -# able to configure the process file limit to allow for the specified limit -# the max number of allowed clients is set to the current file limit -# minus 32 (as Redis reserves a few file descriptors for internal uses). -# -# Once the limit is reached Redis will close all the new connections sending -# an error 'max number of clients reached'. -# -# maxclients 10000 - -# Don't use more memory than the specified amount of bytes. -# When the memory limit is reached Redis will try to remove keys -# according to the eviction policy selected (see maxmemory-policy). -# -# If Redis can't remove keys according to the policy, or if the policy is -# set to 'noeviction', Redis will start to reply with errors to commands -# that would use more memory, like SET, LPUSH, and so on, and will continue -# to reply to read-only commands like GET. -# -# This option is usually useful when using Redis as an LRU cache, or to set -# a hard memory limit for an instance (using the 'noeviction' policy). -# -# WARNING: If you have slaves attached to an instance with maxmemory on, -# the size of the output buffers needed to feed the slaves are subtracted -# from the used memory count, so that network problems / resyncs will -# not trigger a loop where keys are evicted, and in turn the output -# buffer of slaves is full with DELs of keys evicted triggering the deletion -# of more keys, and so forth until the database is completely emptied. -# -# In short... if you have slaves attached it is suggested that you set a lower -# limit for maxmemory so that there is some free RAM on the system for slave -# output buffers (but this is not needed if the policy is 'noeviction'). -# -# maxmemory - -# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory -# is reached. You can select among five behaviors: -# -# volatile-lru -> remove the key with an expire set using an LRU algorithm -# allkeys-lru -> remove any key accordingly to the LRU algorithm -# volatile-random -> remove a random key with an expire set -# allkeys-random -> remove a random key, any key -# volatile-ttl -> remove the key with the nearest expire time (minor TTL) -# noeviction -> don't expire at all, just return an error on write operations -# -# Note: with any of the above policies, Redis will return an error on write -# operations, when there are not suitable keys for eviction. -# -# At the date of writing this commands are: set setnx setex append -# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd -# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby -# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby -# getset mset msetnx exec sort -# -# The default is: -# -# maxmemory-policy volatile-lru - -# LRU and minimal TTL algorithms are not precise algorithms but approximated -# algorithms (in order to save memory), so you can select as well the sample -# size to check. For instance for default Redis will check three keys and -# pick the one that was used less recently, you can change the sample size -# using the following configuration directive. -# -# maxmemory-samples 3 - -############################## APPEND ONLY MODE ############################### - -# By default Redis asynchronously dumps the dataset on disk. This mode is -# good enough in many applications, but an issue with the Redis process or -# a power outage may result into a few minutes of writes lost (depending on -# the configured save points). -# -# The Append Only File is an alternative persistence mode that provides -# much better durability. For instance using the default data fsync policy -# (see later in the config file) Redis can lose just one second of writes in a -# dramatic event like a server power outage, or a single write if something -# wrong with the Redis process itself happens, but the operating system is -# still running correctly. -# -# AOF and RDB persistence can be enabled at the same time without problems. -# If the AOF is enabled on startup Redis will load the AOF, that is the file -# with the better durability guarantees. -# -# Please check http://redis.io/topics/persistence for more information. - -appendonly no - -# The name of the append only file (default: "appendonly.aof") - -appendfilename "appendonly.aof" - -# The fsync() call tells the Operating System to actually write data on disk -# instead to wait for more data in the output buffer. Some OS will really flush -# data on disk, some other OS will just try to do it ASAP. -# -# Redis supports three different modes: -# -# no: don't fsync, just let the OS flush the data when it wants. Faster. -# always: fsync after every write to the append only log . Slow, Safest. -# everysec: fsync only one time every second. Compromise. -# -# The default is "everysec", as that's usually the right compromise between -# speed and data safety. It's up to you to understand if you can relax this to -# "no" that will let the operating system flush the output buffer when -# it wants, for better performances (but if you can live with the idea of -# some data loss consider the default persistence mode that's snapshotting), -# or on the contrary, use "always" that's very slow but a bit safer than -# everysec. -# -# More details please check the following article: -# http://antirez.com/post/redis-persistence-demystified.html -# -# If unsure, use "everysec". - -# appendfsync always -# appendfsync everysec -appendfsync no - -# When the AOF fsync policy is set to always or everysec, and a background -# saving process (a background save or AOF log background rewriting) is -# performing a lot of I/O against the disk, in some Linux configurations -# Redis may block too long on the fsync() call. Note that there is no fix for -# this currently, as even performing fsync in a different thread will block -# our synchronous write(2) call. -# -# In order to mitigate this problem it's possible to use the following option -# that will prevent fsync() from being called in the main process while a -# BGSAVE or BGREWRITEAOF is in progress. -# -# This means that while another child is saving, the durability of Redis is -# the same as "appendfsync none". In practical terms, this means that it is -# possible to lose up to 30 seconds of log in the worst scenario (with the -# default Linux settings). -# -# If you have latency problems turn this to "yes". Otherwise leave it as -# "no" that is the safest pick from the point of view of durability. - -no-appendfsync-on-rewrite no - -# Automatic rewrite of the append only file. -# Redis is able to automatically rewrite the log file implicitly calling -# BGREWRITEAOF when the AOF log size grows by the specified percentage. -# -# This is how it works: Redis remembers the size of the AOF file after the -# latest rewrite (if no rewrite has happened since the restart, the size of -# the AOF at startup is used). -# -# This base size is compared to the current size. If the current size is -# bigger than the specified percentage, the rewrite is triggered. Also -# you need to specify a minimal size for the AOF file to be rewritten, this -# is useful to avoid rewriting the AOF file even if the percentage increase -# is reached but it is still pretty small. -# -# Specify a percentage of zero in order to disable the automatic AOF -# rewrite feature. - -auto-aof-rewrite-percentage 100 -auto-aof-rewrite-min-size 64mb - -################################ LUA SCRIPTING ############################### - -# Max execution time of a Lua script in milliseconds. -# -# If the maximum execution time is reached Redis will log that a script is -# still in execution after the maximum allowed time and will start to -# reply to queries with an error. -# -# When a long running script exceed the maximum execution time only the -# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be -# used to stop a script that did not yet called write commands. The second -# is the only way to shut down the server in the case a write commands was -# already issue by the script but the user don't want to wait for the natural -# termination of the script. -# -# Set it to 0 or a negative value for unlimited execution without warnings. -lua-time-limit 5000 - -################################## SLOW LOG ################################### - -# The Redis Slow Log is a system to log queries that exceeded a specified -# execution time. The execution time does not include the I/O operations -# like talking with the client, sending the reply and so forth, -# but just the time needed to actually execute the command (this is the only -# stage of command execution where the thread is blocked and can not serve -# other requests in the meantime). -# -# You can configure the slow log with two parameters: one tells Redis -# what is the execution time, in microseconds, to exceed in order for the -# command to get logged, and the other parameter is the length of the -# slow log. When a new command is logged the oldest one is removed from the -# queue of logged commands. - -# The following time is expressed in microseconds, so 1000000 is equivalent -# to one second. Note that a negative number disables the slow log, while -# a value of zero forces the logging of every command. -slowlog-log-slower-than 10000 - -# There is no limit to this length. Just be aware that it will consume memory. -# You can reclaim memory used by the slow log with SLOWLOG RESET. -slowlog-max-len 128 - -################################ LATENCY MONITOR ############################## - -# The Redis latency monitoring subsystem samples different operations -# at runtime in order to collect data related to possible sources of -# latency of a Redis instance. -# -# Via the LATENCY command this information is available to the user that can -# print graphs and obtain reports. -# -# The system only logs operations that were performed in a time equal or -# greater than the amount of milliseconds specified via the -# latency-monitor-threshold configuration directive. When its value is set -# to zero, the latency monitor is turned off. -# -# By default latency monitoring is disabled since it is mostly not needed -# if you don't have latency issues, and collecting data has a performance -# impact, that while very small, can be measured under big load. Latency -# monitoring can easily be enalbed at runtime using the command -# "CONFIG SET latency-monitor-threshold " if needed. -latency-monitor-threshold 0 - -############################# Event notification ############################## - -# Redis can notify Pub/Sub clients about events happening in the key space. -# This feature is documented at http://redis.io/topics/keyspace-events -# -# For instance if keyspace events notification is enabled, and a client -# performs a DEL operation on key "foo" stored in the Database 0, two -# messages will be published via Pub/Sub: -# -# PUBLISH __keyspace@0__:foo del -# PUBLISH __keyevent@0__:del foo -# -# It is possible to select the events that Redis will notify among a set -# of classes. Every class is identified by a single character: -# -# K Keyspace events, published with __keyspace@__ prefix. -# E Keyevent events, published with __keyevent@__ prefix. -# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... -# $ String commands -# l List commands -# s Set commands -# h Hash commands -# z Sorted set commands -# x Expired events (events generated every time a key expires) -# e Evicted events (events generated when a key is evicted for maxmemory) -# A Alias for g$lshzxe, so that the "AKE" string means all the events. -# -# The "notify-keyspace-events" takes as argument a string that is composed -# by zero or multiple characters. The empty string means that notifications -# are disabled at all. -# -# Example: to enable list and generic events, from the point of view of the -# event name, use: -# -# notify-keyspace-events Elg -# -# Example 2: to get the stream of the expired keys subscribing to channel -# name __keyevent@0__:expired use: -# -# notify-keyspace-events Ex -# -# By default all notifications are disabled because most users don't need -# this feature and the feature has some overhead. Note that if you don't -# specify at least one of K or E, no events will be delivered. -notify-keyspace-events "" - -############################### ADVANCED CONFIG ############################### - -# Hashes are encoded using a memory efficient data structure when they have a -# small number of entries, and the biggest entry does not exceed a given -# threshold. These thresholds can be configured using the following directives. -hash-max-ziplist-entries 512 -hash-max-ziplist-value 64 - -# Similarly to hashes, small lists are also encoded in a special way in order -# to save a lot of space. The special representation is only used when -# you are under the following limits: -list-max-ziplist-entries 512 -list-max-ziplist-value 64 - -# Sets have a special encoding in just one case: when a set is composed -# of just strings that happens to be integers in radix 10 in the range -# of 64 bit signed integers. -# The following configuration setting sets the limit in the size of the -# set in order to use this special memory saving encoding. -set-max-intset-entries 512 - -# Similarly to hashes and lists, sorted sets are also specially encoded in -# order to save a lot of space. This encoding is only used when the length and -# elements of a sorted set are below the following limits: -zset-max-ziplist-entries 128 -zset-max-ziplist-value 64 - -# HyperLogLog sparse representation bytes limit. The limit includes the -# 16 bytes header. When an HyperLogLog using the sparse representation crosses -# this limit, it is converted into the dense representation. -# -# A value greater than 16000 is totally useless, since at that point the -# dense representation is more memory efficient. -# -# The suggested value is ~ 3000 in order to have the benefits of -# the space efficient encoding without slowing down too much PFADD, -# which is O(N) with the sparse encoding. The value can be raised to -# ~ 10000 when CPU is not a concern, but space is, and the data set is -# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. -hll-sparse-max-bytes 3000 - -# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in -# order to help rehashing the main Redis hash table (the one mapping top-level -# keys to values). The hash table implementation Redis uses (see dict.c) -# performs a lazy rehashing: the more operation you run into a hash table -# that is rehashing, the more rehashing "steps" are performed, so if the -# server is idle the rehashing is never complete and some more memory is used -# by the hash table. -# -# The default is to use this millisecond 10 times every second in order to -# active rehashing the main dictionaries, freeing memory when possible. -# -# If unsure: -# use "activerehashing no" if you have hard latency requirements and it is -# not a good thing in your environment that Redis can reply form time to time -# to queries with 2 milliseconds delay. -# -# use "activerehashing yes" if you don't have such hard requirements but -# want to free memory asap when possible. -activerehashing no - -# The client output buffer limits can be used to force disconnection of clients -# that are not reading data from the server fast enough for some reason (a -# common reason is that a Pub/Sub client can't consume messages as fast as the -# publisher can produce them). -# -# The limit can be set differently for the three different classes of clients: -# -# normal -> normal clients including MONITOR clients -# slave -> slave clients -# pubsub -> clients subscribed to at least one pubsub channel or pattern -# -# The syntax of every client-output-buffer-limit directive is the following: -# -# client-output-buffer-limit -# -# A client is immediately disconnected once the hard limit is reached, or if -# the soft limit is reached and remains reached for the specified number of -# seconds (continuously). -# So for instance if the hard limit is 32 megabytes and the soft limit is -# 16 megabytes / 10 seconds, the client will get disconnected immediately -# if the size of the output buffers reach 32 megabytes, but will also get -# disconnected if the client reaches 16 megabytes and continuously overcomes -# the limit for 10 seconds. -# -# By default normal clients are not limited because they don't receive data -# without asking (in a push way), but just after a request, so only -# asynchronous clients may create a scenario where data is requested faster -# than it can read. -# -# Instead there is a default limit for pubsub and slave clients, since -# subscribers and slaves receive data in a push fashion. -# -# Both the hard or the soft limit can be disabled by setting them to zero. -client-output-buffer-limit normal 0 0 0 -client-output-buffer-limit slave 256mb 64mb 60 -client-output-buffer-limit pubsub 32mb 8mb 60 - -# Redis calls an internal function to perform many background tasks, like -# closing connections of clients in timeout, purging expired keys that are -# never requested, and so forth. -# -# Not all tasks are performed with the same frequency, but Redis checks for -# tasks to perform accordingly to the specified "hz" value. -# -# By default "hz" is set to 10. Raising the value will use more CPU when -# Redis is idle, but at the same time will make Redis more responsive when -# there are many keys expiring at the same time, and timeouts may be -# handled with more precision. -# -# The range is between 1 and 500, however a value over 100 is usually not -# a good idea. Most users should use the default of 10 and raise this up to -# 100 only in environments where very low latency is required. -hz 5 - -# When a child rewrites the AOF file, if the following option is enabled -# the file will be fsync-ed every 32 MB of data generated. This is useful -# in order to commit the file to the disk more incrementally and avoid -# big latency spikes. -aof-rewrite-incremental-fsync yes - +# Redis configuration file example + +# Note on units: when memory size is needed, it is possible to specify +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis server but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Notice option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# include /path/to/local.conf +# include /path/to/other.conf + +################################ GENERAL ##################################### + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +daemonize no + +# When running daemonized, Redis writes a pid file in /var/run/redis.pid by +# default. You can specify a custom pid file location here. +pidfile /run/redis/redis.pid + +# Accept connections on the specified port, default is 6379. +# If port 0 is specified Redis will not listen on a TCP socket. +#port 6379 +port 0 + +# TCP listen() backlog. +# +# In high requests-per-second environments you need an high backlog in order +# to avoid slow clients connections issues. Note that the Linux kernel +# will silently truncate it to the value of /proc/sys/net/core/somaxconn so +# make sure to raise both the value of somaxconn and tcp_max_syn_backlog +# in order to get the desired effect. +tcp-backlog 511 + +# By default Redis listens for connections from all the network interfaces +# available on the server. It is possible to listen to just one or multiple +# interfaces using the "bind" configuration directive, followed by one or +# more IP addresses. +# +# Examples: +# +# bind 192.168.1.100 10.0.0.1 +bind 127.0.0.1 + +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +unixsocket /tmp/redis.sock +unixsocketperm 777 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# TCP keepalive. +# +# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence +# of communication. This is useful for two reasons: +# +# 1) Detect dead peers. +# 2) Take the connection alive from the point of view of network +# equipment in the middle. +# +# On Linux, the specified value (in seconds) is the period used to send ACKs. +# Note that to close the connection the double of the time is needed. +# On other kernels the period depends on the kernel configuration. +# +# A reasonable value for this option is 60 seconds. +tcp-keepalive 0 + +# Specify the server verbosity level. +# This can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel warning + +# Specify the log file name. Also the empty string can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile "" + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +# syslog-enabled no + +# Specify the syslog identity. +# syslog-ident redis + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +# syslog-facility local0 + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 1 + +################################ SNAPSHOTTING ################################ +# +# Save the DB on disk: +# +# save +# +# Will save the DB if both the given number of seconds and the given +# number of write operations against the DB occurred. +# +# In the example below the behaviour will be to save: +# after 900 sec (15 min) if at least 1 key changed +# after 300 sec (5 min) if at least 10 keys changed +# after 60 sec if at least 10000 keys changed +# +# Note: you can disable saving at all commenting all the "save" lines. +# +# It is also possible to remove all the previously configured save +# points by adding a save directive with a single empty string argument +# like in the following example: +# +# save "" + +save 900 1 +save 300 10 +save 60 10000 + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in a hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# disaster will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usual even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error yes + +# Compress string objects using LZF when dump .rdb databases? +# For default that's set to 'yes' as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression no + +# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum no + +# The filename where to dump the DB +dbfilename rune.rdb + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# The Append Only File will also be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir /var/lib/redis/ + +################################# REPLICATION ################################# + +# Master-Slave replication. Use slaveof to make a Redis instance a copy of +# another Redis server. Note that the configuration is local to the slave +# so for example it is possible to configure the slave to save the DB with a +# different interval, or to listen to another port, and so on. +# +# slaveof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the slave to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the slave request. +# +# masterauth + +# When a slave loses its connection with the master, or when the replication +# is still in progress, the slave can act in two different ways: +# +# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will +# still reply to client requests, possibly with out of date data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) if slave-serve-stale-data is set to 'no' the slave will reply with +# an error "SYNC with master in progress" to all the kind of commands +# but to INFO and SLAVEOF. +# +slave-serve-stale-data yes + +# You can configure a slave instance to accept writes or not. Writing against +# a slave instance may be useful to store some ephemeral data (because data +# written on a slave will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default slaves are read-only. +# +# Note: read only slaves are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only slave exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve +# security of read only slaves using 'rename-command' to shadow all the +# administrative / dangerous commands. +slave-read-only yes + +# Slaves send PINGs to server in a predefined interval. It's possible to change +# this interval with the repl_ping_slave_period option. The default value is 10 +# seconds. +# +# repl-ping-slave-period 10 + +# The following option sets the replication timeout for: +# +# 1) Bulk transfer I/O during SYNC, from the point of view of slave. +# 2) Master timeout from the point of view of slaves (data, pings). +# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings). +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-slave-period otherwise a timeout will be detected +# every time there is low traffic between the master and the slave. +# +# repl-timeout 60 + +# Disable TCP_NODELAY on the slave socket after SYNC? +# +# If you select "yes" Redis will use a smaller number of TCP packets and +# less bandwidth to send data to slaves. But this can add a delay for +# the data to appear on the slave side, up to 40 milliseconds with +# Linux kernels using a default configuration. +# +# If you select "no" the delay for data to appear on the slave side will +# be reduced but more bandwidth will be used for replication. +# +# By default we optimize for low latency, but in very high traffic conditions +# or when the master and slaves are many hops away, turning this to "yes" may +# be a good idea. +repl-disable-tcp-nodelay no + +# Set the replication backlog size. The backlog is a buffer that accumulates +# slave data when slaves are disconnected for some time, so that when a slave +# wants to reconnect again, often a full resync is not needed, but a partial +# resync is enough, just passing the portion of data the slave missed while +# disconnected. +# +# The biggest the replication backlog, the longer the time the slave can be +# disconnected and later be able to perform a partial resynchronization. +# +# The backlog is only allocated once there is at least a slave connected. +# +# repl-backlog-size 1mb + +# After a master has no longer connected slaves for some time, the backlog +# will be freed. The following option configures the amount of seconds that +# need to elapse, starting from the time the last slave disconnected, for +# the backlog buffer to be freed. +# +# A value of 0 means to never release the backlog. +# +# repl-backlog-ttl 3600 + +# The slave priority is an integer number published by Redis in the INFO output. +# It is used by Redis Sentinel in order to select a slave to promote into a +# master if the master is no longer working correctly. +# +# A slave with a low priority number is considered better for promotion, so +# for instance if there are three slaves with priority 10, 100, 25 Sentinel will +# pick the one with priority 10, that is the lowest. +# +# However a special priority of 0 marks the slave as not able to perform the +# role of master, so a slave with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +slave-priority 100 + +# It is possible for a master to stop accepting writes if there are less than +# N slaves connected, having a lag less or equal than M seconds. +# +# The N slaves need to be in "online" state. +# +# The lag in seconds, that must be <= the specified value, is calculated from +# the last ping received from the slave, that is usually sent every second. +# +# This option does not GUARANTEES that N replicas will accept the write, but +# will limit the window of exposure for lost writes in case not enough slaves +# are available, to the specified number of seconds. +# +# For example to require at least 3 slaves with a lag <= 10 seconds use: +# +# min-slaves-to-write 3 +# min-slaves-max-lag 10 +# +# Setting one or the other to 0 disables the feature. +# +# By default min-slaves-to-write is set to 0 (feature disabled) and +# min-slaves-max-lag is set to 10. + +################################## SECURITY ################################### + +# Require clients to issue AUTH before processing any other +# commands. This might be useful in environments in which you do not trust +# others with access to the host running redis-server. +# +# This should stay commented out for backward compatibility and because most +# people do not need auth (e.g. they run their own servers). +# +# Warning: since Redis is pretty fast an outside user can try up to +# 150k passwords per second against a good box. This means that you should +# use a very strong password otherwise it will be very easy to break. +# +# requirepass foobared + +# Command renaming. +# +# It is possible to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# hard to guess so that it will still be available for internal-use tools +# but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possible to completely kill a command by renaming it into +# an empty string: +# +# rename-command CONFIG "" +# +# Please note that changing the name of commands that are logged into the +# AOF file or transmitted to slaves may cause problems. + +################################### LIMITS #################################### + +# Set the max number of connected clients at the same time. By default +# this limit is set to 10000 clients, however if the Redis server is not +# able to configure the process file limit to allow for the specified limit +# the max number of allowed clients is set to the current file limit +# minus 32 (as Redis reserves a few file descriptors for internal uses). +# +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# maxclients 10000 + +# Don't use more memory than the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys +# according to the eviction policy selected (see maxmemory-policy). +# +# If Redis can't remove keys according to the policy, or if the policy is +# set to 'noeviction', Redis will start to reply with errors to commands +# that would use more memory, like SET, LPUSH, and so on, and will continue +# to reply to read-only commands like GET. +# +# This option is usually useful when using Redis as an LRU cache, or to set +# a hard memory limit for an instance (using the 'noeviction' policy). +# +# WARNING: If you have slaves attached to an instance with maxmemory on, +# the size of the output buffers needed to feed the slaves are subtracted +# from the used memory count, so that network problems / resyncs will +# not trigger a loop where keys are evicted, and in turn the output +# buffer of slaves is full with DELs of keys evicted triggering the deletion +# of more keys, and so forth until the database is completely emptied. +# +# In short... if you have slaves attached it is suggested that you set a lower +# limit for maxmemory so that there is some free RAM on the system for slave +# output buffers (but this is not needed if the policy is 'noeviction'). +# +# maxmemory + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached. You can select among five behaviors: +# +# volatile-lru -> remove the key with an expire set using an LRU algorithm +# allkeys-lru -> remove any key accordingly to the LRU algorithm +# volatile-random -> remove a random key with an expire set +# allkeys-random -> remove a random key, any key +# volatile-ttl -> remove the key with the nearest expire time (minor TTL) +# noeviction -> don't expire at all, just return an error on write operations +# +# Note: with any of the above policies, Redis will return an error on write +# operations, when there are not suitable keys for eviction. +# +# At the date of writing this commands are: set setnx setex append +# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd +# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby +# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby +# getset mset msetnx exec sort +# +# The default is: +# +# maxmemory-policy volatile-lru + +# LRU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can select as well the sample +# size to check. For instance for default Redis will check three keys and +# pick the one that was used less recently, you can change the sample size +# using the following configuration directive. +# +# maxmemory-samples 3 + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). +# +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. +# +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check http://redis.io/topics/persistence for more information. + +appendonly no + +# The name of the append only file (default: "appendonly.aof") + +appendfilename "appendonly.aof" + +# The fsync() call tells the Operating System to actually write data on disk +# instead to wait for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log . Slow, Safest. +# everysec: fsync only one time every second. Compromise. +# +# The default is "everysec", as that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# More details please check the following article: +# http://antirez.com/post/redis-persistence-demystified.html +# +# If unsure, use "everysec". + +# appendfsync always +# appendfsync everysec +appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving, the durability of Redis is +# the same as "appendfsync none". In practical terms, this means that it is +# possible to lose up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. + +no-appendfsync-on-rewrite no + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size grows by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (if no rewrite has happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a percentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +################################ LUA SCRIPTING ############################### + +# Max execution time of a Lua script in milliseconds. +# +# If the maximum execution time is reached Redis will log that a script is +# still in execution after the maximum allowed time and will start to +# reply to queries with an error. +# +# When a long running script exceed the maximum execution time only the +# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be +# used to stop a script that did not yet called write commands. The second +# is the only way to shut down the server in the case a write commands was +# already issue by the script but the user don't want to wait for the natural +# termination of the script. +# +# Set it to 0 or a negative value for unlimited execution without warnings. +lua-time-limit 5000 + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 128 + +################################ LATENCY MONITOR ############################## + +# The Redis latency monitoring subsystem samples different operations +# at runtime in order to collect data related to possible sources of +# latency of a Redis instance. +# +# Via the LATENCY command this information is available to the user that can +# print graphs and obtain reports. +# +# The system only logs operations that were performed in a time equal or +# greater than the amount of milliseconds specified via the +# latency-monitor-threshold configuration directive. When its value is set +# to zero, the latency monitor is turned off. +# +# By default latency monitoring is disabled since it is mostly not needed +# if you don't have latency issues, and collecting data has a performance +# impact, that while very small, can be measured under big load. Latency +# monitoring can easily be enalbed at runtime using the command +# "CONFIG SET latency-monitor-threshold " if needed. +latency-monitor-threshold 0 + +############################# Event notification ############################## + +# Redis can notify Pub/Sub clients about events happening in the key space. +# This feature is documented at http://redis.io/topics/keyspace-events +# +# For instance if keyspace events notification is enabled, and a client +# performs a DEL operation on key "foo" stored in the Database 0, two +# messages will be published via Pub/Sub: +# +# PUBLISH __keyspace@0__:foo del +# PUBLISH __keyevent@0__:del foo +# +# It is possible to select the events that Redis will notify among a set +# of classes. Every class is identified by a single character: +# +# K Keyspace events, published with __keyspace@__ prefix. +# E Keyevent events, published with __keyevent@__ prefix. +# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... +# $ String commands +# l List commands +# s Set commands +# h Hash commands +# z Sorted set commands +# x Expired events (events generated every time a key expires) +# e Evicted events (events generated when a key is evicted for maxmemory) +# A Alias for g$lshzxe, so that the "AKE" string means all the events. +# +# The "notify-keyspace-events" takes as argument a string that is composed +# by zero or multiple characters. The empty string means that notifications +# are disabled at all. +# +# Example: to enable list and generic events, from the point of view of the +# event name, use: +# +# notify-keyspace-events Elg +# +# Example 2: to get the stream of the expired keys subscribing to channel +# name __keyevent@0__:expired use: +# +# notify-keyspace-events Ex +# +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "" + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-ziplist-entries 512 +hash-max-ziplist-value 64 + +# Similarly to hashes, small lists are also encoded in a special way in order +# to save a lot of space. The special representation is only used when +# you are under the following limits: +list-max-ziplist-entries 512 +list-max-ziplist-value 64 + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happens to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-ziplist-entries 128 +zset-max-ziplist-value 64 + +# HyperLogLog sparse representation bytes limit. The limit includes the +# 16 bytes header. When an HyperLogLog using the sparse representation crosses +# this limit, it is converted into the dense representation. +# +# A value greater than 16000 is totally useless, since at that point the +# dense representation is more memory efficient. +# +# The suggested value is ~ 3000 in order to have the benefits of +# the space efficient encoding without slowing down too much PFADD, +# which is O(N) with the sparse encoding. The value can be raised to +# ~ 10000 when CPU is not a concern, but space is, and the data set is +# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. +hll-sparse-max-bytes 3000 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation Redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into a hash table +# that is rehashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# active rehashing the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply form time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing no + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# The limit can be set differently for the three different classes of clients: +# +# normal -> normal clients including MONITOR clients +# slave -> slave clients +# pubsub -> clients subscribed to at least one pubsub channel or pattern +# +# The syntax of every client-output-buffer-limit directive is the following: +# +# client-output-buffer-limit +# +# A client is immediately disconnected once the hard limit is reached, or if +# the soft limit is reached and remains reached for the specified number of +# seconds (continuously). +# So for instance if the hard limit is 32 megabytes and the soft limit is +# 16 megabytes / 10 seconds, the client will get disconnected immediately +# if the size of the output buffers reach 32 megabytes, but will also get +# disconnected if the client reaches 16 megabytes and continuously overcomes +# the limit for 10 seconds. +# +# By default normal clients are not limited because they don't receive data +# without asking (in a push way), but just after a request, so only +# asynchronous clients may create a scenario where data is requested faster +# than it can read. +# +# Instead there is a default limit for pubsub and slave clients, since +# subscribers and slaves receive data in a push fashion. +# +# Both the hard or the soft limit can be disabled by setting them to zero. +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit slave 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Redis calls an internal function to perform many background tasks, like +# closing connections of clients in timeout, purging expired keys that are +# never requested, and so forth. +# +# Not all tasks are performed with the same frequency, but Redis checks for +# tasks to perform accordingly to the specified "hz" value. +# +# By default "hz" is set to 10. Raising the value will use more CPU when +# Redis is idle, but at the same time will make Redis more responsive when +# there are many keys expiring at the same time, and timeouts may be +# handled with more precision. +# +# The range is between 1 and 500, however a value over 100 is usually not +# a good idea. Most users should use the default of 10 and raise this up to +# 100 only in environments where very low latency is required. +hz 5 + +# When a child rewrites the AOF file, if the following option is enabled +# the file will be fsync-ed every 32 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +aof-rewrite-incremental-fsync yes + diff --git a/app/config/_os/etc/samba/smb-dev.conf b/app/config/_os/etc/samba/smb-dev.conf index 2076a7dd..fafc1dfc 100644 --- a/app/config/_os/etc/samba/smb-dev.conf +++ b/app/config/_os/etc/samba/smb-dev.conf @@ -1,54 +1,54 @@ -#======================= Global Settings ======================= - -[global] - - workgroup = RUNENET - server string = RuneAudio Player - dns proxy = no - log level = 0 - syslog = 0 - security = share - guest account = root - map to guest = bad user - load printers = no - domain master = no - local master = no - preferred master = no - - -#======================= Share Definitions ======================= - - directory mask = 0775 - create mask = 0775 - -[music_store] - comment = Music Datastore root (/mnt/MPD) - path = /mnt/MPD/ - read only = no - public = yes - follow symlinks = yes - wide links = yes - -[mpd] - comment = MPD directory (/var/lib/mpd/) - path = /var/lib/mpd/ - read only = no - public = yes - follow symlinks = yes - wide links = yes - -[www] - comment = RuneAudio webroot (/var/www/) - path = /var/www/ - read only = no - public = yes - follow symlinks = yes - wide links = yes - -[etc] - comment = RuneAudio sys config dir (/etc) - path = /etc/ - read only = no - public = yes - follow symlinks = yes - wide links = yes +#======================= Global Settings ======================= + +[global] + + workgroup = RUNENET + server string = RuneAudio Player + dns proxy = no + log level = 0 + syslog = 0 + security = share + guest account = root + map to guest = bad user + load printers = no + domain master = no + local master = no + preferred master = no + + +#======================= Share Definitions ======================= + + directory mask = 0775 + create mask = 0775 + +[music_store] + comment = Music Datastore root (/mnt/MPD) + path = /mnt/MPD/ + read only = no + public = yes + follow symlinks = yes + wide links = yes + +[mpd] + comment = MPD directory (/var/lib/mpd/) + path = /var/lib/mpd/ + read only = no + public = yes + follow symlinks = yes + wide links = yes + +[www] + comment = RuneAudio webroot (/var/www/) + path = /var/www/ + read only = no + public = yes + follow symlinks = yes + wide links = yes + +[etc] + comment = RuneAudio sys config dir (/etc) + path = /etc/ + read only = no + public = yes + follow symlinks = yes + wide links = yes diff --git a/app/config/_os/etc/samba/smb-prod.conf b/app/config/_os/etc/samba/smb-prod.conf index b79d8d62..9d3d2f37 100644 --- a/app/config/_os/etc/samba/smb-prod.conf +++ b/app/config/_os/etc/samba/smb-prod.conf @@ -1,31 +1,31 @@ -#======================= Global Settings ======================= - -[global] - - workgroup = RUNENET - server string = RuneAudio Player - dns proxy = no - log level = 0 - syslog = 0 - security = share - guest account = root - map to guest = bad user - #map untrusted to domain = yes - load printers = no - domain master = no - local master = no - preferred master = no - - -#======================= Share Definitions ======================= - - directory mask = 0775 - create mask = 0775 - -[MPD] - comment = MPD root (/mnt/MPD) - path = /mnt/MPD/ - read only = no - public = yes - follow symlinks = yes - wide links = yes +#======================= Global Settings ======================= + +[global] + + workgroup = RUNENET + server string = RuneAudio Player + dns proxy = no + log level = 0 + syslog = 0 + security = share + guest account = root + map to guest = bad user + #map untrusted to domain = yes + load printers = no + domain master = no + local master = no + preferred master = no + + +#======================= Share Definitions ======================= + + directory mask = 0775 + create mask = 0775 + +[MPD] + comment = MPD root (/mnt/MPD) + path = /mnt/MPD/ + read only = no + public = yes + follow symlinks = yes + wide links = yes diff --git a/app/config/_os/etc/samba/smb.conf b/app/config/_os/etc/samba/smb.conf index d73dcec5..7b7876c0 100644 --- a/app/config/_os/etc/samba/smb.conf +++ b/app/config/_os/etc/samba/smb.conf @@ -1,54 +1,54 @@ -#======================= Global Settings ======================= - -[global] - - workgroup = RUNENET - server string = RuneAudio Player - dns proxy = no - log level = 0 - syslog = 0 - security = share - guest account = root - map to guest = bad user - load printers = no - domain master = no - local master = no - preferred master = no - - -#======================= Share Definitions ======================= - - directory mask = 0775 - create mask = 0775 - -[mounts] - comment = mount root (/mnt/) - path = /mnt/ - read only = no - public = yes - follow symlinks = yes - wide links = yes - -[mpd] - comment = MPD directory (/var/lib/mpd/) - path = /var/lib/mpd/ - read only = no - public = yes - follow symlinks = yes - wide links = yes - -[www] - comment = RuneAudio webroot (/var/www/) - path = /var/www/ - read only = no - public = yes - follow symlinks = yes - wide links = yes - -[etc] - comment = RuneAudio sys config dir (/etc) - path = /etc/ - read only = no - public = yes - follow symlinks = yes - wide links = yes +#======================= Global Settings ======================= + +[global] + + workgroup = RUNENET + server string = RuneAudio Player + dns proxy = no + log level = 0 + syslog = 0 + security = share + guest account = root + map to guest = bad user + load printers = no + domain master = no + local master = no + preferred master = no + + +#======================= Share Definitions ======================= + + directory mask = 0775 + create mask = 0775 + +[mounts] + comment = mount root (/mnt/) + path = /mnt/ + read only = no + public = yes + follow symlinks = yes + wide links = yes + +[mpd] + comment = MPD directory (/var/lib/mpd/) + path = /var/lib/mpd/ + read only = no + public = yes + follow symlinks = yes + wide links = yes + +[www] + comment = RuneAudio webroot (/var/www/) + path = /var/www/ + read only = no + public = yes + follow symlinks = yes + wide links = yes + +[etc] + comment = RuneAudio sys config dir (/etc) + path = /etc/ + read only = no + public = yes + follow symlinks = yes + wide links = yes diff --git a/app/config/_os/etc/security/limits.conf b/app/config/_os/etc/security/limits.conf index bf1f80c4..cd00862a 100644 --- a/app/config/_os/etc/security/limits.conf +++ b/app/config/_os/etc/security/limits.conf @@ -1,57 +1,57 @@ -# /etc/security/limits.conf -# -#Each line describes a limit for a user in the form: -# -# -# -#Where: -# can be: -# - a user name -# - a group name, with @group syntax -# - the wildcard *, for default entry -# - the wildcard %, can be also used with %group syntax, -# for maxlogin limit -# -# can have the two values: -# - "soft" for enforcing the soft limits -# - "hard" for enforcing hard limits -# -# can be one of the following: -# - core - limits the core file size (KB) -# - data - max data size (KB) -# - fsize - maximum filesize (KB) -# - memlock - max locked-in-memory address space (KB) -# - nofile - max number of open files -# - rss - max resident set size (KB) -# - stack - max stack size (KB) -# - cpu - max CPU time (MIN) -# - nproc - max number of processes -# - as - address space limit (KB) -# - maxlogins - max number of logins for this user -# - maxsyslogins - max number of logins on the system -# - priority - the priority to run user process with -# - locks - max number of file locks the user can hold -# - sigpending - max number of pending signals -# - msgqueue - max memory used by POSIX message queues (bytes) -# - nice - max nice priority allowed to raise to values: [-20, 19] -# - rtprio - max realtime priority -# -# -# - -#* soft core 0 -#* hard rss 10000 -#@student hard nproc 20 -#@faculty soft nproc 20 -#@faculty hard nproc 50 -#ftp hard nproc 0 -#@student - maxlogins 4 - - -* - rtprio 0 -* - nice 0 -#@audio - rtprio 65 -@audio - rtprio 99 -@audio - nice -10 -#@audio - memlock 40000 +# /etc/security/limits.conf +# +#Each line describes a limit for a user in the form: +# +# +# +#Where: +# can be: +# - a user name +# - a group name, with @group syntax +# - the wildcard *, for default entry +# - the wildcard %, can be also used with %group syntax, +# for maxlogin limit +# +# can have the two values: +# - "soft" for enforcing the soft limits +# - "hard" for enforcing hard limits +# +# can be one of the following: +# - core - limits the core file size (KB) +# - data - max data size (KB) +# - fsize - maximum filesize (KB) +# - memlock - max locked-in-memory address space (KB) +# - nofile - max number of open files +# - rss - max resident set size (KB) +# - stack - max stack size (KB) +# - cpu - max CPU time (MIN) +# - nproc - max number of processes +# - as - address space limit (KB) +# - maxlogins - max number of logins for this user +# - maxsyslogins - max number of logins on the system +# - priority - the priority to run user process with +# - locks - max number of file locks the user can hold +# - sigpending - max number of pending signals +# - msgqueue - max memory used by POSIX message queues (bytes) +# - nice - max nice priority allowed to raise to values: [-20, 19] +# - rtprio - max realtime priority +# +# +# + +#* soft core 0 +#* hard rss 10000 +#@student hard nproc 20 +#@faculty soft nproc 20 +#@faculty hard nproc 50 +#ftp hard nproc 0 +#@student - maxlogins 4 + + +* - rtprio 0 +* - nice 0 +#@audio - rtprio 65 +@audio - rtprio 99 +@audio - nice -10 +#@audio - memlock 40000 @audio - memlock unlimited \ No newline at end of file diff --git a/app/config/_os/etc/sysctl.d/20-quiet-printk.conf b/app/config/_os/etc/sysctl.d/20-quiet-printk.conf index 47146d2a..fd7f54ec 100644 --- a/app/config/_os/etc/sysctl.d/20-quiet-printk.conf +++ b/app/config/_os/etc/sysctl.d/20-quiet-printk.conf @@ -1 +1 @@ -kernel.printk = 3 3 3 3 +kernel.printk = 3 3 3 3 diff --git a/app/config/_os/etc/sysctl.d/raspberrypi.conf b/app/config/_os/etc/sysctl.d/raspberrypi.conf index b892ead2..9a628d02 100644 --- a/app/config/_os/etc/sysctl.d/raspberrypi.conf +++ b/app/config/_os/etc/sysctl.d/raspberrypi.conf @@ -1,13 +1,13 @@ -vm.min_free_kbytes=32768 -vm.vfs_cache_pressure = 300 -net.core.rmem_max=12582912 -net.core.wmem_max=12582912 -net.ipv4.tcp_rmem= 10240 87380 12582912 -net.ipv4.tcp_wmem= 10240 87380 12582912 -net.ipv4.tcp_timestamps = 0 -net.ipv4.tcp_window_scaling = 1 -net.ipv4.tcp_sack = 0 -net.ipv4.tcp_no_metrics_save = 1 -net.core.netdev_max_backlog = 5000 -#vm.overcommit_memory = 2 -#vm.overcommit_ratio = 50 +vm.min_free_kbytes=32768 +vm.vfs_cache_pressure = 300 +net.core.rmem_max=12582912 +net.core.wmem_max=12582912 +net.ipv4.tcp_rmem= 10240 87380 12582912 +net.ipv4.tcp_wmem= 10240 87380 12582912 +net.ipv4.tcp_timestamps = 0 +net.ipv4.tcp_window_scaling = 1 +net.ipv4.tcp_sack = 0 +net.ipv4.tcp_no_metrics_save = 1 +net.core.netdev_max_backlog = 5000 +#vm.overcommit_memory = 2 +#vm.overcommit_ratio = 50 diff --git a/app/config/_os/etc/systemd/coredump.conf b/app/config/_os/etc/systemd/coredump.conf index 46715dcd..eb8e88fe 100644 --- a/app/config/_os/etc/systemd/coredump.conf +++ b/app/config/_os/etc/systemd/coredump.conf @@ -1,17 +1,17 @@ -# This file is part of systemd. -# -# systemd is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# See coredump.conf(5) for details - -[Coredump] -Storage=none -#Compress=yes -#ProcessSizeMax=2G -#ExternalSizeMax=2G -#JournalSizeMax=767M -#MaxUse= -#KeepFree= +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# See coredump.conf(5) for details + +[Coredump] +Storage=none +#Compress=yes +#ProcessSizeMax=2G +#ExternalSizeMax=2G +#JournalSizeMax=767M +#MaxUse= +#KeepFree= diff --git a/app/config/_os/etc/systemd/journald.conf b/app/config/_os/etc/systemd/journald.conf index 72129ef0..01c7ed76 100644 --- a/app/config/_os/etc/systemd/journald.conf +++ b/app/config/_os/etc/systemd/journald.conf @@ -1,35 +1,35 @@ -# This file is part of systemd. -# -# systemd is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# See journald.conf(5) for details - -[Journal] -Storage=none -#Compress=yes -#Seal=yes -#SplitMode=uid -#SyncIntervalSec=5m -#RateLimitInterval=30s -#RateLimitBurst=1000 -#SystemMaxUse= -#SystemKeepFree= -#SystemMaxFileSize= -#RuntimeMaxUse= -#RuntimeKeepFree= -#RuntimeMaxFileSize= -#MaxRetentionSec= -#MaxFileSec=1month -#ForwardToSyslog=yes -#ForwardToKMsg=no -#ForwardToConsole=no -#ForwardToWall=yes -#TTYPath=/dev/console -#MaxLevelStore=debug -#MaxLevelSyslog=debug -#MaxLevelKMsg=notice -#MaxLevelConsole=info -#MaxLevelWall=emerg +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# See journald.conf(5) for details + +[Journal] +Storage=none +#Compress=yes +#Seal=yes +#SplitMode=uid +#SyncIntervalSec=5m +#RateLimitInterval=30s +#RateLimitBurst=1000 +#SystemMaxUse= +#SystemKeepFree= +#SystemMaxFileSize= +#RuntimeMaxUse= +#RuntimeKeepFree= +#RuntimeMaxFileSize= +#MaxRetentionSec= +#MaxFileSec=1month +#ForwardToSyslog=yes +#ForwardToKMsg=no +#ForwardToConsole=no +#ForwardToWall=yes +#TTYPath=/dev/console +#MaxLevelStore=debug +#MaxLevelSyslog=debug +#MaxLevelKMsg=notice +#MaxLevelConsole=info +#MaxLevelWall=emerg diff --git a/app/config/_os/etc/tmpfiles.d/shairport.conf b/app/config/_os/etc/tmpfiles.d/shairport.conf new file mode 100644 index 00000000..24a499c2 --- /dev/null +++ b/app/config/_os/etc/tmpfiles.d/shairport.conf @@ -0,0 +1 @@ +d /run/shairport 0777 root root diff --git a/app/config/_os/etc/udev/rules.d/60-schedulers.rules b/app/config/_os/etc/udev/rules.d/60-schedulers.rules index d44036cf..c965dd65 100644 --- a/app/config/_os/etc/udev/rules.d/60-schedulers.rules +++ b/app/config/_os/etc/udev/rules.d/60-schedulers.rules @@ -1,2 +1,2 @@ -# set deadline scheduler for non-rotating disks -ACTION=="add|change", KERNEL=="mmcblk[0-9]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="noop" +# set deadline scheduler for non-rotating disks +ACTION=="add|change", KERNEL=="mmcblk[0-9]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="noop" diff --git a/app/config/_os/etc/udev/rules.d/rune_usb-audio.rules b/app/config/_os/etc/udev/rules.d/rune_usb-audio.rules index fea6dc00..1661b819 100644 --- a/app/config/_os/etc/udev/rules.d/rune_usb-audio.rules +++ b/app/config/_os/etc/udev/rules.d/rune_usb-audio.rules @@ -1,2 +1,2 @@ -#KERNEL=="card*", DRIVER=="snd-usb-audio", RUN+="/var/www/command/refresh_ao" -KERNEL=="card*", SUBSYSTEM=="sound", RUN+="/var/www/command/refresh_ao" +#KERNEL=="card*", DRIVER=="snd-usb-audio", RUN+="/var/www/command/refresh_ao" +KERNEL=="card*", SUBSYSTEM=="sound", RUN+="/var/www/command/refresh_ao" diff --git a/app/config/_os/etc/udev/rules.d/rune_usb-stor.rules b/app/config/_os/etc/udev/rules.d/rune_usb-stor.rules index 076b61be..3fa7a97d 100644 --- a/app/config/_os/etc/udev/rules.d/rune_usb-stor.rules +++ b/app/config/_os/etc/udev/rules.d/rune_usb-stor.rules @@ -1,3 +1,3 @@ -#KERNEL=="sd*", SUBSYSTEM="usb", RUN+="/var/www/command/ui_notify.php 'Usb Storage' event simplemessage" -KERNEL=="sd?1", ACTION=="add", RUN+="/var/www/command/ui_notify.php 'Usb Storage' connected simplemessage" -KERNEL=="sd?1", ACTION=="remove", RUN+="/var/www/command/ui_notify.php 'Usb Storage' disconnected simplemessage" +#KERNEL=="sd*", SUBSYSTEM="usb", RUN+="/var/www/command/ui_notify.php 'Usb Storage' event simplemessage" +KERNEL=="sd?1", ACTION=="add", RUN+="/var/www/command/ui_notify.php 'Usb Storage' connected simplemessage" +KERNEL=="sd?1", ACTION=="remove", RUN+="/var/www/command/ui_notify.php 'Usb Storage' disconnected simplemessage" diff --git a/app/config/_os/etc/udevil/udevil.conf b/app/config/_os/etc/udevil/udevil.conf index 2a2010f7..880f07c1 100644 --- a/app/config/_os/etc/udevil/udevil.conf +++ b/app/config/_os/etc/udevil/udevil.conf @@ -1,331 +1,331 @@ -############################################################################## -# -# udevil configuration file /etc/udevil/udevil.conf -# -# This file controls what devices, networks, and files users may mount and -# unmount via udevil (set suid). -# -# IMPORTANT: IT IS POSSIBLE TO CREATE SERIOUS SECURITY PROBLEMS IF THIS FILE -# IS MISCONFIGURED - EDIT WITH CARE -# -# Note: For greater control for specific users, including root, copy this -# file to /etc/udevil/udevil-user-USERNAME.conf replacing USERNAME with the -# desired username (eg /etc/udevil/udevil-user-jim.conf). -# -# Format: -# OPTION = VALUE[, VALUE, ...] -# -# DO NOT USE QUOTES except literally -# Lines beginning with # are ignored -# -############################################################################## - - -# To log all uses of udevil, set log_file to a file path: -# log_file = /var/log/udevil.log - -# Approximate number of days to retain log entries (0=forever, max=60): -log_keep_days = 1 - - -# allowed_types determines what fstypes can be passed by a user to the u/mount -# program, what device filesystems may be un/mounted implicitly, and what -# network filesystems may be un/mounted. -# It may also include the 'file' keyword, indicating that the user is allowed -# to mount files (eg an ISO file). The $KNOWN_FILESYSTEMS variable may -# be included to include common local filesystems as well as those listed in -# /etc/filesystems and /proc/filesystems. -# allowed_types_USERNAME, if present, is used to override allowed_types for -# the specific user 'USERNAME'. For example, to allow user 'jim' to mount -# only vfat filesystems, add: -# allowed_types_jim = vfat -# Setting allowed_types = * does NOT allow all types, as this is a security -# risk, but does allow all recognized types. -# allowed_types = $KNOWN_FILESYSTEMS, file, cifs, smbfs, nfs, curlftpfs, ftpfs, sshfs, davfs, tmpfs, ramfs -allowed_types = $KNOWN_FILESYSTEMS, file, hfsplus - - -# allowed_users is a list of users permitted to mount and unmount with udevil. -# Wildcards (* or ?) may be used in the usernames. To allow all users, -# specify "allowed_users=*". UIDs may be included using the form UID=1000. -# For example: allowed_users = carl, UID=1000, pre* -# Also note that permission to execute udevil may be limited to users belonging -# to the group that owns /usr/bin/udevil, such as 'plugdev' or 'storage', -# depending on installation. -# allowed_users_FSTYPE, if present, is used to override allowed_users when -# mounting or unmounting a specific fstype (eg nfs, ext3, file). -# Note that when mounting a file, fstype will always be 'file' regardless of -# the internal fstype of the file. -# For example, to allow only user 'bob' to mount nfs shares, add: -# allowed_users_nfs = bob -# The root user is NOT automatically allowed to use udevil in some cases unless -# listed here (except for unmounting anything or mounting fstab devices). -allowed_users = * - - -# allowed_groups is a list of groups permitted to mount and unmount with -# udevil. The user MUST belong to at least one of these groups. Wildcards -# or GIDs may NOT be used in group names, but a single * may be used to allow -# all groups. -# Also note that permission to execute udevil may be limited to users belonging -# to the group that owns /usr/bin/udevil, such as 'plugdev' or 'storage', -# depending on installation. -# allowed_groups_FSTYPE, if present, is used to override allowed_groups when -# mounting or unmounting a specific fstype (eg nfs, ext3, file). For example, -# to allow only members of the 'network' group to mount smb and nfs shares, -# use both of these lines: -# allowed_groups_smbfs = network -# allowed_groups_nfs = network -# The root user is NOT automatically allowed to use udevil in some cases unless -# listed here (except for unmounting anything or mounting fstab devices). -allowed_groups = * - - -# allowed_media_dirs specifies the media directories in which user mount points -# may be located. The first directory which exists and does not contain a -# wildcard will be used as the default media directory (normally /media or -# /run/media/$USER). -# The $USER variable, if included, will be replaced with the username of the -# user running udevil. Wildcards may also be used in any directory EXCEPT the -# default. Wildcards will not match a / -# allowed_media_dirs_FSTYPE, if present, is used to override allowed_media_dirs -# when mounting or unmounting a specific fstype (eg ext2, nfs). For example, -# to cause /media/network to be used as the default media directory for -# nfs and ftpfs mounts, use these two lines: -# allowed_media_dirs_nfs = /media/network, /media, /run/media/$USER -# allowed_media_dirs_ftpfs = /media/network, /media, /run/media/$USER -# NOTE: If you want only the user who mounted a device to have access to it -# and be allowed to unmount it, specify /run/media/$USER as the first -# allowed media directory. -# IMPORTANT: If an allowed file is mounted to a media directory, the user may -# be permitted to unmount its associated loop device even though internal. -# INCLUDING /MNT HERE IS NOT RECOMMENDED. ALL ALLOWED MEDIA DIRECTORIES -# SHOULD BE OWNED AND WRITABLE ONLY BY ROOT. -allowed_media_dirs = /mnt/MPD/USB - - -# allowed_devices is the first criteria for what block devices users may mount -# or unmount. If a device is not listed in allowed_devices, it cannot be -# un/mounted (unless in fstab). However, even if a device is listed, other -# factors may prevent its use. For example, access to system internal devices -# will be denied to normal users even if they are included in allowed_devices. -# allowed_devices_FSTYPE, if present, is used to override allowed_devices when -# mounting or unmounting a specific fstype (eg ext3, ntfs). For example, to -# prevent all block devices containing an ext4 filesystem from being -# un/mounted use: -# allowed_devices_ext4 = -# Note: Wildcards may be used, but a wildcard will never match a /, except -# for "allowed_devices=*" which allows any device. The recommended setting is -# allowed_devices = /dev/* -# WARNING: ALLOWING USERS TO MOUNT DEVICES OUTSIDE OF /dev CAN CAUSE SERIOUS -# SECURITY PROBLEMS. DO NOT ALLOW DEVICES IN /dev/shm -allowed_devices = /dev/* - - -# allowed_internal_devices causes udevil to treat any listed block devices as -# removable, thus allowing normal users to un/mount them (providing they are -# also listed in allowed_devices). -# allowed_internal_devices_FSTYPE, if present, is used to override -# allowed_internal_devices when mounting or unmounting a specific fstype -# (eg ext3, ntfs). For example, to allow block devices containing a vfat -# filesystem to be un/mounted even if they are system internal devices, use: -# allowed_internal_devices_vfat = /dev/sdb* -# Some removable esata drives look like internal drives to udevil. To avoid -# this problem, they can be treated as removable with this setting. -# WARNING: SETTING A SYSTEM DEVICE HERE CAN CAUSE SERIOUS SECURITY PROBLEMS. -# allowed_internal_devices = - - -# allowed_internal_uuids and allowed_internal_uuids_FSTYPE work similarly to -# allowed_internal_devices, except that UUIDs are specified instead of devices. -# For example, to allow un/mounting of an internal filesystem based on UUID: -# allowed_internal_uuids = cc0c4489-8def-1e5b-a304-ab87c3cb626c0 -# WARNING: SETTING A SYSTEM DEVICE HERE CAN CAUSE SERIOUS SECURITY PROBLEMS. -# allowed_internal_uuids = - - -# forbidden_devices is used to prevent block devices from being un/mounted -# even if other settings would allow them (except devices in fstab). -# forbidden_devices_FSTYPE, if present, is used to override -# forbidden_devices when mounting or unmounting a specific fstype -# (eg ext3, ntfs). For example, to prevent device /dev/sdd1 from being -# mounted when it contains an ntfs filesystem, use: -# forbidden_devices_ntfs = /dev/sdd1 -# NOTE: device node paths are canonicalized before being tested, so forbidding -# a link to a device will have no effect. -forbidden_devices = - -# allowed_networks determines what hosts may be un/mounted by udevil users when -# using nfs, cifs, smbfs, curlftpfs, ftpfs, or sshfs. Hosts may be specified -# using a hostname (eg myserver.com) or IP address (192.168.1.100). -# Wildcards may be used in hostnames and IP addresses, but CIDR notation -# (192.168.1.0/16) is NOT supported. IP v6 is supported. For example: -# allowed_networks = 127.0.0.1, 192.168.1.*, 10.0.0.*, localmachine, *.okay.com -# Or, to prevent un/mounting of any network shares, set: -# allowed_networks = -# allowed_networks_FSTYPE, if present, is used to override allowed_networks -# when mounting or unmounting a specific network fstype (eg nfs, cifs, sshfs, -# curlftpfs). For example, to limit nfs and samba shares to only local -# networks, use these two lines: -# allowed_networks_nfs = 192.168.1.*, 10.0.0.* -# allowed_networks_cifs = 192.168.1.*, 10.0.0.* -#allowed_networks = * - - -# forbidden_networks and forbidden_networks_FSTYPE are used to specify networks -# that are never allowed, even if other settings allow them (except fstab). -# NO REVERSE LOOKUP IS PERFORMED, so including bad.com will only have an effect -# if the user uses that hostname. IP lookup is always performed, so forbidding -# an IP address will also forbid all corresponding hostnames. -forbidden_networks = * - - -# allowed_files is used to determine what files in what directories may be -# un/mounted. A user must also have read permission on a file to mount it. -# Note: Wildcards may be used, but a wildcard will never match a /, except -# for "allowed_files=*" which allows any file. For example, to allow only -# files in the /share directory to be mounted, use: -# allowed_files = /share/* -# NOTE: Specifying allowed_files_FSTYPE will NOT work because the fstype of -# files is always 'file'. -allowed_files = * - - -# forbidden_files is used to specify files that are never allowed, even if -# other settings allow them (except fstab). Specify a full path. -# Note: Wildcards may be used, but a wildcard will never match a /, except -# for "forbidden_files = *". -# NOTE: file paths are canonicalized before being tested, so forbidding -# a link to a file will have no effect. -forbidden_files = - - -# default_options specifies what options are always included when performing -# a mount, in addition to any options the user may specify. -# Note: When a device is present in /etc/fstab, and the user does not specify -# a mount point, the device is mounted with normal user permissions using -# the fstab entry, without these options. -# default_options_FSTYPE, if present, is used to override default_options -# when mounting a specific fstype (eg ext2, nfs). -# The variables $USER, $UID, and $GID are changed to the user's username, UID, -# and GID. -# FOR GOOD SECURITY, default_options SHOULD ALWAYS INCLUDE: nosuid,noexec,nodev -# WARNING: OPTIONS PRESENT OR MISSING CAN CAUSE SERIOUS SECURITY PROBLEMS. -default_options = nosuid, noexec, nodev, noatime -default_options_file = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID, ro -# mount iso9660 with 'ro' to prevent mount read-only warning -default_options_iso9660 = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID, ro, utf8 -default_options_udf = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID -default_options_vfat = ro, iocharset=utf8, noatime, uid=$UID, gid=$GID -default_options_exfat = ro, nonempty, uid=$UID, gid=$GID -default_options_hfsplus = ro, uid=$UID, gid=$GID -default_options_msdos = nosuid, noexec, nodev, noatime, fmask=0133, dmask=0022, uid=$UID, gid=$GID -default_options_umsdos = nosuid, noexec, nodev, noatime, fmask=0133, dmask=0022, uid=$UID, gid=$GID -default_options_ntfs = nosuid, noexec, nodev, noatime, fmask=0133, uid=$UID, gid=$GID, utf8 -default_options_cifs = nosuid, noexec, nodev, uid=$UID, gid=$GID -default_options_smbfs = nosuid, noexec, nodev, uid=$UID, gid=$GID -default_options_sshfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID, nonempty, allow_other -default_options_curlftpfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID, nonempty, allow_other -default_options_ftpfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID -default_options_davfs = nosuid, noexec, nodev, uid=$UID, gid=$GID -default_options_tmpfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID -default_options_ramfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID - - -# allowed_options determines all options that a user may specify when mounting. -# All the options used in default_options above must be included here too, or -# they will be rejected. If the user attempts to use an option not included -# here, an error will result. Wildcards may be used. -# allowed_options_FSTYPE, if present, is used to override allowed_options -# when mounting a specific fstype (eg ext2, nfs). -# The variables $USER, $UID, and $GID are changed to the user's username, UID, -# and GID. -# If you want to forbid remounts, remove 'remount' from here. -# WARNING: OPTIONS HERE CAN CAUSE SERIOUS SECURITY PROBLEMS - CHOOSE CAREFULLY -allowed_options = nosuid, noexec, nodev, noatime, fmask=0133, dmask=0022, uid=$UID, gid=$GID, ro, rw, sync, flush, iocharset=*, utf8, remount, nonempty -allowed_options_nfs = nosuid, noexec, nodev, noatime, ro, rw, sync, remount, port=*, rsize=*, wsize=*, hard, proto=*, timeo=*, retrans=* -allowed_options_cifs = nosuid, noexec, nodev, ro, rw, remount, port=*, user=*, username=*, pass=*, password=*, guest, domain=*, uid=$UID, gid=$GID, credentials=* -allowed_options_smbfs = nosuid, noexec, nodev, ro, rw, remount, port=*, user=*, username=*, pass=*, password=*, guest, domain=*, uid=$UID, gid=$GID, credentials=* -allowed_options_sshfs = nosuid, noexec, nodev, noatime, ro, rw, uid=$UID, gid=$GID, nonempty, allow_other, idmap=user, BatchMode=yes, port=* -allowed_options_curlftpfs = nosuid, noexec, nodev, noatime, ro, rw, uid=$UID, gid=$GID, nonempty, allow_other, user=* -allowed_options_ftpfs = nosuid, noexec, nodev, noatime, ro, rw, port=*, user=*, pass=*, ip=*, root=*, uid=$UID, gid=$GID - - -# mount_point_mode, if present and set to a non-empty value, will cause udevil -# to set the mode (permissions) on the moint point after mounting If not -# specified or if left empty, the mode is not changed. Mode must be octal -# starting with a zero (0755). -# mount_point_mode_FSTYPE, if present, is used to override mount_point_mode -# when mounting a specific fstype (eg ext2, nfs). -# NOT SETTING A MODE CAN HAVE SECURITY IMPLICATIONS FOR SOME FSTYPES -mount_point_mode = 0777 -# don't set a mode for some types: -mount_point_mode_sshfs = -mount_point_mode_curlftpfs = -mount_point_mode_ftpfs = - - -# Use the settings below to change the default locations of programs used by -# udevil, or (advanced topic) to redirect commands to your scripts. -# When substituting scripts, make sure they are root-owned and accept the -# options used by udevil (for example, the mount_program must accept --fake, -# -o, -v, and other options valid to mount.) -# Be sure to specify the full path and include NO OPTIONS or other arguments. -# These programs may also be specified as configure options when building -# udevil. -# THESE PROGRAMS ARE RUN AS ROOT -# mount_program = /bin/mount -# umount_program = /bin/umount -# losetup_program = /sbin/losetup -# setfacl_program = /usr/bin/setfacl - - -# validate_exec specifies a program or script which provides additional -# validation of a mount or unmount command, beyond the checks performed by -# udevil. The program is run as a normal user (if root runs udevil, -# validate_exec will NOT be run). The program is NOT run if the user is -# mounting a device without root priviledges (a device in fstab). -# The program is passed the username, a printable description of what is -# happening, and the entire udevil command line as the first three arguments. -# The program must return an exit status of 0 to allow the mount or unmount -# to proceed. If it returns non-zero, the user will be denied permission. -# For example, validate_exec might specify a script which notifies you -# of the command being run, or performs additional steps to authenticate the -# user. -# Specify a full path to the program, with NO options or arguments. -# validate_exec = - - -# validate_rootexec works similarly to validate_exec, except that the program -# is run as root. validate_rootexec will also be run if the root user runs -# udevil. If both validate_exec and validate_rootexec are specified, -# validate_rootexec will run first, followed by validate_exec. -# The program must return an exit status of 0 to allow the mount or unmount -# to proceed. If it returns non-zero, the user will be denied permission. -# Unless you are familiar with writing root scripts, it is recommended that -# rootexec settings NOT be used, as it is easy to inadvertently open exploits. -# THIS PROGRAM IS ALWAYS RUN AS ROOT, even if the user running udevil is not. -# validate_rootexec = - - -# success_exec is run after a successful mount, remount, or unmount. The -# program is run as a normal user (if root runs udevil, success_exec -# will NOT be run). -# The program is passed the username, a printable description of what action -# was taken, and the entire udevil command line as the first three arguments. -# The program's exit status is ignored. -# For example, success_exec might run a script which informs you of what action -# was taken, and might perform further actions. -# Specify a full path to the program, with NO options or arguments. -success_exec = - - -# success_rootexec works similarly to success_exec, except that the program is -# run as root. success_rootexec will also be run if the root user runs udevil. -# If both success_exec and success_rootexec are specified, success_rootexec -# will run first, followed by success_exec. -# Unless you are familiar with writing root scripts, it is recommended that -# rootexec settings NOT be used, as it is easy to inadvertently open exploits. -# THIS PROGRAM IS ALWAYS RUN AS ROOT, even if the user running udevil is not. -success_rootexec = /var/www/command/usbmount - +############################################################################## +# +# udevil configuration file /etc/udevil/udevil.conf +# +# This file controls what devices, networks, and files users may mount and +# unmount via udevil (set suid). +# +# IMPORTANT: IT IS POSSIBLE TO CREATE SERIOUS SECURITY PROBLEMS IF THIS FILE +# IS MISCONFIGURED - EDIT WITH CARE +# +# Note: For greater control for specific users, including root, copy this +# file to /etc/udevil/udevil-user-USERNAME.conf replacing USERNAME with the +# desired username (eg /etc/udevil/udevil-user-jim.conf). +# +# Format: +# OPTION = VALUE[, VALUE, ...] +# +# DO NOT USE QUOTES except literally +# Lines beginning with # are ignored +# +############################################################################## + + +# To log all uses of udevil, set log_file to a file path: +# log_file = /var/log/udevil.log + +# Approximate number of days to retain log entries (0=forever, max=60): +log_keep_days = 1 + + +# allowed_types determines what fstypes can be passed by a user to the u/mount +# program, what device filesystems may be un/mounted implicitly, and what +# network filesystems may be un/mounted. +# It may also include the 'file' keyword, indicating that the user is allowed +# to mount files (eg an ISO file). The $KNOWN_FILESYSTEMS variable may +# be included to include common local filesystems as well as those listed in +# /etc/filesystems and /proc/filesystems. +# allowed_types_USERNAME, if present, is used to override allowed_types for +# the specific user 'USERNAME'. For example, to allow user 'jim' to mount +# only vfat filesystems, add: +# allowed_types_jim = vfat +# Setting allowed_types = * does NOT allow all types, as this is a security +# risk, but does allow all recognized types. +# allowed_types = $KNOWN_FILESYSTEMS, file, cifs, smbfs, nfs, curlftpfs, ftpfs, sshfs, davfs, tmpfs, ramfs +allowed_types = $KNOWN_FILESYSTEMS, file, hfsplus + + +# allowed_users is a list of users permitted to mount and unmount with udevil. +# Wildcards (* or ?) may be used in the usernames. To allow all users, +# specify "allowed_users=*". UIDs may be included using the form UID=1000. +# For example: allowed_users = carl, UID=1000, pre* +# Also note that permission to execute udevil may be limited to users belonging +# to the group that owns /usr/bin/udevil, such as 'plugdev' or 'storage', +# depending on installation. +# allowed_users_FSTYPE, if present, is used to override allowed_users when +# mounting or unmounting a specific fstype (eg nfs, ext3, file). +# Note that when mounting a file, fstype will always be 'file' regardless of +# the internal fstype of the file. +# For example, to allow only user 'bob' to mount nfs shares, add: +# allowed_users_nfs = bob +# The root user is NOT automatically allowed to use udevil in some cases unless +# listed here (except for unmounting anything or mounting fstab devices). +allowed_users = * + + +# allowed_groups is a list of groups permitted to mount and unmount with +# udevil. The user MUST belong to at least one of these groups. Wildcards +# or GIDs may NOT be used in group names, but a single * may be used to allow +# all groups. +# Also note that permission to execute udevil may be limited to users belonging +# to the group that owns /usr/bin/udevil, such as 'plugdev' or 'storage', +# depending on installation. +# allowed_groups_FSTYPE, if present, is used to override allowed_groups when +# mounting or unmounting a specific fstype (eg nfs, ext3, file). For example, +# to allow only members of the 'network' group to mount smb and nfs shares, +# use both of these lines: +# allowed_groups_smbfs = network +# allowed_groups_nfs = network +# The root user is NOT automatically allowed to use udevil in some cases unless +# listed here (except for unmounting anything or mounting fstab devices). +allowed_groups = * + + +# allowed_media_dirs specifies the media directories in which user mount points +# may be located. The first directory which exists and does not contain a +# wildcard will be used as the default media directory (normally /media or +# /run/media/$USER). +# The $USER variable, if included, will be replaced with the username of the +# user running udevil. Wildcards may also be used in any directory EXCEPT the +# default. Wildcards will not match a / +# allowed_media_dirs_FSTYPE, if present, is used to override allowed_media_dirs +# when mounting or unmounting a specific fstype (eg ext2, nfs). For example, +# to cause /media/network to be used as the default media directory for +# nfs and ftpfs mounts, use these two lines: +# allowed_media_dirs_nfs = /media/network, /media, /run/media/$USER +# allowed_media_dirs_ftpfs = /media/network, /media, /run/media/$USER +# NOTE: If you want only the user who mounted a device to have access to it +# and be allowed to unmount it, specify /run/media/$USER as the first +# allowed media directory. +# IMPORTANT: If an allowed file is mounted to a media directory, the user may +# be permitted to unmount its associated loop device even though internal. +# INCLUDING /MNT HERE IS NOT RECOMMENDED. ALL ALLOWED MEDIA DIRECTORIES +# SHOULD BE OWNED AND WRITABLE ONLY BY ROOT. +allowed_media_dirs = /mnt/MPD/USB + + +# allowed_devices is the first criteria for what block devices users may mount +# or unmount. If a device is not listed in allowed_devices, it cannot be +# un/mounted (unless in fstab). However, even if a device is listed, other +# factors may prevent its use. For example, access to system internal devices +# will be denied to normal users even if they are included in allowed_devices. +# allowed_devices_FSTYPE, if present, is used to override allowed_devices when +# mounting or unmounting a specific fstype (eg ext3, ntfs). For example, to +# prevent all block devices containing an ext4 filesystem from being +# un/mounted use: +# allowed_devices_ext4 = +# Note: Wildcards may be used, but a wildcard will never match a /, except +# for "allowed_devices=*" which allows any device. The recommended setting is +# allowed_devices = /dev/* +# WARNING: ALLOWING USERS TO MOUNT DEVICES OUTSIDE OF /dev CAN CAUSE SERIOUS +# SECURITY PROBLEMS. DO NOT ALLOW DEVICES IN /dev/shm +allowed_devices = /dev/* + + +# allowed_internal_devices causes udevil to treat any listed block devices as +# removable, thus allowing normal users to un/mount them (providing they are +# also listed in allowed_devices). +# allowed_internal_devices_FSTYPE, if present, is used to override +# allowed_internal_devices when mounting or unmounting a specific fstype +# (eg ext3, ntfs). For example, to allow block devices containing a vfat +# filesystem to be un/mounted even if they are system internal devices, use: +# allowed_internal_devices_vfat = /dev/sdb* +# Some removable esata drives look like internal drives to udevil. To avoid +# this problem, they can be treated as removable with this setting. +# WARNING: SETTING A SYSTEM DEVICE HERE CAN CAUSE SERIOUS SECURITY PROBLEMS. +# allowed_internal_devices = + + +# allowed_internal_uuids and allowed_internal_uuids_FSTYPE work similarly to +# allowed_internal_devices, except that UUIDs are specified instead of devices. +# For example, to allow un/mounting of an internal filesystem based on UUID: +# allowed_internal_uuids = cc0c4489-8def-1e5b-a304-ab87c3cb626c0 +# WARNING: SETTING A SYSTEM DEVICE HERE CAN CAUSE SERIOUS SECURITY PROBLEMS. +# allowed_internal_uuids = + + +# forbidden_devices is used to prevent block devices from being un/mounted +# even if other settings would allow them (except devices in fstab). +# forbidden_devices_FSTYPE, if present, is used to override +# forbidden_devices when mounting or unmounting a specific fstype +# (eg ext3, ntfs). For example, to prevent device /dev/sdd1 from being +# mounted when it contains an ntfs filesystem, use: +# forbidden_devices_ntfs = /dev/sdd1 +# NOTE: device node paths are canonicalized before being tested, so forbidding +# a link to a device will have no effect. +forbidden_devices = + +# allowed_networks determines what hosts may be un/mounted by udevil users when +# using nfs, cifs, smbfs, curlftpfs, ftpfs, or sshfs. Hosts may be specified +# using a hostname (eg myserver.com) or IP address (192.168.1.100). +# Wildcards may be used in hostnames and IP addresses, but CIDR notation +# (192.168.1.0/16) is NOT supported. IP v6 is supported. For example: +# allowed_networks = 127.0.0.1, 192.168.1.*, 10.0.0.*, localmachine, *.okay.com +# Or, to prevent un/mounting of any network shares, set: +# allowed_networks = +# allowed_networks_FSTYPE, if present, is used to override allowed_networks +# when mounting or unmounting a specific network fstype (eg nfs, cifs, sshfs, +# curlftpfs). For example, to limit nfs and samba shares to only local +# networks, use these two lines: +# allowed_networks_nfs = 192.168.1.*, 10.0.0.* +# allowed_networks_cifs = 192.168.1.*, 10.0.0.* +#allowed_networks = * + + +# forbidden_networks and forbidden_networks_FSTYPE are used to specify networks +# that are never allowed, even if other settings allow them (except fstab). +# NO REVERSE LOOKUP IS PERFORMED, so including bad.com will only have an effect +# if the user uses that hostname. IP lookup is always performed, so forbidding +# an IP address will also forbid all corresponding hostnames. +forbidden_networks = * + + +# allowed_files is used to determine what files in what directories may be +# un/mounted. A user must also have read permission on a file to mount it. +# Note: Wildcards may be used, but a wildcard will never match a /, except +# for "allowed_files=*" which allows any file. For example, to allow only +# files in the /share directory to be mounted, use: +# allowed_files = /share/* +# NOTE: Specifying allowed_files_FSTYPE will NOT work because the fstype of +# files is always 'file'. +allowed_files = * + + +# forbidden_files is used to specify files that are never allowed, even if +# other settings allow them (except fstab). Specify a full path. +# Note: Wildcards may be used, but a wildcard will never match a /, except +# for "forbidden_files = *". +# NOTE: file paths are canonicalized before being tested, so forbidding +# a link to a file will have no effect. +forbidden_files = + + +# default_options specifies what options are always included when performing +# a mount, in addition to any options the user may specify. +# Note: When a device is present in /etc/fstab, and the user does not specify +# a mount point, the device is mounted with normal user permissions using +# the fstab entry, without these options. +# default_options_FSTYPE, if present, is used to override default_options +# when mounting a specific fstype (eg ext2, nfs). +# The variables $USER, $UID, and $GID are changed to the user's username, UID, +# and GID. +# FOR GOOD SECURITY, default_options SHOULD ALWAYS INCLUDE: nosuid,noexec,nodev +# WARNING: OPTIONS PRESENT OR MISSING CAN CAUSE SERIOUS SECURITY PROBLEMS. +default_options = nosuid, noexec, nodev, noatime +default_options_file = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID, ro +# mount iso9660 with 'ro' to prevent mount read-only warning +default_options_iso9660 = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID, ro, utf8 +default_options_udf = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID +default_options_vfat = ro, iocharset=utf8, noatime, uid=$UID, gid=$GID +default_options_exfat = ro, nonempty, uid=$UID, gid=$GID +default_options_hfsplus = ro, uid=$UID, gid=$GID +default_options_msdos = nosuid, noexec, nodev, noatime, fmask=0133, dmask=0022, uid=$UID, gid=$GID +default_options_umsdos = nosuid, noexec, nodev, noatime, fmask=0133, dmask=0022, uid=$UID, gid=$GID +default_options_ntfs = nosuid, noexec, nodev, noatime, fmask=0133, uid=$UID, gid=$GID, utf8 +default_options_cifs = nosuid, noexec, nodev, uid=$UID, gid=$GID +default_options_smbfs = nosuid, noexec, nodev, uid=$UID, gid=$GID +default_options_sshfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID, nonempty, allow_other +default_options_curlftpfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID, nonempty, allow_other +default_options_ftpfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID +default_options_davfs = nosuid, noexec, nodev, uid=$UID, gid=$GID +default_options_tmpfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID +default_options_ramfs = nosuid, noexec, nodev, noatime, uid=$UID, gid=$GID + + +# allowed_options determines all options that a user may specify when mounting. +# All the options used in default_options above must be included here too, or +# they will be rejected. If the user attempts to use an option not included +# here, an error will result. Wildcards may be used. +# allowed_options_FSTYPE, if present, is used to override allowed_options +# when mounting a specific fstype (eg ext2, nfs). +# The variables $USER, $UID, and $GID are changed to the user's username, UID, +# and GID. +# If you want to forbid remounts, remove 'remount' from here. +# WARNING: OPTIONS HERE CAN CAUSE SERIOUS SECURITY PROBLEMS - CHOOSE CAREFULLY +allowed_options = nosuid, noexec, nodev, noatime, fmask=0133, dmask=0022, uid=$UID, gid=$GID, ro, rw, sync, flush, iocharset=*, utf8, remount, nonempty +allowed_options_nfs = nosuid, noexec, nodev, noatime, ro, rw, sync, remount, port=*, rsize=*, wsize=*, hard, proto=*, timeo=*, retrans=* +allowed_options_cifs = nosuid, noexec, nodev, ro, rw, remount, port=*, user=*, username=*, pass=*, password=*, guest, domain=*, uid=$UID, gid=$GID, credentials=* +allowed_options_smbfs = nosuid, noexec, nodev, ro, rw, remount, port=*, user=*, username=*, pass=*, password=*, guest, domain=*, uid=$UID, gid=$GID, credentials=* +allowed_options_sshfs = nosuid, noexec, nodev, noatime, ro, rw, uid=$UID, gid=$GID, nonempty, allow_other, idmap=user, BatchMode=yes, port=* +allowed_options_curlftpfs = nosuid, noexec, nodev, noatime, ro, rw, uid=$UID, gid=$GID, nonempty, allow_other, user=* +allowed_options_ftpfs = nosuid, noexec, nodev, noatime, ro, rw, port=*, user=*, pass=*, ip=*, root=*, uid=$UID, gid=$GID + + +# mount_point_mode, if present and set to a non-empty value, will cause udevil +# to set the mode (permissions) on the moint point after mounting If not +# specified or if left empty, the mode is not changed. Mode must be octal +# starting with a zero (0755). +# mount_point_mode_FSTYPE, if present, is used to override mount_point_mode +# when mounting a specific fstype (eg ext2, nfs). +# NOT SETTING A MODE CAN HAVE SECURITY IMPLICATIONS FOR SOME FSTYPES +mount_point_mode = 0777 +# don't set a mode for some types: +mount_point_mode_sshfs = +mount_point_mode_curlftpfs = +mount_point_mode_ftpfs = + + +# Use the settings below to change the default locations of programs used by +# udevil, or (advanced topic) to redirect commands to your scripts. +# When substituting scripts, make sure they are root-owned and accept the +# options used by udevil (for example, the mount_program must accept --fake, +# -o, -v, and other options valid to mount.) +# Be sure to specify the full path and include NO OPTIONS or other arguments. +# These programs may also be specified as configure options when building +# udevil. +# THESE PROGRAMS ARE RUN AS ROOT +# mount_program = /bin/mount +# umount_program = /bin/umount +# losetup_program = /sbin/losetup +# setfacl_program = /usr/bin/setfacl + + +# validate_exec specifies a program or script which provides additional +# validation of a mount or unmount command, beyond the checks performed by +# udevil. The program is run as a normal user (if root runs udevil, +# validate_exec will NOT be run). The program is NOT run if the user is +# mounting a device without root priviledges (a device in fstab). +# The program is passed the username, a printable description of what is +# happening, and the entire udevil command line as the first three arguments. +# The program must return an exit status of 0 to allow the mount or unmount +# to proceed. If it returns non-zero, the user will be denied permission. +# For example, validate_exec might specify a script which notifies you +# of the command being run, or performs additional steps to authenticate the +# user. +# Specify a full path to the program, with NO options or arguments. +# validate_exec = + + +# validate_rootexec works similarly to validate_exec, except that the program +# is run as root. validate_rootexec will also be run if the root user runs +# udevil. If both validate_exec and validate_rootexec are specified, +# validate_rootexec will run first, followed by validate_exec. +# The program must return an exit status of 0 to allow the mount or unmount +# to proceed. If it returns non-zero, the user will be denied permission. +# Unless you are familiar with writing root scripts, it is recommended that +# rootexec settings NOT be used, as it is easy to inadvertently open exploits. +# THIS PROGRAM IS ALWAYS RUN AS ROOT, even if the user running udevil is not. +# validate_rootexec = + + +# success_exec is run after a successful mount, remount, or unmount. The +# program is run as a normal user (if root runs udevil, success_exec +# will NOT be run). +# The program is passed the username, a printable description of what action +# was taken, and the entire udevil command line as the first three arguments. +# The program's exit status is ignored. +# For example, success_exec might run a script which informs you of what action +# was taken, and might perform further actions. +# Specify a full path to the program, with NO options or arguments. +success_exec = + + +# success_rootexec works similarly to success_exec, except that the program is +# run as root. success_rootexec will also be run if the root user runs udevil. +# If both success_exec and success_rootexec are specified, success_rootexec +# will run first, followed by success_exec. +# Unless you are familiar with writing root scripts, it is recommended that +# rootexec settings NOT be used, as it is easy to inadvertently open exploits. +# THIS PROGRAM IS ALWAYS RUN AS ROOT, even if the user running udevil is not. +success_rootexec = /var/www/command/usbmount + diff --git a/app/config/_os/etc/wpa_supplicant/wpa_supplicant.conf b/app/config/_os/etc/wpa_supplicant/wpa_supplicant.conf index d5d66e7e..7900a500 100644 --- a/app/config/_os/etc/wpa_supplicant/wpa_supplicant.conf +++ b/app/config/_os/etc/wpa_supplicant/wpa_supplicant.conf @@ -1,2 +1,5 @@ -ctrl_interface=/var/run/wpa_supplicant -update_config=1 +ctrl_interface=/var/run/wpa_supplicant +#update_config=1 +eapol_version=1 +ap_scan=1 +fast_reauth=1 diff --git a/app/config/_os/usr/lib/systemd/system/mpd.service b/app/config/_os/usr/lib/systemd/system/mpd.service new file mode 100644 index 00000000..3b9b0acb --- /dev/null +++ b/app/config/_os/usr/lib/systemd/system/mpd.service @@ -0,0 +1,10 @@ +[Unit] +Description=Music Player Daemon +After=network.target sound.target + +[Service] +ExecStart=/usr/bin/mpd --no-daemon +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/mpdscribble.service b/app/config/_os/usr/lib/systemd/system/mpdscribble.service index 37427677..c201024a 100644 --- a/app/config/_os/usr/lib/systemd/system/mpdscribble.service +++ b/app/config/_os/usr/lib/systemd/system/mpdscribble.service @@ -1,12 +1,12 @@ -[Unit] -Description=MPD Scribble -After=sound.target -Requires=mpd.service -After=mpd.service - -[Service] -ExecStart=/usr/local/bin/mpdscribble -D -Restart=always - -[Install] -WantedBy=multi-user.target +[Unit] +Description=MPD Scribble +After=sound.target +Requires=mpd.service +After=mpd.service + +[Service] +ExecStart=/usr/local/bin/mpdscribble -D +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/netctl-ifplugd@.service b/app/config/_os/usr/lib/systemd/system/netctl-ifplugd@.service new file mode 100644 index 00000000..508f4eee --- /dev/null +++ b/app/config/_os/usr/lib/systemd/system/netctl-ifplugd@.service @@ -0,0 +1,12 @@ +[Unit] +Description=Automatic wired network connection using netctl profiles +Documentation=man:netctl.special(7) +BindsTo=sys-subsystem-net-devices-%i.device +After=sys-subsystem-net-devices-%i.device + +[Service] +ExecStart=/usr/bin/ifplugd -i %I -r /etc/ifplugd/netctl.action -bfIns +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/ntpd.service b/app/config/_os/usr/lib/systemd/system/ntpd.service new file mode 100644 index 00000000..82186f6f --- /dev/null +++ b/app/config/_os/usr/lib/systemd/system/ntpd.service @@ -0,0 +1,16 @@ +[Unit] +Description=Network Time Service +After=network.target nss-lookup.target +Conflicts=systemd-timesyncd.service + +[Service] +Type=forking +PrivateTmp=true +ExecStart=/usr/bin/ntpd -g -u ntp:ntp +Restart=always +RestartSec=1 +StartLimitInterval=30 +StartLimitBurst=20 + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/php-fpm.service b/app/config/_os/usr/lib/systemd/system/php-fpm.service new file mode 100644 index 00000000..2245a350 --- /dev/null +++ b/app/config/_os/usr/lib/systemd/system/php-fpm.service @@ -0,0 +1,17 @@ +[Unit] +Description=The PHP FastCGI Process Manager +After=syslog.target network.target + +[Service] +Type=notify +PIDFile=/run/php-fpm/php-fpm.pid +PrivateTmp=true +ExecStart=/usr/bin/php-fpm --nodaemonize --pid /run/php-fpm/php-fpm.pid +ExecReload=/bin/kill -USR2 $MAINPID +Restart=always +RestartSec=1 +StartLimitInterval=30 +StartLimitBurst=20 + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/rc-local.service b/app/config/_os/usr/lib/systemd/system/rc-local.service index 158569ae..e25d0896 100644 --- a/app/config/_os/usr/lib/systemd/system/rc-local.service +++ b/app/config/_os/usr/lib/systemd/system/rc-local.service @@ -1,12 +1,12 @@ -[Unit] -Description=/etc/rc.local -Requires=dhcpcd@eth0.service -After=dhcpcd@eth0.service - -[Service] -Type=forking -ExecStart=/etc/rc.local -TimeoutSec=0 - -[Install] -WantedBy=multi-user.target +[Unit] +Description=/etc/rc.local +Requires=dhcpcd@eth0.service +After=dhcpcd@eth0.service + +[Service] +Type=forking +ExecStart=/etc/rc.local +TimeoutSec=0 + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/redis.service b/app/config/_os/usr/lib/systemd/system/redis.service index d55bf4ed..212437c0 100644 --- a/app/config/_os/usr/lib/systemd/system/redis.service +++ b/app/config/_os/usr/lib/systemd/system/redis.service @@ -1,15 +1,18 @@ -[Unit] -Description=Advanced key-value store -After=network.target - -[Service] -#Type=forking -User=redis -PIDFile=/run/redis/redis.pid -ExecStartPre=/bin/mkdir -p /var/lib/redis -ExecStart=/usr/bin/redis-server /etc/redis.conf -ExecStop=/usr/bin/redis-cli shutdown -Restart=always - -[Install] -WantedBy=multi-user.target +[Unit] +Description=Advanced key-value store +After=network.target + +[Service] +#Type=forking +User=redis +PIDFile=/run/redis/redis.pid +ExecStartPre=/bin/mkdir -p /var/lib/redis +ExecStart=/usr/bin/redis-server /etc/redis.conf +ExecStop=/usr/bin/redis-cli shutdown +Restart=always +RestartSec=1 +StartLimitInterval=30 +StartLimitBurst=20 + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/rune_PL_wrk.service b/app/config/_os/usr/lib/systemd/system/rune_PL_wrk.service index 1118fe75..cc387a7f 100644 --- a/app/config/_os/usr/lib/systemd/system/rune_PL_wrk.service +++ b/app/config/_os/usr/lib/systemd/system/rune_PL_wrk.service @@ -1,10 +1,14 @@ -[Unit] -Description=RuneAudio Playback Worker After=network.target - -[Service] -ExecStart=/var/www/command/rune_PL_wrk -TimeoutSec=0 -Restart=always - -[Install] -WantedBy=multi-user.target +[Unit] +Description=RuneAudio Playback Worker +After=network.target redis.target + +[Service] +ExecStart=/var/www/command/rune_PL_wrk +TimeoutSec=0 +Restart=always +RestartSec=1 +StartLimitInterval=30 +StartLimitBurst=20 + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/rune_SY_wrk.service b/app/config/_os/usr/lib/systemd/system/rune_SY_wrk.service index f8a4eacf..093b7d58 100644 --- a/app/config/_os/usr/lib/systemd/system/rune_SY_wrk.service +++ b/app/config/_os/usr/lib/systemd/system/rune_SY_wrk.service @@ -1,11 +1,14 @@ -[Unit] -Description=RuneAudio System Worker -After=multi-user.target redis.service - -[Service] -ExecStart=/var/www/command/rune_SY_wrk -TimeoutSec=0 -Restart=always - -[Install] -WantedBy=multi-user.target +[Unit] +Description=RuneAudio System Worker +After=multi-user.target network.target redis.target + +[Service] +ExecStart=/var/www/command/rune_SY_wrk +TimeoutSec=0 +Restart=always +RestartSec=1 +StartLimitInterval=30 +StartLimitBurst=20 + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/rune_shutdown.service b/app/config/_os/usr/lib/systemd/system/rune_shutdown.service index 775d8f36..aa58863e 100644 --- a/app/config/_os/usr/lib/systemd/system/rune_shutdown.service +++ b/app/config/_os/usr/lib/systemd/system/rune_shutdown.service @@ -1,14 +1,14 @@ -[Unit] -Description=RuneAudio shutdown script -ConditionFileIsExecutable=/var/www/command/rune_shutdown -DefaultDependencies=no -Before=shutdown.target reboot.target halt.target - -[Service] -Type=oneshot -RemainAfterExit=true -ExecStart=/bin/true -ExecStop=/var/www/command/rune_shutdown - -[Install] +[Unit] +Description=RuneAudio shutdown script +ConditionFileIsExecutable=/var/www/command/rune_shutdown +DefaultDependencies=no +Before=shutdown.target reboot.target halt.target + +[Service] +Type=oneshot +RemainAfterExit=true +ExecStart=/bin/true +ExecStop=/var/www/command/rune_shutdown + +[Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/app/config/_os/usr/lib/systemd/system/shairport.service b/app/config/_os/usr/lib/systemd/system/shairport.service old mode 100755 new mode 100644 index 7da98604..f6e2c9ff --- a/app/config/_os/usr/lib/systemd/system/shairport.service +++ b/app/config/_os/usr/lib/systemd/system/shairport.service @@ -1,12 +1,12 @@ -[Unit] -Description=Shairport AirTunes receiver -After=sound.target -Requires=avahi-daemon.service -After=avahi-daemon.service - -[Service] -ExecStart=/usr/bin/shairport -w --on-start=/var/www/command/airplay_toggle --on-stop=/var/www/command/airplay_toggle -o alsa -- -d hw:0,0 -Restart=always - -[Install] -WantedBy=multi-user.target +[Unit] +Description=Shairport AirTunes receiver +After=sound.target +Requires=avahi-daemon.service +After=avahi-daemon.service + +[Service] +ExecStart=/usr/bin/shairport -w --name=RuneAudio -M /run/shairport --on-start=/var/www/command/airplay_toggle --on-stop=/var/www/command/airplay_toggle -o alsa -- -d hw:0,0 +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/udevil.service b/app/config/_os/usr/lib/systemd/system/udevil.service index 7ef7b0f1..75204712 100644 --- a/app/config/_os/usr/lib/systemd/system/udevil.service +++ b/app/config/_os/usr/lib/systemd/system/udevil.service @@ -1,14 +1,14 @@ -[Unit] -Description=Usb Automount (udevil) -After=sound.target -#Requires=mpd.service -#After=mpd.service - -[Service] -User=mpd -Group=mpd -ExecStart=/usr/bin/devmon -Restart=always - -[Install] -WantedBy=multi-user.target +[Unit] +Description=Usb Automount (udevil) +After=sound.target +#Requires=mpd.service +#After=mpd.service + +[Service] +User=mpd +Group=mpd +ExecStart=/usr/bin/devmon +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/app/config/_os/usr/lib/systemd/system/wpa_supplicant@.service b/app/config/_os/usr/lib/systemd/system/wpa_supplicant@.service deleted file mode 100644 index 15e880e5..00000000 --- a/app/config/_os/usr/lib/systemd/system/wpa_supplicant@.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=WPA supplicant daemon (interface-specific version) -Requires=sys-subsystem-net-devices-%i.device -After=sys-subsystem-net-devices-%i.device - -# NetworkManager users will probably want the dbus version instead. - -[Service] -Type=simple -ExecStart=/usr/bin/wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant.conf -i%I - -[Install] -Alias=multi-user.target.wants/wpa_supplicant@%i.service diff --git a/app/config/_os/usr/share/ca-certificates/runeaudio/gd_intermediate.crt b/app/config/_os/usr/share/ca-certificates/runeaudio/gd_intermediate.crt index 77cb1601..910e0a3f 100644 --- a/app/config/_os/usr/share/ca-certificates/runeaudio/gd_intermediate.crt +++ b/app/config/_os/usr/share/ca-certificates/runeaudio/gd_intermediate.crt @@ -1,30 +1,30 @@ ------BEGIN CERTIFICATE----- -MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx -ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g -RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw -MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH -QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j -b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j -b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H -KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm -VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR -SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT -cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ -6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu -MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS -kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB -BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f -BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv -c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH -AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO -BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG -OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU -A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o -0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX -RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH -qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV -U+4= ------END CERTIFICATE----- - +-----BEGIN CERTIFICATE----- +MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx +ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g +RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw +MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH +QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j +b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j +b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H +KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm +VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR +SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT +cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ +6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu +MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS +kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB +BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f +BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv +c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH +AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO +BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG +OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU +A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o +0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX +RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH +qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV +U+4= +-----END CERTIFICATE----- + diff --git a/app/config/config.php b/app/config/config.php index 5b0564ce..00602e95 100755 --- a/app/config/config.php +++ b/app/config/config.php @@ -48,15 +48,18 @@ //$redis->pconnect('/tmp/redis.sock'); $devmode = $redis->get('dev'); $activePlayer = $redis->get('activePlayer'); + // LogSettings if ($redis->get('debug') > 0 ) { - $activeLog=1; + $activeLog='1'; } else { - $activeLog=0; + $activeLog='0'; } ini_set('log_errors', $activeLog); -ini_set('error_log', '/var/log/runeaudio/runeui.log'); +// ini_set('error_log', '/var/log/runeaudio/runeui.log'); +ini_set('error_log', '/var/log/runeaudio/runeui_error.log'); ini_set('display_errors', $activeLog); + // connect to MPD daemon if ($_SERVER["SCRIPT_FILENAME"] === '/var/www/command/index.php' && $activePlayer === 'MPD') { // debug @@ -68,4 +71,4 @@ $mpd = openMpdSocket('/run/mpd.sock', 1); } elseif ($redis->hGet('spotify', 'enable') === '1' && $activePlayer === 'Spotify') { $spop = openSpopSocket('localhost', 6602, 1); -} +} \ No newline at end of file diff --git a/app/config/defaults/eth0 b/app/config/defaults/eth0 index 50480b3b..6020c6d7 100644 --- a/app/config/defaults/eth0 +++ b/app/config/defaults/eth0 @@ -1,7 +1,7 @@ -Description='eth0 connection' -Interface=eth0 -ForceConnect=yes -SkipNoCarrier=yes -Connection=ethernet -IP=dhcp -ExecUpPost='/usr/bin/ntpd -gq || true' +Description='eth0 connection' +Interface=eth0 +ForceConnect=yes +SkipNoCarrier=yes +Connection=ethernet +IP=dhcp +ExecUpPost='/usr/bin/ntpd -gq || true' diff --git a/app/config/defaults/libao.conf b/app/config/defaults/libao.conf index ad2055b9..a3256486 100755 --- a/app/config/defaults/libao.conf +++ b/app/config/defaults/libao.conf @@ -1,3 +1,3 @@ -default_driver=alsa -use_mmap=no -dev=hw:0,0 +default_driver=alsa +use_mmap=no +dev=hw:0,0 diff --git a/app/config/defaults/mpdscribble.conf b/app/config/defaults/mpdscribble.conf index 84e6a790..64159949 100644 --- a/app/config/defaults/mpdscribble.conf +++ b/app/config/defaults/mpdscribble.conf @@ -1,54 +1,54 @@ -## mpdscribble - an audioscrobbler for the Music Player Daemon. -## http://mpd.wikia.com/wiki/Client:mpdscribble - -# HTTP proxy URL. -#proxy = http://the.proxy.server:3128 - -# The location of the pid file. mpdscribble saves its process id there. -#pidfile = /var/run/mpdscribble.pid - -# Change to this system user after daemonization. -#daemon_user = mpdscribble - -# The location of the mpdscribble log file. The special value -# "syslog" makes mpdscribble use the local syslog daemon. On most -# systems, log messages will appear in /var/log/daemon.log then. -# "-" means log to stderr (the current terminal). -log = syslog - -# How verbose mpdscribble's logging should be. Default is 1. -verbose = 1 - -# How often should mpdscribble save the journal file? [seconds] -#journal_interval = 600 - -# The host running MPD, possibly protected by a password -# ([PASSWORD@]HOSTNAME). Defaults to $MPD_HOST or localhost. -#host = localhost - -# The port that the MPD listens on and mpdscribble should try to -# connect to. Defaults to $MPD_PORT or 6600. -#port = 6600 - -[last.fm] -url = http://post.audioscrobbler.com/ -username = user -password = pass -# The file where mpdscribble should store its Last.fm journal in case -# you do not have a connection to the Last.fm server. -journal = /run/lastfm.journal - -#[libre.fm] -#url = http://turtle.libre.fm/ -#username = my_username -#password = my_password -#journal = /var/cache/mpdscribble/librefm.journal - -#[jamendo] -#url = http://postaudioscrobbler.jamendo.com/ -#username = my_username -#password = my_password -#journal = /var/cache/mpdscribble/jamendo.journal - -#[file] -#file = /var/log/mpdscribble/log +## mpdscribble - an audioscrobbler for the Music Player Daemon. +## http://mpd.wikia.com/wiki/Client:mpdscribble + +# HTTP proxy URL. +#proxy = http://the.proxy.server:3128 + +# The location of the pid file. mpdscribble saves its process id there. +#pidfile = /var/run/mpdscribble.pid + +# Change to this system user after daemonization. +#daemon_user = mpdscribble + +# The location of the mpdscribble log file. The special value +# "syslog" makes mpdscribble use the local syslog daemon. On most +# systems, log messages will appear in /var/log/daemon.log then. +# "-" means log to stderr (the current terminal). +log = syslog + +# How verbose mpdscribble's logging should be. Default is 1. +verbose = 1 + +# How often should mpdscribble save the journal file? [seconds] +#journal_interval = 600 + +# The host running MPD, possibly protected by a password +# ([PASSWORD@]HOSTNAME). Defaults to $MPD_HOST or localhost. +#host = localhost + +# The port that the MPD listens on and mpdscribble should try to +# connect to. Defaults to $MPD_PORT or 6600. +#port = 6600 + +[last.fm] +url = http://post.audioscrobbler.com/ +username = user +password = pass +# The file where mpdscribble should store its Last.fm journal in case +# you do not have a connection to the Last.fm server. +journal = /run/lastfm.journal + +#[libre.fm] +#url = http://turtle.libre.fm/ +#username = my_username +#password = my_password +#journal = /var/cache/mpdscribble/librefm.journal + +#[jamendo] +#url = http://postaudioscrobbler.jamendo.com/ +#username = my_username +#password = my_password +#journal = /var/cache/mpdscribble/jamendo.journal + +#[file] +#file = /var/log/mpdscribble/log diff --git a/app/config/defaults/shairport.service b/app/config/defaults/shairport.service index 13306ab5..1ba82904 100755 --- a/app/config/defaults/shairport.service +++ b/app/config/defaults/shairport.service @@ -1,12 +1,12 @@ -[Unit] -Description=Shairport AirTunes receiver -After=sound.target -Requires=avahi-daemon.service -After=avahi-daemon.service - -[Service] -ExecStart=/usr/bin/shairport -w --name=RuneAudio --on-start=/var/www/command/airplay_toggle --on-stop=/var/www/command/airplay_toggle -o alsa -- -d hw:0,0 -Restart=always - -[Install] -WantedBy=multi-user.target +[Unit] +Description=Shairport AirTunes receiver +After=sound.target +Requires=avahi-daemon.service +After=avahi-daemon.service + +[Service] +ExecStart=/usr/bin/shairport -w --name=RuneAudio --on-start=/var/www/command/airplay_toggle --on-stop=/var/www/command/airplay_toggle -o alsa -- -d hw:0,0 +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/app/config/defaults/spopd.conf b/app/config/defaults/spopd.conf index f9380357..8006f3b3 100644 --- a/app/config/defaults/spopd.conf +++ b/app/config/defaults/spopd.conf @@ -1,38 +1,38 @@ -# RuneAudio SPOP configuration -# SPOP Spotify client daemon by Thomas Jost (Schnouki) -# GitHub repo: https://github.com/Schnouki/spop - -### Spotify auth settings ### -[spop] -spotify_username = user -spotify_password = pass - -### Audio settings ### -audio_output = ao -high_bitrate = true -offline_high_bitrate = true - -### Cache settings ### -#cache_size = 0 -cache_path = -settings_path = /tmp/spop - -### Network settings ### -#listen_address = 127.0.0.1 -#listen_port = 6602 -## proxy_config -#proxy=http://proxy.lan:3128 -#proxy_username= -#proxy_password= - -### System settings ### -log_file = /var/log/runeaudio/spopd.log -#plugins = scrobble - -[scrobble] -# API endpoint of the scrobbling service you're using. -# - Last.FM: http://post.audioscrobbler.com:80/ -# - Libre.FM: http://turtle.libre.fm/ -api_endpoint = http://post.audioscrobbler.com:80/ -username = user -password = pass +# RuneAudio SPOP configuration +# SPOP Spotify client daemon by Thomas Jost (Schnouki) +# GitHub repo: https://github.com/Schnouki/spop + +### Spotify auth settings ### +[spop] +spotify_username = user +spotify_password = pass + +### Audio settings ### +audio_output = ao +high_bitrate = true +offline_high_bitrate = true + +### Cache settings ### +#cache_size = 0 +cache_path = +settings_path = /tmp/spop + +### Network settings ### +#listen_address = 127.0.0.1 +#listen_port = 6602 +## proxy_config +#proxy=http://proxy.lan:3128 +#proxy_username= +#proxy_password= + +### System settings ### +log_file = /var/log/runeaudio/spopd.log +#plugins = scrobble + +[scrobble] +# API endpoint of the scrobbling service you're using. +# - Last.FM: http://post.audioscrobbler.com:80/ +# - Libre.FM: http://turtle.libre.fm/ +api_endpoint = http://post.audioscrobbler.com:80/ +username = user +password = pass diff --git a/app/config/defaults/wpa_supplicant.conf b/app/config/defaults/wpa_supplicant.conf index d5d66e7e..40abe098 100644 --- a/app/config/defaults/wpa_supplicant.conf +++ b/app/config/defaults/wpa_supplicant.conf @@ -1,2 +1,2 @@ -ctrl_interface=/var/run/wpa_supplicant -update_config=1 +ctrl_interface=/var/run/wpa_supplicant +update_config=1 diff --git a/app/config_ctl.php b/app/config_ctl.php new file mode 100644 index 00000000..4efb9ea9 --- /dev/null +++ b/app/config_ctl.php @@ -0,0 +1,34 @@ +. + * + * file: app/config_ctl.php + * version: 1.3 + * coder: Simone De Gregori + * + */ + \ No newline at end of file diff --git a/app/credits_ctl.php b/app/credits_ctl.php old mode 100644 new mode 100755 index 5a2ae29c..87eeddb6 --- a/app/credits_ctl.php +++ b/app/credits_ctl.php @@ -33,4 +33,5 @@ */ $template->buildversion = $redis->get('buildversion'); + $template->release = $redis->get('release'); \ No newline at end of file diff --git a/app/dev_ctl.php b/app/dev_ctl.php index 8bee4b72..b361e581 100755 --- a/app/dev_ctl.php +++ b/app/dev_ctl.php @@ -87,6 +87,7 @@ $template->debug = $redis->get('debug'); $template->playerid = $redis->get('playerid'); $template->opcache = $redis->get('opcache'); +$template->gitbranch = $redis->hGet('git', 'branch'); // debug // var_dump($template->dev); // var_dump($template->debug); diff --git a/app/libs/composer.json b/app/libs/composer.json index 29f3f8d6..bbf45b3b 100644 --- a/app/libs/composer.json +++ b/app/libs/composer.json @@ -1,5 +1,5 @@ -{ - "require": { - "league/plates": "1.1.*", - } +{ + "require": { + "league/plates": "1.1.*", + } } \ No newline at end of file diff --git a/app/libs/runeaudio.php b/app/libs/runeaudio.php index a8f1749b..85dda101 100755 --- a/app/libs/runeaudio.php +++ b/app/libs/runeaudio.php @@ -95,14 +95,8 @@ function sendMpdCommand($sock, $cmd) if (isset($sock['resource'])) { $sock = $sock['resource']; } - if ($cmd == 'cmediafix') { - socket_write($sock, 'pause\n', strlen('pause\n')); - usleep(500000); - socket_write($sock, 'pause\n', strlen('pause\n')); - } else { - $cmd = $cmd."\n"; - socket_write($sock, $cmd, strlen($cmd)); - } + $cmd = $cmd."\n"; + socket_write($sock, $cmd, strlen($cmd)); runelog("MPD COMMAND: (socket=".$sock.")", $cmd); } @@ -339,14 +333,8 @@ function sendSpopCommand($sock, $cmd) if (isset($sock['resource'])) { $sock = $sock['resource']; } - if ($cmd == 'cmediafix') { - socket_write($sock, 'toggle\n', strlen('toggle\n')); - usleep(500000); - socket_write($sock, 'toggle\n', strlen('toggle\n')); - } else { - $cmd = $cmd."\n"; - socket_write($sock, $cmd, strlen($cmd)); - } + $cmd = $cmd."\n"; + socket_write($sock, $cmd, strlen($cmd)); runelog("SPOP COMMAND: (socket=".$sock.")", $cmd); //ui_notify('COMMAND GIVEN','CMD = '.$cmd,'','.9'); } @@ -587,6 +575,12 @@ function deleteBookmark($redis, $id) return $return; } + +// browseDB +// $sock = MPD Socket +// $browsemode = ("file", etc.) +// $query = the path ("NAS/Music", etc.) + function browseDB($sock,$browsemode,$query) { switch ($browsemode) { case 'file': @@ -750,11 +744,17 @@ function sysCmd($syscmd) return $output; } -function sysCmdAsync($syscmd) { -exec($syscmd." > /dev/null 2>&1 &", $output); -// runelog('sysCmdAsync($str)',$syscmd); -// runelog('sysCmdAsync() output:',$output); -return $output; +function sysCmdAsync($syscmd, $waitsec = null) { + if (isset($waitsec)) { + $cmdstr = "/var/www/command/cmd_async ".base64_encode($syscmd); + } else { + $cmdstr = "/var/www/command/cmd_async ".base64_encode($syscmd); + } + exec($cmdstr." > /dev/null 2>&1 &", $output); + runelog('sysCmdAsync($cmdstr) decoded', $syscmd, __FUNCTION__); + runelog('sysCmdAsync($cmdstr) encoded', $cmdstr, __FUNCTION__); + runelog('sysCmdAsync() output:', $output, __FUNCTION__); + return $output; } function getMpdDaemonDetalis() @@ -774,7 +774,9 @@ function _parseFileListResponse($resp) if (is_null($resp)) { return null; } else { + $plistArray = array(); + $plistAlphabet = array(); $plistLine = strtok($resp, "\n"); // $plistFile = ""; $plCounter = -1; @@ -794,6 +796,21 @@ function _parseFileListResponse($resp) $dirCounter++; // $plistFile = $value; $plistArray[$plCounter]['directory'] = $value; + + // + // Set up server side Alphabet Navigation + // get the first letter, and if it is unique, send it along in the JSON for this item + $str = strtoupper(substr($value, strrpos($value, '/') + 1, 1)); + + if (!in_array($str, $plistAlphabet)) { + // working with only letters for now + if (preg_match('/^\s*[a-z,A-Z]/', $str) > 0) { + array_push($plistAlphabet, $str); + $plistArray[$plCounter]['firstLetter'] = $str; + }; + } + // + } else if ($browseMode) { if ( $element === 'Album' ) { $plCounter++; @@ -1197,7 +1214,11 @@ function runelog($title, $data = null, $function_name = null) error_log($function_name.'### '.$title.' ### [\''.$key.'\'] => '.$value,0); } } else { - error_log($function_name.'### '.$title.' ### '.$data,0); + $msg = $function_name.'### '.$title.' ### '; + if (isset($data)) { + $msg = $msg.$data; + } + error_log($msg,0); } } $store->close(); @@ -1213,7 +1234,7 @@ function waitSyWrk($redis, $jobID) } } elseif (!empty($jobID)) { do { - usleep(650000); + usleep(650000); } while ($redis->sIsMember('w_lock', $jobID)); } } @@ -1235,7 +1256,8 @@ function wrk_control($redis, $action, $data) break; } // debug - runelog('[wrk] wrk_control($redis,'.$action.','.$data.') jobID=', $jobID); + $msg = '[wrk] wrk_control($redis,'.$action.','.$data.') jobID='.$jobID; + runelog(msg); // [TODO] check this return $jobID; } @@ -1298,29 +1320,24 @@ function wrk_backup($bktype) function wrk_opcache($action, $redis) { +// debug +runelog('wrk_opcache ', $action); switch ($action) { case 'prime': - if ($redis->get('opcache') == 1) { - $ch = curl_init('http://localhost/command/cachectl.php?action=prime'); - curl_exec($ch); - curl_close($ch); - } - runelog('wrk_opcache ', $action); + opcache_reset(); + if ($redis->get('opcache') == 1) sysCmd('curl http://127.0.0.1/command/cachectl.php?action=prime'); break; case 'forceprime': - $ch = curl_init('http://localhost/command/cachectl.php?action=prime'); - curl_exec($ch); - curl_close($ch); - runelog('wrk_opcache ', $action); + opcache_reset(); + sysCmd('curl http://127.0.0.1/command/cachectl.php?action=prime'); break; case 'reset': - $ch = curl_init('http://localhost/command/cachectl.php?action=reset'); - curl_exec($ch); - curl_close($ch); - runelog('wrk_opcache ', $action); + // sysCmd('curl http://127.0.0.1/clear'); + // reset cache + OpCacheCtl('reset', '/srv/http/'); + opcache_reset(); break; case 'enable': - wrk_opcache('reset'); // opcache.ini $file = '/etc/php/conf.d/opcache.ini'; $newArray = wrk_replaceTextLine($file, '', 'opcache.enable', 'opcache.enable=1', 'zend_extension', 1); @@ -1328,10 +1345,9 @@ function wrk_opcache($action, $redis) $fp = fopen($file, 'w'); fwrite($fp, implode("", $newArray)); fclose($fp); - runelog('wrk_opcache ', $action); + $redis->set('opcache', 1); break; case 'disable': - wrk_opcache('reset'); // opcache.ini // -- REWORK NEEDED -- $file = '/etc/php/conf.d/opcache.ini'; @@ -1340,18 +1356,19 @@ function wrk_opcache($action, $redis) $fp = fopen($file, 'w'); fwrite($fp, implode("", $newArray)); fclose($fp); - runelog('wrk_opcache ', $action); + $redis->set('opcache', 0); break; } } function wrk_netconfig($redis, $action, $args = null, $configonly = null) { -$updateh = 0; + // nics blacklist + $excluded_nics = array('ifb0', 'ifb1', 'p2p0', 'bridge'); + + $updateh = 0; switch ($action) { case 'setnics': - // nics blacklist - $excluded_nics = array('ifb0', 'ifb1', 'p2p0', 'bridge'); // flush nics Redis hash table $transaction = $redis->multi(); $transaction->del('nics'); @@ -1381,44 +1398,71 @@ function wrk_netconfig($redis, $action, $args = null, $configonly = null) $transaction->hSet('nics', $interface , json_encode(array('ip' => $ip[0], 'netmask' => $netmask, 'gw' => $gw[0], 'dns1' => $dns[0], 'dns2' => $dns[1], 'speed' => $speed[0],'wireless' => 0))); } } - //// if ($scanwifi) sysCmdAsync('/var/www/command/refresh_nics'); - // check wpa_supplicant auto-start and purge interface list - $wpa_supplicant_start = sysCmd("ls -lah /etc/systemd/system/multi-user.target.wants/wpa_supplicant@*.service | cut -d '@' -f 2 | cut -d '.' -f 1"); - $disable_wpa_supplicant = array_diff($wpa_supplicant_start, $interfaces); - if (!empty($disable_wpa_supplicant)) { - foreach ($disable_wpa_supplicant as $interface_name) { - unlink('/etc/systemd/system/multi-user.target.wants/wpa_supplicant@'.$interface_name.'.service'); - } - } $transaction->exec(); break; case 'getnics': - foreach ($redis->hGetAll('nics') as $interface => $details) { - $interfaces[$interface] = json_decode($details); + $interfaces = sysCmd("ip addr |grep \"BROADCAST,\" |cut -d':' -f1-2 |cut -d' ' -f2"); + $interfaces = array_diff($interfaces, $excluded_nics); + foreach ($interfaces as $interface) { + $ip = sysCmd("ip addr list ".$interface." |grep \"inet \" |cut -d' ' -f6|cut -d/ -f1"); + $netmask = sysCmd("ip addr list ".$interface." |grep \"inet \" |cut -d' ' -f6|cut -d/ -f2"); + if (isset($netmask[0]) && isset($ip[0])) { + $netmask = "255.255.255.0";//netmask($netmask[0]); + } else { + unset($netmask); + } + if (isset($netmask[0])) { + $gw = sysCmd("route -n |grep \"0.0.0.0\" |grep \"UG\" |cut -d' ' -f10"); + $dns = sysCmd("cat /etc/resolv.conf |grep \"nameserver\" |cut -d' ' -f2"); + } + $type = sysCmd("iwconfig ".$interface." 2>&1 | grep \"no wireless\""); + if (empty($type[0])) { + $speed = sysCmd("iwconfig ".$interface." 2>&1 | grep 'Bit Rate' | cut -d ':' -f 2 | cut -d ' ' -f 1-2"); + $currentSSID = sysCmd("iwconfig ".$interface." | grep 'ESSID' | cut -d ':' -f 2 | cut -d '\"' -f 2"); + $actinterfaces[$interface] = (object) ['ip' => $ip[0], 'netmask' => $netmask, 'gw' => $gw[0], 'dns1' => $dns[0], 'dns2' => $dns[1], 'speed' => $speed[0], 'wireless' => 1, 'currentssid' => $currentSSID[0]]; + $redis->hSet('nics', $interface , json_encode(array('ip' => $ip[0], 'netmask' => $netmask, 'gw' => $gw[0], 'dns1' => $dns[0], 'dns2' => $dns[1], 'speed' => $speed[0],'wireless' => 1, 'currentssid' => $currentSSID[0]))); + } else { + $speed = sysCmd("ethtool ".$interface." 2>&1 | grep -i speed | cut -d':' -f2"); + $actinterfaces[$interface] = (object) ['ip' => $ip[0], 'netmask' => $netmask, 'gw' => $gw[0], 'dns1' => $dns[0], 'dns2' => $dns[1], 'speed' => $speed[0], 'wireless' => 0]; + $redis->hSet('nics', $interface , json_encode(array('ip' => $ip[0], 'netmask' => $netmask, 'gw' => $gw[0], 'dns1' => $dns[0], 'dns2' => $dns[1], 'speed' => $speed[0],'wireless' => 0))); + } } - return $interfaces; + return $actinterfaces; + break; + case 'getstoredwlans': + sysCmd('/www/command/refresh_nics'); + $wlans_profiles = json_decode($redis->Get('stored_profiles')); + foreach ($wlans_profiles as $profile) { + runelog(' Get stored wlan profiles:'.$profile); + if ($nicdetail->currentssid === $profile) { + $connected = 1; + } else { + $connected = 0; + } + $wlans[] = json_encode(array('ssid' => $profile, 'encryption' => 'on', 'connected' => $connected, 'storedprofile' => 1)); + } + return $wlans; break; case 'writecfg': // ArchLinux netctl config for wired ethernet $nic = "Description='".$args->name." connection'\n"; $nic .= "Interface=".$args->name."\n"; - $nic .= "ForceConnect=yes\n"; - $nic .= "SkipNoCarrier=yes\n"; if ($args->wireless === '1') { // Wireless configuration $nic .= "Connection=wireless\n"; - $nic .= "Security=wpa-config\n"; - $nic .= "WPAConfigFile='/etc/wpa_supplicant/wpa_supplicant.conf'\n"; + $nic .= "Security=wpa-configsection\n"; } else { // Wired configuration + $nic .= "ForceConnect=yes\n"; + $nic .= "SkipNoCarrier=yes\n"; $nic .= "Connection=ethernet\n"; } if ($args->dhcp === '1') { // DHCP configuration $nic .= "IP=dhcp\n"; // Prepare data object for Redis record - $args = array( 'name' => $args->name, 'dhcp' => $args->dhcp ); - $args = (object) $args; + $dhcpargs = array( 'name' => $args->name, 'dhcp' => $args->dhcp ); + $dhcpargs = (object) $args; } else { // STATIC configuration $nic .= "AutoWired=yes\n"; @@ -1431,8 +1475,66 @@ function wrk_netconfig($redis, $action, $args = null, $configonly = null) $nic .= "DNS=('".$args->dns1."')\n"; } } - // ntp sync after connect - $nic .= "ExecUpPost='/usr/bin/ntpd -gq || true'\n"; + if ($args->wireless === '1') { + $nic .= "WPAConfigSection=(\n"; + if ($args->newssid === "add") { + $nic .= " 'ssid=\"".$args->ssid."\"'\n"; + $nic .= " 'scan_ssid=1'\n"; + } else { + $nic .= " 'ssid=\"".$args->newssid."\"'\n"; + } + //$key = $args->key; + switch ($args->encryption) { + case 'none': + $nic .= " 'key_mgmt=NONE'\n"; + break; + case 'wep': + $nic .= " 'key_mgmt=NONE'\n"; + $nic .= " 'wep_tx_keyidx=0'\n"; + $wepkey = $args->key; + if (ctype_xdigit($wepkey) && (strlen($wepkey) == 10 OR strlen($wepkey) == 26 OR strlen($wepkey) == 32)) { + $nic .= " 'wep_key0=".$wepkey."'\n"; + } elseif (strlen($wepkey) <= 16) { + $nic .= " 'wep_key0=\"".$wepkey."\"'\n"; + } else { + $nic .= " 'wep_key0=\"* wrong wepkey *\"'\n"; + ui_notify_async('WIFI-Config ERROR', "You entered a wrong key!\n"); + return ''; + } + // auth_alg=SHARED + break; + case 'wpa': + $wpakey = $args->key; + if (ctype_xdigit($wpakey) && strlen($wpakey) == 64) { + $nic .= " 'psk=".$wpakey."'\n"; + } elseif (strlen($wpakey) >= 8 && strlen($wpakey) <= 63) { + $nic .= " 'psk=\"".$wpakey."\"'\n"; + } else { + $nic .= " 'psk=\"* wrong wepkey *\"'\n"; + ui_notify_async('WIFI-Config ERROR', "You entered a wrong key!\n"); + return ''; + } + $nic .= " 'key_mgmt=WPA-PSK'\n"; + if (strpos($args->ie, "WPA2") !== false) { + $nic .= " 'proto=RSN'\n"; + } else { + $nic .= " 'proto=WPA'\n"; + } + if (strpos($args->GroupCipher, "CCMP") !== false) { + $nic .= " 'group=CCMP'\n"; + } else { + $nic .= " 'group=TKIP'\n"; + } + if (strpos($args->PairwiseCiphers1, "CCMP") !== false OR strpos($args->PairwiseCiphers2, "CCMP") !== false) { + $nic .= " 'pairwise=CCMP'\n"; + } else { + $nic .= " 'pairwise=TKIP'\n"; + } + } + $nic .= " 'priority=3'\n"; + $nic .= ")\n"; + } + // set advanced DNS options $newArray = wrk_replaceTextLine('/etc/resolvconf.conf', '', 'resolv_conf_options=', "resolv_conf_options=('timeout:".$redis->hGet('resolvconf', 'timeout')." attempts:".$redis->hGet('resolvconf', 'attempts')."')", '#name_servers=127.0.0.1', 1); // Commit changes to /etc/resolvconf.conf @@ -1443,11 +1545,27 @@ function wrk_netconfig($redis, $action, $args = null, $configonly = null) sysCmd('resolvconf -u'); // write current network config - $redis->set($args->name, json_encode($args)); - $fp = fopen('/etc/netctl/'.$args->name, 'w'); + runelog("wireless = ".$args->wireless); + if ($args->wireless === '1') { + // wireless + runelog("save as wireless"); + if ($args->newssid === "add") { + $redis->Set($args->ssid, json_encode($args)); + $fp = fopen('/etc/netctl/'.$args->ssid, 'w'); + } else { + $redis->Set($args->newssid, json_encode($args)); + $fp = fopen('/etc/netctl/'.$args->newssid, 'w'); + } + } else { + // wired + runelog("save as wired"); + $redis->Set($args->name, json_encode($args)); + $fp = fopen('/etc/netctl/'.$args->name, 'w'); + } fwrite($fp, $nic); fclose($fp); if (!isset($configonly)) $updateh = 1; + //$updateh = 1; break; case 'manual': $file = '/etc/netctl/'.$args['name']; @@ -1466,195 +1584,68 @@ function wrk_netconfig($redis, $action, $args = null, $configonly = null) break; } if ($updateh === 1) { - runelog('wireless NIC?', $args->wireless); + sysCmd('mpc pause'); + //sysCmd('systemctl stop mpd'); // activate configuration (RuneOS) - wrk_mpdPlaybackStatus($redis, 'record'); - sysCmd('netctl stop '.$args->name); - sysCmd('ip addr flush dev '.$args->name); - if ($args->dhcp === '1') { - // dhcp configuration - if ($args->wireless !== '1') { - // $cmd = 'systemctl enable ifplugd@'.$args->name; - sysCmd("ln -s '/usr/lib/systemd/system/netctl-ifplugd@.service' '/etc/systemd/system/multi-user.target.wants/netctl-ifplugd@".$args->name.".service'"); - sysCmd('systemctl daemon-reload'); - sysCmd('systemctl start netctl-ifplugd@'.$args->name); + runelog("wireless = ".$args->wireless); + if ($args->wireless !== '1') { + sysCmd('systemctl reenable netctl-ifplugd@'.$args->name); + if ($args->reboot === '1') { + runelog('**** reboot requested ****', $args->name); + $return = 'reboot'; + } else { + runelog('**** no reboot requested ****', $args->name); + sysCmd('systemctl restart netctl-ifplugd@'.$args->name); + $return = ''; } - // sysCmd('systemctl daemon-reload'); } else { - // static configuration - if ($args->wireless !== '1') { - // $cmd = 'systemctl disable ifplugd@'.$args->name; - sysCmd("rm '/etc/systemd/system/multi-user.target.wants/netctl-ifplugd@".$args->name.".service'"); - sysCmd('systemctl daemon-reload'); - // kill ifplugd - sysCmd('killall ifplugd'); - } - // get pids of dhcpcd - $pids = sysCmd('pgrep -xf "dhcpcd -4 -q -t 30 -L '.$args->name.'"'); - foreach ($pids as $pid) { - // debug - runelog('kill pid:', $pid); - // kill dhcpcd - sysCmd('kill '.$pid); - } + sysCmd('systemctl reenable netctl-auto@'.$args->name); + sysCmd('systemctl restart netctl-auto@'.$args->name); + sysCmd('netctl-auto enable '.$args->newssid); + sysCmd('netctl-auto switch-to '.$args->newssid); + runelog('**** wireless => do not reboot ****', $args->name); + $return = ''; } - // start netctl profile for wired nics - if ($args->wireless !== '1') sysCmd('netctl start '.$args->name); - sysCmd('systemctl restart mpd'); - // set process priority - sysCmdAsync('sleep 1 && rune_prio nice'); } // update hash if necessary $updateh === 0 || $redis->set($args->name.'_hash', md5_file('/etc/netctl/'.$args->name)); if (wrk_mpdPlaybackStatus($redis, 'laststate') === 'playing') sysCmd('mpc play'); -} - -function wrk_wifiprofile($redis, $action, $args) -{ - switch ($action) { - case 'add': - $args->id = wrk_wpa_cli('add', $args); - if ($args->id !== false) { - unset($args->action); - $return = $redis->hSet('wlan_profiles', $args->ssid, json_encode($args)); - } - break; - case 'edit': - if(wrk_wpa_cli('edit', $args, $redis)) { - unset($args->action); - unset($args->edit); - $return = $redis->hSet('wlan_profiles', $args->ssid, json_encode($args)); - } - break; - case 'delete': - if (wrk_wpa_cli('delete', $args)) $return = $redis->hDel('wlan_profiles', $args->ssid); - break; - case 'connect': - if (wrk_wpa_cli('connect')) { - $redis->Set('wlan_autoconnect', 1); - $return = 1; - } - break; - case 'disconnect': - if (wrk_wpa_cli('disconnect')) { - $redis->Set('wlan_autoconnect', 0); - $return = 1; - } - break; - } return $return; } -function wrk_wpa_cli($action, $args = null, $redis = null) +function wrk_wifiprofile($redis, $action, $args) { - $return = 0; - // debug - runelog('**** wrk_wpa_cli() START ****'); - runelog('----- wrk_wpa_cli() args:',$args); - runelog('----- wrk_wpa_cli() action:',$action); switch ($action) { case 'add': - // add wpa entry in the stack - $wlanid = sysCmd('wpa_cli add_network'); - $args->id = $wlanid[1]; - // debug - runelog('wrk_wpa_cli('.$action.'): assigned wlan ID', $args->id); - $wpa_cli_response = wrk_wpa_cli('store', $args); - if ($wpa_cli_response) { - if (isset($args->connect)) { - wrk_wpa_cli('connect'); - } - $return = $args->id; - } else { - // rollback - sysCmd('wpa_cli remove_network '.$args->id); - $return = false; - } + runelog('**** wrk_wifiprofile ADD ****', $args->ssid); + wrk_wifiprofile($redis, 'connect', $args); break; case 'edit': - $args->edit = 1; - if (isset($args->connect)) { - unset($args->connect); - $connect = 1; - } - if (wrk_wpa_cli('store', $args, $redis)) { - if ($connect) wrk_wpa_cli('connect'); - $return = 1; - } - break; - case 'store': - // set default set password command - $cmd_pass = "wpa_cli set_network ".$args->id." psk '\"".$args->key."\"'"; - if (isset($args->edit)) { - $stored_profile = json_decode($redis->hGet('wlan_profiles',$args->ssid)); - if ($stored_profile->encryption === $args->encryption) { - // select correct change password command - $cmd_pass = "wpa_cli new_password ".$args->id." '\"".$args->key."\"'"; - } else { - // remove and - if (wrk_wpa_cli('delete', $args)) { - unset($args->edit); - wrk_wpa_cli('add', $args); - } else { - $return = false; - break; - } - } - } - // set the SSID - $wpa_cli_response = sysCmd("wpa_cli set_network ".$args->id." ssid '\"".$args->ssid."\"'"); - // set the encryption type - if ($wpa_cli_response[1] === 'OK') { - if ($args->encryption === 'open') { - $wpa_cli_response = sysCmd('wpa_cli set_network '.$args->id.' key_mgmt NONE'); - } - if ($args->encryption === 'wpa') { - $wpa_cli_response = sysCmd('wpa_cli set_network '.$args->id.' key_mgmt WPA-PSK'); - // store the encryption key - $wpa_cli_response = sysCmd($cmd_pass); - } - if ($args->encryption === 'wep') { - $wpa_cli_response = sysCmd('wpa_cli set_network '.$args->id.' key_mgmt NONE'); - // store the encryption key - if (isset($chpass)) { - $wpa_cli_response = sysCmd("wpa_cli new_password ".$args->id." '\"".$args->key."\"'"); - } else { - $wpa_cli_response = sysCmd("wpa_cli set_network ".$args->id." wep_key0 '\"".$args->key."\"'"); - } - } - } - // enable the new network profile - if ($wpa_cli_response[1] === 'OK') $wpa_cli_response = sysCmd('wpa_cli enable_network '.$args->id); - // save configuration file - if ($wpa_cli_response[1] === 'OK') $wpa_cli_response = sysCmd('wpa_cli save_config'); - if ($wpa_cli_response[1] === 'OK') { - $return = true; - } else { - $return = false; - } + runelog('**** wrk_wifiprofile EDIT ****', $args->ssid); + wrk_wifiprofile($redis, 'connect', $args); break; case 'delete': - $wpa_cli_response = sysCmd('wpa_cli remove_network '.$args->id); - if ($wpa_cli_response[1] === 'OK') $wpa_cli_response = sysCmd('wpa_cli save_config'); - if ($wpa_cli_response[1] === 'OK') { - $return = 1; - } else { - $return = false; - } + runelog('**** wrk_wifiprofile DELETE ****', $args->ssid); + wrk_wifiprofile($redis, 'disconnect', $args); + $redis->Del($args->ssid); + $redis->Del('stored_profiles'); + sysCmd("rm /etc/netctl/".$args->ssid); + sysCmdAsync("systemctl restart netctl-auto@".$args->nic); + $return = 1; break; case 'connect': - // TODO: enhance, catch the event "CTRL-EVENT-CONNECTED" instead of "OK" response from wpa_cli - $wpa_cli_response = sysCmd('wpa_cli reconnect'); - if ($wpa_cli_response[1] === 'OK') $return = 1; + runelog('**** wrk_wifiprofile CONNECT ****', $args->ssid); + sysCmdAsync("netctl-auto switch-to ".$args->ssid); + $redis->Set('wlan_autoconnect', 1); + $return = 1; break; case 'disconnect': - $wpa_cli_response = sysCmd('wpa_cli disconnect'); - if ($wpa_cli_response[1] === 'OK') $return = 1; + runelog('**** wrk_wifiprofile DISCONNECT ****', $args->ssid); + sysCmdAsync("netctl-auto disable ".$args->ssid); + $redis->Set('wlan_autoconnect', 0); + $return = 1; break; } - // debug - runelog('----- wrk_wpa_cli() exit status',$return); - runelog('**** wrk_wpa_cli() STOP ****'); return $return; } @@ -1855,6 +1846,12 @@ function wrk_i2smodule($redis, $args) } switch ($args) { case 'none': + // + // Not sure this section works as expected, but I added the "Remove" + // commands to address what could be added by selecting Transducer + sysCmd('rmmod snd_soc_rpi_dac').usleep(300000); + sysCmd('rmmod snd_soc_pcm1794a').usleep(300000); + // sysCmd('rmmod snd_soc_iqaudio_dac').usleep(300000); sysCmd('rmmod snd_soc_hifiberry_digi').usleep(300000); sysCmd('rmmod snd_soc_hifiberry_dac').usleep(300000); @@ -1862,7 +1859,8 @@ function wrk_i2smodule($redis, $args) sysCmd('rmmod snd_soc_wm8804').usleep(300000); sysCmd('rmmod snd_soc_pcm512x').usleep(300000); sysCmd('rmmod snd_soc_pcm5102a'); - break; + + break; case 'berrynos': sysCmd('modprobe bcm2708_dmaengine').usleep(300000); sysCmd('modprobe snd_soc_wm8804').usleep(300000); @@ -1919,6 +1917,16 @@ function wrk_i2smodule($redis, $args) sysCmd('modprobe snd_soc_pcm512x').usleep(300000); sysCmd('modprobe snd_soc_iqaudio_dac'); break; + // + // Adding an entry for the Musica Pristina Trtasnducer DAC, based on PCM 1794 + case 'transducer': + sysCmd('modprobe snd_soc_bcm2708_i2s').usleep(300000); + sysCmd('modprobe bcm2708_dmaengine').usleep(300000); + sysCmd('modprobe snd_soc_pcm1794a').usleep(300000); + sysCmd('modprobe snd_soc_rpi_dac'); + break; + + // } $redis->set('i2smodule', $args); wrk_mpdconf($redis, 'refresh'); @@ -1949,6 +1957,13 @@ function wrk_kernelswitch($redis, $args) function wrk_mpdconf($redis, $action, $args = null, $jobID = null) { +// check if we are in "advanced mode" (manual edit mode) +if ($action === 'reset') { + $redis->set('mpdconf_advanced', 0); + $mpdconf_advanced = 0; +} else { + $mpdconf_advanced = $redis->get('mpdconf_advanced'); +} // set mpd.conf file header $header = "###################################\n"; $header .= "# Auto generated mpd.conf file\n"; @@ -2051,8 +2066,8 @@ function wrk_mpdconf($redis, $action, $args = null, $jobID = null) // --- decoder plugin --- $output .="\n"; $output .="decoder {\n"; - $output .="plugin \t\"ffmpeg\"\n"; - $output .="enabled \"".$value."\"\n"; + $output .="\tplugin \t\"ffmpeg\"\n"; + $output .="\tenabled \"".$value."\"\n"; $output .="}\n"; continue; } @@ -2060,17 +2075,22 @@ function wrk_mpdconf($redis, $action, $args = null, $jobID = null) // --- input plugin --- $output .="\n"; $output .="input {\n"; - $output .="plugin \t\"curl\"\n"; + $output .="\tplugin \t\"curl\"\n"; if ($redis->hget('proxy','enable') === '1') { - $output .="proxy \t\"".($redis->hget('proxy', 'host'))."\"\n"; + $output .="\tproxy \t\"".($redis->hget('proxy', 'host'))."\"\n"; if ($redis->hget('proxy','user') !== '') { - $output .="proxy_user \t\"".($redis->hget('proxy', 'user'))."\"\n"; - $output .="proxy_password \t\"".($redis->hget('proxy', 'pass'))."\"\n"; + $output .="\tproxy_user \t\"".($redis->hget('proxy', 'user'))."\"\n"; + $output .="\tproxy_password \t\"".($redis->hget('proxy', 'pass'))."\"\n"; } } $output .="}\n"; continue; } + // if ($value === '1') { + // $value = 'yes'; + // } else if ($value === '0') { + // $value = 'no'; + // } $output .= $param." \t\"".$value."\"\n"; } $output = $header.$output; @@ -2080,16 +2100,17 @@ function wrk_mpdconf($redis, $action, $args = null, $jobID = null) runelog('detected ACARDS ', $acards, __FUNCTION__); $ao = $redis->Get('ao'); $sub_count = 0; - foreach ($acards as $card) { + foreach ($acards as $main_acard_name => $main_acard_details) { $card_decoded = new stdClass(); - $card_decoded = json_decode($card); + $card_decoded = json_decode($main_acard_details); // debug - runelog('decoded ACARD ', $card_decoded, __FUNCTION__); + runelog('decoded ACARD '.$card_decoded->name, $card_decoded, __FUNCTION__); + // handle sub-interfaces if (isset($card_decoded->integrated_sub) && $card_decoded->integrated_sub === 1) { // record UI audio output name $current_card = $card_decoded->name; - if ($sub_count >= 1) continue; - $card_decoded = json_decode($card_decoded->real_interface); + // if ($sub_count >= 1) continue; + // $card_decoded = json_decode($card_decoded->real_interface); runelog('current AO ----> ', $ao, __FUNCTION__); // var_dump($ao); runelog('current card_name ----> ', $card_decoded->name, __FUNCTION__); @@ -2101,54 +2122,61 @@ function wrk_mpdconf($redis, $action, $args = null, $jobID = null) // debug runelog('this is a sub_interface', __FUNCTION__); $sub_interface = 1; + // debug $sub_count++; runelog('sub_count', $sub_count, __FUNCTION__); } $output .="\n"; $output .="audio_output {\n"; - $output .="name \t\t\"".$card_decoded->name."\"\n"; - $output .="type \t\t\"".$card_decoded->type."\"\n"; - $output .="device \t\t\"".$card_decoded->device."\"\n"; + // $output .="name \t\t\"".$card_decoded->name."\"\n"; + if (isset($sub_interface)) { + $output .="\tname \t\t\"".$card_decoded->name."\"\n"; + } else { + $output .="\tname \t\t\"".$main_acard_name."\"\n"; + } + $output .="\ttype \t\t\"".$card_decoded->type."\"\n"; + $output .="\tdevice \t\t\"".$card_decoded->device."\"\n"; if (isset($hwmixer)) { - $output .="mixer_type \t\"hardware\"\n"; - if (isset($card_decoded->mixer_device)) { - $output .="mixer_device \t\"".$card_decoded->mixer_device."\"\n"; - } else { - $output .="mixer_device \t\"hw:".$card_decoded->device."\"\n"; - } - if (isset($card_decoded->mixer_control)) { - $output .="mixer_control \t\"".$card_decoded->mixer_control."\"\n"; + if (isset($card_decoded->mixer_control)) { + $output .="\tmixer_control \t\"".$card_decoded->mixer_control."\"\n"; + $output .="\tmixer_type \t\"hardware\"\n"; + $output .="\tmixer_device \t\"".substr($card_decoded->device, 0, 4)."\"\n"; } else { - $output .="mixer_control \t\"".alsa_findHwMixerControl(substr($card_decoded->device, 4, 1)).",0\"\n"; + if (!isset($sub_interface)) { + $output .="\tmixer_control \t\"".alsa_findHwMixerControl(substr($card_decoded->device, 5, 1))."\"\n"; + } } - $output .="mixer_index \t\"0\"\n";"\t\t \t\"0\"\n"; - } - if ($mpdcfg['dsd_usb'] === 'yes') $output .="dsd_usb \t\"yes\"\n"; - $output .="auto_resample \t\"no\"\n"; - $output .="auto_format \t\"no\"\n"; - if (isset($sub_interface_selected)) { - // use UI selector name to enable a sub_interface (ex. switch between AnalogOut / HDMI on RaspberryPI) - $output .="enabled \t\"yes\"\n"; - } else { - // normal condition - if ($ao === $card_decoded->name) $output .="enabled \t\"yes\"\n"; + // $output .="\tmixer_index \t\"0\"\n";"\t\t \t\"0\"\n"; } + if ($mpdcfg['dsd_usb'] === 'yes') $output .="\tdsd_usb \t\"yes\"\n"; + $output .="\tauto_resample \t\"no\"\n"; + $output .="\tauto_format \t\"no\"\n"; + if ($ao === $main_acard_name) $output .="\tenabled \t\"yes\"\n"; $output .="}\n"; - // unset($current_card); - // unset($sub_interface); - // unset($card_decoded); + unset($sub_interface); // debug - runelog('conf output (in loop)', $output, __FUNCTION__); + // runelog('conf output (in loop)', $output, __FUNCTION__); } $output .="\n"; // debug - runelog('raw mpd.conf', $output, __FUNCTION__); - // write mpd.conf file - $fh = fopen('/etc/mpd.conf', 'w'); - fwrite($fh, $output); - fclose($fh); - // update hash - $redis->set('mpdconfhash', md5_file('/etc/mpd.conf')); + // runelog('raw mpd.conf', $output, __FUNCTION__); + // check if mpd.conf was modified outside RuneUI (advanced mode) + runelog('mpd.conf advanced state', $mpdconf_advanced); + if ($mpdconf_advanced !== '1' OR $mpdconf_advanced === '') { + if ($mpdconf_advanced !== '') { + runelog('mpd.conf advanced mode OFF'); + } else { + runelog('mpd.conf advanced mode RESET STATE'); + } + // write mpd.conf file + $fh = fopen('/etc/mpd.conf', 'w'); + fwrite($fh, $output); + fclose($fh); + // update hash + $redis->set('mpdconfhash', md5_file('/etc/mpd.conf')); + } else { + runelog('mpd.conf advanced mode ON'); + } break; case 'update': foreach ($args as $param => $value) { @@ -2169,7 +2197,7 @@ function wrk_mpdconf($redis, $action, $args = null, $jobID = null) sysCmd($interface_details->route_cmd); // TODO: improove this function sysCmd('amixer -c 0 set PCM unmute'); - $mpdout = $interface_details->sysname; + // $mpdout = $interface_details->sysname; } wrk_mpdconf($redis, 'writecfg'); wrk_shairport($redis, $args); @@ -2355,12 +2383,20 @@ function wrk_sourcecfg($redis, $action, $args) switch ($action) { case 'add': // unset($args->id); + runelog('KEW wrk_sourcecfg PRE redis incr(mountidx)', $args->id); + $args->id = $redis->incr('mountidx'); + + runelog('KEW wrk_sourcecfg POST', $args->id); + $args = (array) $args; $redis->hMset('mount_'.$args['id'], $args); $return = wrk_sourcemount($redis, 'mount', $args['id']); break; case 'edit': + + runelog('KEW wrk_sourcecfg PRE edit', $args->id); + $mp = $redis->hGetAll('mount_'.$args->id); $args = (array) $args; $redis->hMset('mount_'.$args['id'], $args); @@ -2657,6 +2693,8 @@ function alsa_findHwMixerControl($cardID) $cmd = "amixer -c ".$cardID." |grep \"mixer control\""; $str = sysCmd($cmd); $hwmixerdev = substr(substr($str[0], 0, -(strlen($str[0]) - strrpos($str[0], "'"))), strpos($str[0], "'")+1); + runelog('Try to find HwMixer control (str): ', $str); + runelog('Try to find HwMixer control: (output)', $hwmixerdev); return $hwmixerdev; } @@ -3054,7 +3092,7 @@ function netmask($bitcount) } // sort multi-dimensional array by key -function osort(&$array, $key) +function osort($array, $key) { usort($array, function($a, $b) use ($key) { return $a->$key > $b->$key ? 1 : -1; diff --git a/app/libs/vendor/evenement/evenement/LICENSE b/app/libs/vendor/evenement/evenement/LICENSE index d9a37d0a..dfb5651d 100644 --- a/app/libs/vendor/evenement/evenement/LICENSE +++ b/app/libs/vendor/evenement/evenement/LICENSE @@ -1,19 +1,19 @@ -Copyright (c) 2011 Igor Wiedler - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +Copyright (c) 2011 Igor Wiedler + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/libs/vendor/league/plates/LICENSE b/app/libs/vendor/league/plates/LICENSE index 26357d46..e8f025cc 100644 --- a/app/libs/vendor/league/plates/LICENSE +++ b/app/libs/vendor/league/plates/LICENSE @@ -1,21 +1,21 @@ -The MIT License (MIT) - -Copyright (c) 2013 The League of Extraordinary Packages - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +The MIT License (MIT) + +Copyright (c) 2013 The League of Extraordinary Packages + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/app/libs/vendor/react/promise/LICENSE b/app/libs/vendor/react/promise/LICENSE index 9bfcd411..7298c68d 100644 --- a/app/libs/vendor/react/promise/LICENSE +++ b/app/libs/vendor/react/promise/LICENSE @@ -1,22 +1,22 @@ -Copyright (c) 2012 Jan Sorgalla - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2012 Jan Sorgalla + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/app/libs/vendor/react/react/LICENSE b/app/libs/vendor/react/react/LICENSE index a808108c..c5678ca9 100644 --- a/app/libs/vendor/react/react/LICENSE +++ b/app/libs/vendor/react/react/LICENSE @@ -1,19 +1,19 @@ -Copyright (c) 2012 Igor Wiedler, Chris Boden - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +Copyright (c) 2012 Igor Wiedler, Chris Boden + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/app/libs/vendor/react/react/tests/Dns/Fixtures/etc/resolv.conf b/app/libs/vendor/react/react/tests/Dns/Fixtures/etc/resolv.conf index cae093a8..c3b2b3d1 100644 --- a/app/libs/vendor/react/react/tests/Dns/Fixtures/etc/resolv.conf +++ b/app/libs/vendor/react/react/tests/Dns/Fixtures/etc/resolv.conf @@ -1 +1 @@ -nameserver 8.8.8.8 +nameserver 8.8.8.8 diff --git a/app/network_ctl.php b/app/network_ctl.php index e3d7fbab..d406379a 100755 --- a/app/network_ctl.php +++ b/app/network_ctl.php @@ -34,40 +34,44 @@ // inspect POST if (isset($_POST)) { - if (isset($_POST['nic'])) { + if (isset($_POST['nic']) && !isset($_POST['wifiprofile'])) { + //ui_notify_async("'wrkcmd' => 'netcfg', 'action' => 'config'", $_POST['nic']); $redis->get($_POST['nic']['name']) === json_encode($nic) || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'netcfg', 'action' => 'config', 'args' => $_POST['nic'])); } if (isset($_POST['refresh'])) { + //ui_notify_async("'wrkcmd' => 'netcfg', 'action' => 'refresh'", $_POST['nic']); $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'netcfg', 'action' => 'refresh')); } if (isset($_POST['wifiprofile'])) { switch ($_POST['wifiprofile']['action']) { case 'add': + //ui_notify_async("'wrkcmd' => 'wificfg', 'action' => 'add'", $_POST['wifiprofile']); $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wificfg', 'action' => 'add', 'args' => $_POST['wifiprofile'])); break; case 'edit': + //ui_notify_async("'wrkcmd' => 'wificfg', 'action' => 'edit'", $_POST['wifiprofile']); $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wificfg', 'action' => 'edit', 'args' => $_POST['wifiprofile'])); break; case 'delete': + //ui_notify_async("'wrkcmd' => 'wificfg', 'action' => 'delete'", $_POST['wifiprofile']); $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wificfg', 'action' => 'delete', 'args' => $_POST['wifiprofile'])); break; + case 'connect': + //ui_notify_async("'wrkcmd' => 'wificfg', 'action' => 'connect'", $_POST['wifiprofile']); + $jobID[] = wrk_control($redis, 'newjob', $data = array( 'wrkcmd' => 'wificfg', 'action' => 'connect', 'args' => $_POST['wifiprofile'] )); + break; case 'disconnect': + //ui_notify_async("'wrkcmd' => 'wificfg', 'action' => 'disconnect'", $_POST['wifiprofile']); $jobID[] = wrk_control($redis, 'newjob', $data = array( 'wrkcmd' => 'wificfg', 'action' => 'disconnect', 'args' => $_POST['wifiprofile'] )); break; } } - // if (isset($_POST['wifidelete'])) { - // $jobID[] = wrk_control($redis,'newjob', $data = array( 'wrkcmd' => 'wificfg', 'action' => 'delete', 'args' => $_POST['wifidelete'] )); - // } - if (isset($_POST['wpa_cli'])) { - $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'wificfg', 'action' => 'wpa_cli', 'args' => $_POST['wpa_cli'])); - } } waitSyWrk($redis,$jobID); $template->nics = wrk_netconfig($redis, 'getnics'); $template->wlan_autoconnect = $redis->Get('wlan_autoconnect'); -if ($redis->hExists('wlan_profiles', urldecode($template->uri(4)))) $template->stored = 1; +if ($redis->Exists(urldecode($template->uri(4)))) $template->stored = 1; if (isset($template->action)) { // check if we are into interface details (ex. http://runeaudio/network/edit/eth0) if (isset($template->arg)) { @@ -97,8 +101,10 @@ if ($template->nic->wireless === 1) { $template->wlans = json_decode($redis->get('wlans')); $template->wlan_profiles = new stdClass(); - if ($wlan_profiles = $redis->hGetAll('wlan_profiles')) foreach ($wlan_profiles as $key => $value) { - $template->wlan_profiles->{$key} = json_decode($value); + if ($wlan_profiles = wrk_netconfig($redis, 'getstoredwlans')) { + foreach ($wlan_profiles as $key => $value) { + $template->wlan_profiles->{$key} = json_decode($value); + } } } // we are in the wlan subtemplate (ex. http://runeaudio/network/wlan/....) diff --git a/app/settings_ctl.php b/app/settings_ctl.php index ed09373a..43f9c7b8 100755 --- a/app/settings_ctl.php +++ b/app/settings_ctl.php @@ -147,12 +147,6 @@ $redis->hGet('spotify','enable') == 0 || $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'spotify', 'action' => 'stop')); } } - // ----- C-MEDIA FIX ----- - if (isset($_POST['cmediafix'][1])){ - $redis->get('cmediafix') == 1 || $redis->set('cmediafix', 1); - } else { - $redis->get('cmediafix') == 0 || $redis->set('cmediafix', 0); - } // ----- SYSTEM COMMANDS ----- if (isset($_POST['syscmd'])){ if ($_POST['syscmd'] === 'reboot') $jobID[] = wrk_control($redis, 'newjob', $data = array('wrkcmd' => 'reboot')); @@ -183,7 +177,6 @@ $template->coverart = $redis->get('coverart'); $template->globalrandom = $redis->get('globalrandom'); $template->lastfm = $redis->hGetAll('lastfm'); -$template->cmediafix = $redis->get('cmediafix'); $template->proxy = $redis->hGetAll('proxy'); $template->spotify = $redis->hGetAll('spotify'); $template->hwplatformid = $redis->get('hwplatformid'); diff --git a/app/sources_ctl.php b/app/sources_ctl.php index 06586352..4026d03a 100644 --- a/app/sources_ctl.php +++ b/app/sources_ctl.php @@ -42,7 +42,7 @@ if ($_POST['mount']['wsize'] == '') $_POST['mount']['wsize'] = 17408; if ($_POST['mount']['options'] == '') { if ($_POST['mount']['type'] === 'cifs' OR $_POST['mount']['type'] === 'osx') { - $_POST['mount']['options'] = "cache=none,ro"; + $_POST['mount']['options'] = "cache=none,noserverino,ro"; } else { $_POST['mount']['options'] = "nfsvers=3,ro"; } diff --git a/app/templates/api_lo.php b/app/templates/api_lo.php new file mode 100644 index 00000000..2ff2b278 --- /dev/null +++ b/app/templates/api_lo.php @@ -0,0 +1,8 @@ +error2 = error_get_last(); + +echo json_encode($this); + +?> \ No newline at end of file diff --git a/app/templates/config.php b/app/templates/config.php new file mode 100644 index 00000000..5bba2766 --- /dev/null +++ b/app/templates/config.php @@ -0,0 +1,8 @@ +
+
+
+
+
connecting...
+
+
+ \ No newline at end of file diff --git a/app/templates/credits.php b/app/templates/credits.php index 105ddb43..9dfb4bd2 100755 --- a/app/templates/credits.php +++ b/app/templates/credits.php @@ -3,7 +3,7 @@
- release version: 0.3 (build: buildversion ?>) + release version: release ?> (build: buildversion ?>)

RuneAudio team

@@ -12,11 +12,14 @@ Carmelo San Giovanni (aka Um3ggh1U) - RuneOS distro build & Kernel optimization

Main contributors

- Cristian Pascottini - RuneUI Javascript optimizations
- Valerio Battaglia - RuneUI Javascript optimizations
- Francesco Casarsa - Shairport patch
- Frank Friedmann - RuneOS porting for Cubietruck
- Saman - RuneOS RT Linux kernel for Wolfson Audio Card (RaspberryPi) + Cristian Pascottini - RuneUI Javascript optimizations
+ Valerio Battaglia - RuneUI Javascript optimizations
+ Frank Friedmann (aka hondagx35) - RuneUI/RuneOS PHP backend code debug, refactoring of network management, RuneOS porting for Cubietruck
+ Kevin Welsh (aka kdubious) - RuneUI/RuneOS Frontend & backend development
+ Andrea Rizzato (aka AandreR) - RuneUI/RuneOS PHP backend code debug, integration of Wolfson Audio Card
+ Saman - RuneOS RT Linux kernel for Wolfson Audio Card (RaspberryPi)
+ Daniele Scasciafratte (aka Mte90) - RuneUI Firefox integration
+ Francesco Casarsa (aka CAS)- Shairport patch

Support us!

diff --git a/app/templates/dev.php b/app/templates/dev.php index 585fc5b7..f3bcc7ef 100755 --- a/app/templates/dev.php +++ b/app/templates/dev.php @@ -50,6 +50,13 @@ Current detected HW fingerprint.
+
+ +
+ + Current Git branch. +
+
diff --git a/app/templates/footer.php b/app/templates/footer.php index 612cead5..8a362db8 100644 --- a/app/templates/footer.php +++ b/app/templates/footer.php @@ -16,11 +16,14 @@
+section !== 'config'): ?>
section == 'dev') { ?> class="hide">
connecting...
+ + section == 'debug'): ?> @@ -36,12 +39,34 @@ +section !== 'config'): ?> dev === '0'):?> + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/app/templates/header.php b/app/templates/header.php index d5bbc24c..794c5c02 100644 --- a/app/templates/header.php +++ b/app/templates/header.php @@ -6,7 +6,7 @@ - + @@ -67,25 +67,34 @@ */ --> \ No newline at end of file + diff --git a/app/templates/network.php b/app/templates/network.php index 970d44e5..b01e4d88 100644 --- a/app/templates/network.php +++ b/app/templates/network.php @@ -11,7 +11,11 @@

List of active network interfaces. Click on an entry to configure the corresponding connection.

nics as $key => $value): ?> -

   [ip !== null): ?>ip ?>no IP assigned]

+ wireless !== 1): ?> +

   [ip !== null): ?>ip ?>no IP assigned]

+ +

   [ip !== null): ?>ip ?>] [currentssid) ?>no IP assigned]

+
\ No newline at end of file diff --git a/app/templates/network_edit.php b/app/templates/network_edit.php index 39c2f1ba..4ca640a9 100644 --- a/app/templates/network_edit.php +++ b/app/templates/network_edit.php @@ -3,6 +3,7 @@ nic->wireless === 1): ?> Wi-Fi networks in range The list of available Wi-Fi networks is automatically refreshed while you are on this page (so don't forget it open in your browser to avoid unnecessary system load). + Click on an entry to connect or to generate a new profile.

scanning for networks...

@@ -27,6 +28,7 @@
+ nic->wireless === 0): ?>
@@ -102,6 +104,16 @@ +
+ +
+ + If you experience problems, set this to YES. +
+
Cancel @@ -109,6 +121,7 @@
+ - \ No newline at end of file + diff --git a/app/templates/playback.php b/app/templates/playback.php index 01422b8b..0ae52849 100755 --- a/app/templates/playback.php +++ b/app/templates/playback.php @@ -92,9 +92,9 @@ 2143 entries
-
    - -
+ + +
diff --git a/app/templates/settings.php b/app/templates/settings.php index e6c5089a..8b1c5fb2 100755 --- a/app/templates/settings.php +++ b/app/templates/settings.php @@ -21,6 +21,9 @@
+ Set your reference time sync server (NTP server).
@@ -106,6 +109,10 @@ + + + + @@ -292,28 +299,6 @@ -
-
- Compatibility fixes -

For people suffering problems with some receivers and DACs.

-
- -
- - For those who have a CM6631 receiver and experiment issues (noise, crackling) between tracks with different sample rates and/or bit depth.
- A "dirty" fix that should avoid the problem, do NOT use if everything works normally.
-
-
-
-
- -
-
-
-
Backup / Restore configuration @@ -357,7 +342,7 @@ - "}return g}function populateDB(a){var b=a.data||"",c=a.path||"",d=a.uplevel||0,e=a.keyword||"",f=a.plugin||"",g=a.querytype||"",h=a.args||"",i="",j=0,k=[];if(""!==f){if("Spotify"===f){for($("#database-entries").removeClass("hide"),$("#db-level-up").removeClass("hide"),$("#home-blocks").addClass("hide"),c&&(GUI.currentpath=c),document.getElementById("database-entries").innerHTML="",b="tracks"===g?b.tracks:b.playlists,j=0;k=b[j];j+=1)i+=parseResponse({inputArr:k,respType:"Spotify",i:j,querytype:g,inpath:h});document.getElementById("database-entries").innerHTML=i}if("Dirble"===f){for($("#database-entries").removeClass("hide"),$("#db-level-up").removeClass("hide"),$("#home-blocks").addClass("hide"),c&&(GUI.currentpath=c),document.getElementById("database-entries").innerHTML="",j=0;k=b[j];j+=1)i+=parseResponse({inputArr:k,respType:"Dirble",i:j,querytype:g});document.getElementById("database-entries").innerHTML=i}if("Jamendo"===f){for($("#database-entries").removeClass("hide"),$("#db-level-up").removeClass("hide"),$("#home-blocks").addClass("hide"),c&&(GUI.currentpath=c),document.getElementById("database-entries").innerHTML="",j=0;k=b[j];j+=1)i+=parseResponse({inputArr:k,respType:"Jamendo",i:j,querytype:g});document.getElementById("database-entries").innerHTML=i}}else{if(""===c&&""===e)return void renderLibraryHome();if($("#database-entries").removeClass("hide"),$("#db-level-up").removeClass("hide"),$("#home-blocks").addClass("hide"),c&&(GUI.currentpath=c),document.getElementById("database-entries").innerHTML="",""!==e){var l=b.length?b.length:"0",m=1===b.length?"":"s";$("#db-level-up").addClass("hide"),$("#db-search-results").removeClass("hide").html('back')}for(j=0;k=b[j];j+=1)i+=parseResponse({inputArr:k,respType:"db",i:j,inpath:c});"Webradio"===c&&(i+='
  • add newadd a webradio to your library
  • '),document.getElementById("database-entries").innerHTML=i}var n=$("span","#db-currentpath");if(n.html("album"===GUI.browsemode?"Albums"===c?c:"Albums/"+c:"artist"===GUI.browsemode?"Artists"===c?c:"Artists/"+c:"genre"===GUI.browsemode?"Genres"===c?c:"Genres/"+c:c),$("#db-homeSetup").addClass("hide"),d){var o=GUI.currentDBpos[GUI.currentDBpos[10]];$("#db-"+o).addClass("active"),customScroll("db",o,0)}else customScroll("db",0,0);loadingSpinner("db","hide")}function getDB(a){var b=a.cmd||"browse",c=a.path||"",d=a.browsemode||"file",e=a.uplevel||"",f=a.plugin||"",g=a.querytype||"",h=a.args||"";if(loadingSpinner("db"),GUI.browsemode=d,""!==f)"Spotify"===f?$.post("/db/?cmd=spotify",{plid:h},function(a){populateDB({data:a,path:c,plugin:f,querytype:g,uplevel:e,args:h})},"json"):"Dirble"===f?$.post("/db/?cmd=dirble",{querytype:""===g?"categories":g,args:h},function(a){populateDB({data:a,path:c,plugin:f,querytype:g,uplevel:e})},"json"):"Jamendo"===f&&$.post("/db/?cmd=jamendo",{querytype:""===g?"radio":g,args:h},function(a){populateDB({data:a.results,path:c,plugin:f,querytype:g})},"json");else if("search"===b){var i=$("#db-search-keyword").val();$.post("/db/?querytype="+GUI.browsemode+"&cmd=search",{query:i},function(a){populateDB({data:a,path:c,uplevel:e,keyword:i})},"json")}else"browse"===b?$.post("/db/?cmd=browse",{path:c,browsemode:GUI.browsemode},function(a){populateDB({data:a,path:c,uplevel:e})},"json"):(loadingSpinner("db","hide"),$.post("/db/?cmd="+b,{path:c,querytype:g},function(){},"json"))}function onreleaseKnob(a){if("stop"!==GUI.state&&""!==GUI.state)if("radio"!==GUI.stream){window.clearInterval(GUI.currentKnob);var b=Math.floor(a*parseInt(GUI.json.time)/1e3);sendCmd("seek "+GUI.json.song+" "+b),$("#time").val(a),$("#countdown-display").countdown("destroy"),$("#countdown-display").countdown({since:-b,compact:!0,format:"MS"})}else $("#time").val(0).trigger("change")}function commandButton(a){var b,c=a.data("cmd");if("stop"===c)a.addClass("btn-primary"),$("#play").removeClass("btn-primary"),$("#section-index").length&&(refreshTimer(0,0,"stop"),window.clearInterval(GUI.currentKnob),$(".playlist").find("li").removeClass("active"),$("#total").html("00:00"));else{if("play"===c){var d=GUI.state;return"play"===d?(b="pause",$("#section-index").length&&$("#countdown-display").countdown("pause")):"pause"===d?(b="play",$("#section-index").length&&$("#countdown-display").countdown("resume")):"stop"===d&&(b="play",$("#section-index").length&&$("#countdown-display").countdown({since:0,compact:!0,format:"MS"})),window.clearInterval(GUI.currentKnob),void sendCmd(b)}if("previous"===c||"next"===c)$("#section-index").length&&($("#countdown-display").countdown("pause"),window.clearInterval(GUI.currentKnob));else if(a.hasClass("btn-volume")){var e,f=parseInt($("#volume").val());return null===GUI.volume&&(GUI.volume=f),"volumedn"===c&&parseInt(GUI.volume)>0?(e=parseInt(GUI.volume)-1,GUI.volume=e,$("#volumemute").removeClass("btn-primary")):"volumeup"===c&&parseInt(GUI.volume)<100?(e=parseInt(GUI.volume)+1,GUI.volume=e,$("#volumemute").removeClass("btn-primary")):"volumemute"===c&&(0!==f?(GUI.volume=f,a.addClass("btn-primary"),e=0):(a.removeClass("btn-primary"),setvol(GUI.volume))),void(e>=0&&100>=e&&sendCmd("setvol "+e))}}a.hasClass("btn-toggle")?(b=c+(a.hasClass("btn-primary")?" 0":" 1"),a.toggleClass("btn-primary")):b=c,sendCmd(b)}function libraryHome(a){GUI.libraryhome=a[0],renderLibraryHome()}function listWLANs(a){var b="",c="",d="",e=a[0];$.each(e,function(a){b+='

    ',0!==e[a].connected&&(b+=''),1===e[a].storedprofile&&"on"===e[a].encryption?b+='':"on"===e[a].encryption?b+='':1!==e[a].storedprofile&&(b+=''),b+=""+e[a].ESSID+"

    ",1===e[a].storedprofile?d+=b:c+=b,b=""}),""===c&&(c='

    scanning for networks...

    '),document.getElementById("wifiNetworks").innerHTML=c,document.getElementById("wifiStored").innerHTML=d,$.ajax({url:"/command/?cmd=wifiscan"})}function nicsDetails(a){var b="",c=a[0];$.each(c,function(a){a===$("#nic-details").data("name")&&(b+="Name:"+a+"",b+="Type:wireless","off/any"===c[a].currentssid?b+='Status:no network connected':(b+='Status:connected',b+="Associated SSID:"+c[a].currentssid+""),b+="Assigned IP:"+(null!==c[a].ip?""+c[a].ip+"":"none")+"",b+="Speed:"+(null!==c[a].speed?c[a].speed:"unknown")+"")}),$("#nic-details tbody").html(b)}function playbackChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode,reconnectOnChannelUnavailableInterval:5e3});a.onmessage=renderUI,a.onstatuschange=function(a){2===a?($("#loader").addClass("hide"),sendCmd("renderui")):0===a&&toggleLoader()},a.addChannel("playback"),a.connect()}function queueChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=getPlaylist,a.addChannel("queue"),a.connect()}function libraryChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=libraryHome,a.addChannel("library"),a.connect()}function notifyChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=renderMSG,a.addChannel("notify"),a.connect()}function wlansChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=listWLANs,a.addChannel("wlans"),a.connect(),$.ajax({url:"/command/?cmd=wifiscan"})}function nicsChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=nicsDetails,a.addChannel("nics"),a.connect()}function overlayTrigger(a){function b(){if(c.hasClass("open")){c.removeClass("open"),c.addClass("closed")}else if(c.hasClass("closed")&&(c.addClass("open"),"#overlay-social"===a)){var b="https://twitter.com/home?status=Listening+to+"+GUI.json.currentsong.replace(/\s+/g,"+")+"+by+"+GUI.json.currentartist.replace(/\s+/g,"+")+"+on+%40RuneAudio+http%3A%2F%2Fwww.runeaudio.com%2F+%23nowplaying",d="https://www.facebook.com/sharer.php?u=http%3A%2F%2Fwww.runeaudio.com%2F&display=popup",e="https://plus.google.com/share?url=http%3A%2F%2Fwww.runeaudio.com%2F";$("#urlTwitter").attr("href",b),$("#urlFacebook").attr("href",d),$("#urlGooglePlus").attr("href",e)}}var c=$(a),d=$(a+"-open"),e=$(a+"-close");transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},d.click(function(){b()}),e.click(function(){b()})}function getHiddenProp(){var a=["webkit","moz","ms","o"];if("hidden"in document)return"hidden";for(var b=0;bback')):(a("#pl-count").removeClass("hide"),a("#pl-filter-results").addClass("hide").html(""))}),a("#pl-filter-results").click(function(){a(this).addClass("hide"),a("#pl-count").removeClass("hide"),a(this).hasClass("back-to-queue")?(a(".playlist").addClass("hide"),getPlaylistCmd(),a("#pl-currentpath").addClass("hide"),a("#pl-manage").removeClass("hide")):(a("li","#playlist-entries").each(function(){var b=a(this);b.show()}),a("#pl-currentpath").removeClass("hide"),a("#pl-filter").val("")),customScroll("pl",parseInt(GUI.json.song),500)}),a("#pl-manage-list").click(function(){getPlaylists()}),a("#modal-pl-save-btn").click(function(){var b=a("#pl-save-name").val();sendCmd('save "'+b+'"')}),a("#pl-editor").on("click",".pl-action",function(b){b.preventDefault();var c=a(this).parent().attr("data-path");GUI.DBentry[0]=c}),a("#pl-rename-button").click(function(){var b=a("#pl-rename-oldname").text(),c=a("#pl-rename-name").val();sendCmd('rename "'+b+'" "'+c+'"'),getPlaylists()});var e=document.getElementById("playlist-entries");new Sortable(e,{ghostClass:"sortable-ghost",onUpdate:function(a){sortOrder(a.item.getAttribute("id"))}}),a("a","#open-panel-sx").click(function(){a("#open-panel-sx").hasClass("active")&&customScroll("pl",parseInt(GUI.json.song),500)}).on("shown.bs.tab",function(){customScroll("db",GUI.currentDBpos[GUI.currentDBpos[10]],0)}),a("#home-blocks").on("click",".home-block",function(b){if(a(this).hasClass("inactive"))a("#overlay-playsource-open").trigger("click");else if(a(this).is("#home-spotify-switch"))a("#overlay-playsource-open").trigger("click");else if(a(b.target).is("span.block-remove")){var c=a(this).attr("id");c=c.replace("home-bookmark-","");var d=a(this).find("h3").text();a.post("/db/?cmd=bookmark",{id:c,name:d})}else++GUI.currentDBpos[10],getDB({browsemode:a(this).data("browsemode"),path:a(this).data("path"),uplevel:0,plugin:a(this).data("plugin")})}),a("#db-homeSetup").click(function(){var b=a(this);b.hasClass("btn-primary")?(b.removeClass("btn-primary").addClass("btn-default"),a(".home-block-remove").remove()):(b.removeClass("btn-default").addClass("btn-primary"),a(".home-block.home-bookmark").append('
    ×
    '))});var f=a("#database-entries");f.on("click","li",function(b){var c="",d="",e=a(this);if(a(b.target).hasClass("db-action"))b.preventDefault(),c="spotify-track"===e.data("type")?e.data("plid")+"-"+e.data("path"):e.data("path"),GUI.DBentry[0]=c;else if(a("li.active","#database-entries").removeClass("active"),e.addClass("active"),e.hasClass("db-folder")){c=e.data("path"),e.hasClass("db-album")?""!==c?getDB({path:c,uplevel:0,browsemode:"album"}):(c=GUI.currentDBpath[GUI.currentDBpos[10]-1],getDB({path:c,uplevel:0,browsemode:"albumfilter"})):e.hasClass("db-artist")?getDB({path:c,uplevel:0,browsemode:"artist"}):e.hasClass("db-genre")?getDB({path:c,uplevel:0,browsemode:"genre"}):e.hasClass("db-spotify")?(c=GUI.currentpath+"/"+e.find("span").text(),getDB({path:c,plugin:"Spotify",args:e.data("path").toString(),querytype:"tracks"}),GUI.plugin="Spotify"):e.hasClass("db-dirble")?(c=GUI.currentpath+"/"+e.find("span").text(),getDB({path:c,plugin:"Dirble",querytype:"stations",args:e.data("path")}),GUI.plugin="Dirble"):e.hasClass("db-jamendo")||(d=e.data("browsemode"),getDB({path:c,uplevel:0,browsemode:d}));var f=e.attr("id");f=f.replace("db-",""),GUI.currentDBpos[GUI.currentDBpos[10]]=f,GUI.currentDBpath[GUI.currentDBpos[10]]=c,++GUI.currentDBpos[10]}else e.hasClass("db-webradio-add")&&a("#modal-webradio-add").modal()}),f.on("dblclick","li",function(b){var c=a(this);if(!a(b.target).hasClass("db-action")){a("li.active","#database-entries").removeClass("active"),c.addClass("active");var d=c.data("path");c.hasClass("db-spotify")?(d=c.attr("data-plid")+"-"+c.attr("data-path"),getDB({cmd:"spaddplay",path:d,querytype:"spotify-track"})):(d=c.hasClass("db-dirble")?d.split(" | ")[1]:d,getDB({cmd:"addplay",path:d}))}}),a("#db-level-up").click(function(){--GUI.currentDBpos[10];var a=GUI.currentpath;if(0===GUI.currentDBpos[10])a="";else if("file"===GUI.browsemode){var b=a.lastIndexOf("/");a=-1!==b?a.slice(0,b):""}else"album"===GUI.browsemode?(a=GUI.currentDBpath[GUI.currentDBpos[10]-1],""===a?a="Albums":GUI.browsemode="artist"):"artist"===GUI.browsemode?(a=GUI.currentDBpath[GUI.currentDBpos[10]-1],""===a?a="Artists":GUI.browsemode="genre"):"genre"===GUI.browsemode?a="Genres":"albumfilter"===GUI.browsemode&&(GUI.browsemode="artist",a=GUI.currentDBpath[GUI.currentDBpos[10]-1]);getDB({browsemode:GUI.browsemode,path:a,plugin:GUI.plugin,uplevel:1}),GUI.plugin=""}),a("#db-search-results").click(function(){a(this).addClass("hide"),a("#db-level-up").removeClass("hide"),getDB({path:GUI.currentpath})}),a("a",".context-menu").click(function(){var b=a(this).data("cmd"),c=a(this).data("type"),d=GUI.DBentry[0];switch(GUI.DBentry[0]="",b){case"pl-add":sendCmd('load "'+d+'"');break;case"pl-replace":sendCmd("clear"),sendCmd('load "'+d+'"');break;case"pl-rename":a("#modal-pl-rename").modal(),a("#pl-rename-oldname").text(d);break;case"pl-rm":a.ajax({url:"/command/?cmd=rm%20%22"+d+"%22",success:function(a){getPlaylists(a)}});break;case"wradd":d=d.split(" | ")[1],getDB({cmd:"add",path:d});break;case"wraddplay":d=d.split(" | ")[1],getDB({cmd:"addplay",path:d});break;case"wraddreplaceplay":d=d.split(" | ")[1],getDB({cmd:"addreplaceplay",path:d});break;case"wredit":a("#modal-webradio-edit").modal(),a.post("/db/?cmd=readradio",{filename:d},function(b){var c=a("#webradio-edit-name");c.val(b.name),c.data("file-name",b.name),a("#webradio-edit-url").val(b.url)},"json");break;case"wrdelete":a("#modal-webradio-delete").modal(),a("#webradio-delete-name").text(d.replace("Webradio/",""));break;case"wrsave":var e=d.split(" | ");a.post("/db/?cmd=addradio",{"radio[label]":e[0],"radio[url]":e[1]});break;default:getDB({cmd:b,path:d,browsemode:GUI.browsemode,querytype:c})}}),a("#webradio-add-button").click(function(){var b=a("#webradio-add-name").val(),c=a("#webradio-add-url").val();""===b||""===c?renderMSG([{title:"Missing fields",text:"Please fill both fields to continue",icon:"fa fa-warning"}]):(a.post("/db/?cmd=addradio",{"radio[label]":b,"radio[url]":c},function(){},"json"),a("#modal-webradio-add").modal("hide"),a("#webradio-add-name").val(""),a("#webradio-add-url").val(""))}),a("#webradio-edit-button").click(function(){var b=a("#webradio-edit-name");a.post("/db/?cmd=editradio",{"radio[newlabel]":b.val(),"radio[label]":b.data("file-name"),"radio[url]":a("#webradio-edit-url").val()},function(){},"json")}),a("#webradio-delete-button").click(function(){var b=a("#webradio-delete-name").text();a.post("/db/?cmd=deleteradio",{"radio[label]":b},function(){},"json")}),a("#db-firstPage").click(function(){a.scrollTo(0,500)}),a("#db-prevPage").click(function(){var b="-="+a(window).height()+"px";a.scrollTo(b,500)}),a("#db-nextPage").click(function(){var b="+="+a(window).height()+"px";a.scrollTo(b,500)}),a("#db-lastPage").click(function(){a.scrollTo("100%",500)}),a("#pl-firstPage").click(function(){a.scrollTo(0,500)}),a("#pl-prevPage").click(function(){var b=a(window).scrollTop(),c=b-a(window).height();a.scrollTo(c,500)}),a("#pl-nextPage").click(function(){var b=a(window).scrollTop(),c=b+a(window).height();a.scrollTo(c,500)}),a("#pl-lastPage").click(function(){a.scrollTo("100%",500)});var g=document.location.toString();g.match("#")&&a('#menu-bottom a[href="/#'+g.split("#")[1]+'"]').tab("show"),a("#menu-bottom a").on("shown",function(a){history.pushState?history.pushState(null,null,a.target.hash):window.location.hash=a.target.hash}).on("click",function(){a("#overlay-social").hasClass("open")&&a(".overlay-close").trigger("click")}),a(".ttip").length&&a(".ttip").tooltip(),FastClick.attach(document.body),a("#syscmd-poweroff").click(function(){a.post("/settings/",{syscmd:"poweroff"}),toggleLoader()}),a("#syscmd-reboot").click(function(){a.post("/settings/",{syscmd:"reboot"}),toggleLoader()}),overlayTrigger("#overlay-social"),overlayTrigger("#overlay-playsource"),a("#playsource-mpd").click(function(){a(this).hasClass("inactive")&&(a.ajax({url:"/command/?switchplayer=MPD"}),a("#overlay-playsource-close").trigger("click"))}),a("#playsource-spotify").click(function(){a(this).hasClass("inactive")&&("1"===GUI.libraryhome.Spotify?(a.ajax({url:"/command/?switchplayer=Spotify"}),a("#overlay-playsource-close").trigger("click")):new PNotify({title:"Spotify not enabled",text:"Enable and configure it under the Settings screen",icon:"fa fa-exclamation-circle"}))})}:function(a){"use strict";if(GUI.mode=checkWebSocket(),playbackChannel(),PNotify.prototype.options.styling="fontawesome",PNotify.prototype.options.stack.dir1="up",PNotify.prototype.options.stack.dir2="left",PNotify.prototype.options.stack.firstpos1=90,PNotify.prototype.options.stack.firstpos2=50,PNotify.prototype.options.stack.spacing1=10,PNotify.prototype.options.stack.spacing2=10,notifyChannel(),a(".btn-cmd").click(function(){var b=a(this);commandButton(b)}),a("#syscmd-poweroff").click(function(){a.post("/settings/",{syscmd:"poweroff"}),toggleLoader()}),a("#syscmd-reboot").click(function(){a.post("/settings/",{syscmd:"reboot"}),toggleLoader()}),a(".selectpicker").selectpicker(),a("#section-sources").length&&("nfs"===a("#mount-type").val()&&a("#mount-cifs").addClass("disabled").children(".disabler").removeClass("hide"),a("#mount-type").change(function(){"cifs"===a(this).val()||"osx"===a(this).val()?a("#mount-cifs").removeClass("disabled").children(".disabler").addClass("hide"):a("#mount-cifs").addClass("disabled").children(".disabler").removeClass("hide")}),a("#nas-guest").change(function(){a(this).prop("checked")?a("#mount-auth").addClass("disabled").children(".disabler").removeClass("hide"):a("#mount-auth").removeClass("disabled").children(".disabler").addClass("hide")}),a("#nas-advanced").change(function(){a(this).prop("checked")?a("#mount-advanced-config").removeClass("hide"):a("#mount-advanced-config").addClass("hide")}),a("#show-mount-advanced-config").click(function(b){b.preventDefault(),a(this).hasClass("active")?(a("#mount-advanced-config").toggleClass("hide"),a(this).removeClass("active"),a(this).find("i").removeClass("fa fa-minus-circle").addClass("fa fa-plus-circle"),a(this).find("span").html("show advanced options")):(a("#mount-advanced-config").toggleClass("hide"),a(this).addClass("active"),a(this).find("i").removeClass("fa fa-plus-circle").addClass("fa fa-minus-circle"),a(this).find("span").html("hide advanced options"))}),a("#usb-mount-list a").click(function(){var b=a(this).data("mount");a("#usb-umount-name").html(b),a("#usb-umount").val(b)})),a("#section-settings").length&&(a("#airplay").change(function(){a(this).prop("checked")?(a("#airplayName").removeClass("hide"),a("#airplayBox").addClass("boxed-group")):(a("#airplayName").addClass("hide"),a("#airplayBox").removeClass("boxed-group"))}),a("#scrobbling-lastfm").change(function(){a(this).prop("checked")?(a("#lastfmAuth").removeClass("hide"),a("#lastfmBox").addClass("boxed-group")):(a("#lastfmAuth").addClass("hide"),a("#lastfmBox").removeClass("boxed-group"))}),a("#proxy").change(function(){a(this).prop("checked")?(a("#proxyAuth").removeClass("hide"),a("#proxyBox").addClass("boxed-group")):(a("#proxyAuth").addClass("hide"),a("#proxyBox").removeClass("boxed-group"))}),a("#dlna").change(function(){a(this).prop("checked")?(a("#dlnaName").removeClass("hide"),a("#dlnaBox").addClass("boxed-group")):(a("#dlnaName").addClass("hide"),a("#dlnaBox").removeClass("boxed-group"))}),a("#spotify").change(function(){a(this).prop("checked")?(a("#spotifyAuth").removeClass("hide"),a("#spotifyBox").addClass("boxed-group")):(a("#spotifyAuth").addClass("hide"),a("#spotifyBox").removeClass("boxed-group"))})),a("#section-network").length){var b=a("#network-manual-config");"0"===a("#dhcp").val()&&b.removeClass("hide"),a("#dhcp").change(function(){"0"===a(this).val()?b.removeClass("hide"):b.addClass("hide")});var c=a("#wifi-security-key");"open"!==a("#wifi-security").val()&&c.removeClass("hide"),a("#wifi-security").change(function(){"open"!==a(this).val()?c.removeClass("hide"):c.addClass("hide")}),a("#wifiNetworks").length&&(wlansChannel(),nicsChannel()),a("#wifiProfiles").change(function(){a(this).prop("checked")?a("#wifiProfilesBox").addClass("hide"):a("#wifiProfilesBox").removeClass("hide")})}if(a("#section-mpd").length&&(a("#audio-output-interface").change(function(){renderMSG([{title:"Switching audio output",text:"Please wait for the config update...",icon:"fa fa-cog fa-spin",delay:5e3}]);var b=a(this).val();a.ajax({type:"POST",url:"/mpd/",data:{ao:b}})}),a(".manual-edit-confirm").find(".btn-primary").click(function(){a("#mpdconf_editor").removeClass("hide"),a("#manual-edit-warning").addClass("hide")})),a("#section-debug").length){ZeroClipboard.config({swfPath:"/assets/js/vendor/ZeroClipboard.swf"});var d=new ZeroClipboard(document.getElementById("copy-to-clipboard"));d.on("ready",function(){d.on("aftercopy",function(){new PNotify({title:"Copied to clipboard",text:"The debug output was copied successfully in your clipboard.",icon:"fa fa-check"})})})}}); \ No newline at end of file +function sendCmd(a){var b=new XMLHttpRequest;b.open("GET","/command/?cmd="+a,!0),b.onreadystatechange=function(){4===this.readyState&&this.status>=200&&this.status<400},b.send(),b=null}function checkWebSocket(){return window.WebSocket?"websocket":"longpolling"}function checkWorkers(){return window.Worker&&window.Blob||Modernizr.webworkers&&Modernizr.blobconstructor?!0:!1}function parsePath(a){var b=a&&a.length?a.lastIndexOf("/"):0,c="";return b&&-1!==b&&(c=a.slice(0,b)),c}function refreshTimer(a,b,c){var d=$("#countdown-display");d.countdown("destroy"),d.countdown({since:"stop"!==c||void 0!==c?-a:0,compact:!0,format:"MS"}),"play"!==c&&d.countdown("pause")}function refreshKnob(){window.clearInterval(GUI.currentKnob);var a=10*parseInt(GUI.json.song_percent),b=parseInt(GUI.json.time),c=parseInt(1e3/b),d=$("#time");d.val(a,!1).trigger("update"),"play"===GUI.state&&(GUI.currentKnob=setInterval(function(){a+="visible"!==GUI.visibility?c:1,d.val(a,!1).trigger("update")},b))}function timeConvert(a){var b=Math.floor(a/60);a-=60*b;var c=10>b?"0"+b:b,d=10>a?"0"+a:a;return c+":"+d}function timeConvert2(a){var b=Math.floor(a/3600),c=Math.floor((a-3600*b)/60);return a=Math.floor(a-3600*b-60*c),b>0?(10>b&&(b="0"+b),b+=":"):b="",10>c&&(c="0"+c),10>a&&(a="0"+a),b+c+":"+a}function countdownRestart(a){var b=$("#countdown-display").countdown("destroy");b.countdown({since:-a,compact:!0,format:"MS"})}function setvol(a){$("#volume").val(a,!1).trigger("update"),GUI.volume=a,$("#volumemute").removeClass("btn-primary"),sendCmd("setvol "+a)}function volumeStepCalc(a){var b=0;GUI.volume=parseInt($("#volume").val());var c=function(){b++,"up"===a?GUI.stepVolumeDelta=parseInt(GUI.volume)+b:"dn"===a&&(GUI.stepVolumeDelta=parseInt(GUI.volume)-b),$("#volume").val(GUI.stepVolumeDelta).trigger("change")};c(),GUI.stepVolumeInt=window.setInterval(function(){c()},200)}function volumeStepSet(){window.clearInterval(GUI.stepVolumeInt),setvol(GUI.stepVolumeDelta)}function setQueuePos(){0!==queueTracks.length&&GUI.currentqueuepos&&GUI.currentqueuepos!==parseInt(GUI.json.song)&&(queueTracks[GUI.currentqueuepos].current=!1,GUI.currentqueuepos=parseInt(GUI.json.song),queueTracks[GUI.currentqueuepos].current=!0,m.redraw())}function customScroll(a,b,c){"undefined"==typeof c&&(c=500);var d=parseInt($(window).height()/2),e=$(window).scrollTop(),f=0,g=0;"db"===a?(f=parseInt(b*listEntryHeight-d),g=f):"pl"===a&&0!==queueTracks.length&&(f=parseInt((b+2)*listEntryHeight-d),g=Math.abs(f-e),g=(f>e?"+":"-")+"="+g+"px"),isCustomScroll=!0,$.scrollTo(f>0?g:0,c,{onAfter:function(){m.redraw(),isCustomScroll=!1}})}function randomScrollPL(){var a=$(".playlist li").size(),b=1+Math.floor(Math.random()*a);customScroll("pl",b)}function randomScrollDB(){var a=$(".database li").size(),b=1+Math.floor(Math.random()*a);customScroll("db",b)}function toggleLoader(a){"close"===a?$("#loader").addClass("hide"):$("#section-dev").length?($("#loader").addClass("hide"),new PNotify({title:"Warning",text:"The loading layer (spinning arrows) points to a socket error",icon:"fa fa-exclamation-circle"})):$("#loader").removeClass("hide")}function customNotify(a){"kernelswitch"===a.custom&&(void 0!==GUI.noticeUI.kernelswitch&&GUI.noticeUI.kernelswitch.remove(),GUI.noticeUI.kernelswitch=new PNotify({title:"title"in a?a.title:"[missing title]",text:"text"in a?a.text:"[missing text]",icon:"fa fa-refresh",hide:!1,confirm:{confirm:!0,buttons:[{text:a.btntext,addClass:"btn-default btn-block uppercase",click:function(){$.post("/settings/",{syscmd:"reboot"}),toggleLoader()}},{text:"Cancel",addClass:"hide"}]},buttons:{closer:!1,sticker:!1}}))}function renderMSG(a){var b=a[0];if("custom"in b&&null!==b.custom)return void customNotify(b);var c={title:"title"in b?b.title:"[missing title]",text:"text"in b?b.text:"[missing text]",icon:void 0===b.icon?"fa fa-check":b.icon,opacity:void 0===b.opacity?.9:b.opacity,hide:void 0===b.hide&&void 0===b.permanotice,buttons:{closer:void 0===b.permanotice,sticker:void 0===b.permanotice},delay:void 0===b.delay?8e3:b.delay,mouse_reset:!1};"permanotice"in b?void 0===GUI.noticeUI[b.permanotice]?GUI.noticeUI[b.permanotice]=new PNotify(c):"permaremove"in b?(GUI.noticeUI[b.permanotice].remove(),GUI.noticeUI[b.permanotice]=void 0):GUI.noticeUI[b.permanotice].open():new PNotify(c)}function sortOrder(a){var b=$("#"+a).index();a=parseInt(a.replace("pl-","")),sendCmd("moveid "+a+" "+b)}function loadingSpinner(a,b){"hide"===b?("db"===a&&$("#spinner-db").addClass("hide"),"pl"===a&&$("#spinner-pl").addClass("hide")):("db"===a&&$("#spinner-db").removeClass("hide"),"pl"===a&&$("#spinner-pl").removeClass("hide"))}function setPlaybackSource(){var a=GUI.libraryhome.ActivePlayer;$("#overlay-playsource-open button").text(a),$("#overlay-playsource a").addClass("inactive");var b=a.toLowerCase();$("#playsource-"+b).removeClass("inactive"),"Spotify"===a||"Airplay"===a?($("#volume").trigger("configure",{readOnly:!0,fgColor:"#1A242F"}).css({color:"#1A242F"}),$(".volume button").prop("disabled",!0),$("#single").addClass("disabled")):($("#volume").trigger("configure",{readOnly:!1,fgColor:"#0095D8"}).css({color:"#0095D8"}),$(".volume button").prop("disabled",!1),$("#single").removeClass("disabled")),$("#playlist-entries").removeClass(function(a,b){return(b.match(/(^|\s)playlist-\S+/g)||[]).join(" ")}).addClass("playlist-"+b),$("#pl-manage").removeClass(function(a,b){return(b.match(/(^|\s)pl-manage-\S+/g)||[]).join(" ")}).addClass("pl-manage-"+b)}function chkKey(a){return void 0!==a&&""!==a}function renderLibraryHome(){loadingSpinner("db"),$("#database-entries").addClass("hide"),$("#db-level-up").addClass("hide"),$("#db-homeSetup").removeClass("hide").removeClass("btn-primary").addClass("btn-default"),$("#home-blocks").removeClass("hide");var a=GUI.libraryhome,b=0,c="",d='
    ',e="
    ",f="",g="",h="Spotify"===a.ActivePlayer||"Airplay"===a.ActivePlayer;for(c='

    Browse your library

    ',setPlaybackSource(),h&&(f=" inactive"),b=0;bookmark=a.bookmarks[b];b+=1)c+=d+'

    '+bookmark.name+"

    bookmark
    "+e;chkKey(a.networkMounts)&&(c+=0===a.networkMounts?h?d+'

    Network mounts (0)

    network attached storages
    '+e:d+'

    Network mounts (0)

    click to add some
    '+e:d+'

    Network mounts ('+a.networkMounts+")

    network attached storages
    "+e),chkKey(a.localStorages)&&(c+=0===a.localStorages?"":d+'

    LocalStorage ('+a.localStorages+")

    locally stored music
    "+e),chkKey(a.USBMounts)&&(c+=0===a.USBMounts?h?d+'

    USB storage (0)

    no USB storage plugged
    '+e:d+'

    USB storage (0)

    no USB storage plugged
    '+e:d+'

    USB storage ('+a.USBMounts+")

    USB attached drives
    "+e),chkKey(a.webradio)&&(c+=0===a.webradio?h?d+'

    My Webradios (0)

    webradio local playlists
    '+e:d+'

    My Webradios (0)

    click to add some
    '+e:d+'

    My Webradios ('+a.webradio+")

    webradio local playlists
    "+e),chkKey(a.Spotify)&&(c+="0"===a.Spotify?d+'

    Spotify

    click to configure
    '+e:"Spotify"!==a.ActivePlayer?d+'

    Spotify

    click to switch renderer
    '+e:d+'

    Spotify

    music for everyone
    '+e),chkKey(a.Dirble)&&(c+=d+'

    Dirble ('+a.Dirble+")

    radio stations open directory
    "+e),c+=d+'

    Jamendo

    world\'s largest platform for free music
    '+e,c+=d+'

    Albums

    browse MPD database by album
    '+e,c+=d+'

    Artists

    browse MPD database by artist
    '+e,c+=d+'

    Genres

    browse MPD database by genre
    '+e,c+="",document.getElementById("home-blocks").innerHTML=c,loadingSpinner("db","hide"),$("span","#db-currentpath").html("")}function refreshState(){var a=GUI.state;if("play"===a?($("#play").addClass("btn-primary"),$("i","#play").removeClass("fa fa-pause").addClass("fa fa-play"),$("#stop").removeClass("btn-primary")):"pause"===a?($("#playlist-position span").html("Not playing"),$("#play").addClass("btn-primary"),$("i","#play").removeClass("fa fa-play").addClass("fa fa-pause"),$("#stop").removeClass("btn-primary")):"stop"===a&&($("#play").removeClass("btn-primary"),$("i","#play").removeClass("fa fa-pause").addClass("fa fa-play"),$("#stop").addClass("btn-primary"),$("#section-index").length&&$("#countdown-display").countdown("destroy"),$("#total").html("radio"===GUI.stream?"":"00:00"),$("#time").val(0,!1).trigger("update"),$("#format-bitrate").html(" "),$("li","#playlist-entries").removeClass("active")),"stop"!==a){$("#total").html("radio"===GUI.stream?"":void 0!==GUI.json.time?timeConvert(GUI.json.time):"00:00");var b=GUI.json.audio_channels&&GUI.json.audio_sample_depth&&GUI.json.audio_sample_rate?GUI.json.audio_channels+", "+GUI.json.audio_sample_depth+" bit, "+GUI.json.audio_sample_rate+" kHz, "+GUI.json.bitrate+" kbps":" ";$("#format-bitrate").html(b),$("li","#playlist-entries").removeClass("active");var c=parseInt(GUI.json.song);$("#playlist-entries").find("li").eq(c).addClass("active")}$("#playlist-position span").html(GUI.json.playlistlength&&"0"!==GUI.json.playlistlength?GUI.json.song?"Playlist position "+(parseInt(GUI.json.song)+1)+"/"+GUI.json.playlistlength:"Playlist position 1/"+GUI.json.playlistlength:"Empty queue, add some music!"),$("a","#open-panel-sx").html(void 0!==GUI.json.updating_db?' Updating':' Library')}function updateGUI(){var a=GUI.json.volume,b=GUI.json.radioname,c=GUI.json.currentartist,d=GUI.json.currentsong,e=GUI.json.currentalbum;if(GUI.stream=null!==b&&void 0!==b&&""!==b?"radio":"",refreshState(),$("#section-index").length){if(GUI.currentsong!==GUI.json.currentsong&&(setQueuePos(),countdownRestart(0),$("#panel-dx").hasClass("active"))){var f=parseInt(GUI.json.song);customScroll("pl",f)}$("#volume").val("-1"===a?100:a,!1).trigger("update"),"radio"!==GUI.stream?($("#currentartist").html(null===c||void 0===c||""===c?'[no artist]':c),$("#currentsong").html(null===d||void 0===d||""===d?'[no title]':d),$("#currentalbum").html(null===e||void 0===e||""===e?'[no album]':e)):($("#currentartist").html(null===c||void 0===c||""===c?b:c),$("#currentsong").html(null===d||void 0===d||""===d?b:d),$("#currentalbum").html('streaming')),"1"===GUI.json.repeat?$("#repeat").addClass("btn-primary"):$("#repeat").removeClass("btn-primary"),"1"===GUI.json.random?$("#random").addClass("btn-primary"):$("#random").removeClass("btn-primary"),"1"===GUI.json.consume?$("#consume").addClass("btn-primary"):$("#consume").removeClass("btn-primary"),"1"===GUI.json.single?$("#single").addClass("btn-primary"):$("#single").removeClass("btn-primary"),GUI.currentsong=d;var g=c+" - "+e;if(GUI.currentalbum!==g)if(null===b||void 0===b||""===b){var h=Math.floor(1001*Math.random());$("#cover-art").css("background-image",'url("/coverart/?v='+h+'")')}else $("#cover-art").css("background-image",'url("assets/img/cover-radio.jpg")');GUI.currentalbum=g}}function getPlaylistPlain(a){var b,c,d=parseInt(GUI.json.song)+1,e=GUI.json.state,f="",g="",h="",i="",j="",k="",l="",m="",n="",o="",p="",q="",r=0,s=a.split("\n"),t=[];for(b=0;c=s[b];b+=1)t=c.split(": "),"Time"===t[0]?g=parseInt(t[1]):"Artist"===t[0]?h=t[1]:"Title"===t[0]?j=t[1]:"Name"===t[0]?k=t[1]:"Album"===t[0]?i=t[1]:"file"===t[0]?l=t[1]:"Id"===t[0]&&(o=t[1],""===j||""===i?(n=parsePath(l),m=l.split("/").pop(),j=m,p=""===h?"path: "+n:h):p=h+" - "+i,""!==k?(j=''+k,p=l,q=""):q=""+timeConvert2(g)+"",r++,f+='
  • '+j+q+''+p+"
  • ",g="",h="",i="",j="",k="");$(".playlist").addClass("hide"),$("#playlist-entries").removeClass("hide");var u=document.getElementById("playlist-entries");u&&(u.innerHTML=f),$("#pl-filter-results").addClass("hide").html(""),$("#pl-filter").val(""),$("#pl-manage").removeClass("hide"),$("#pl-count").removeClass("hide").html(r+(1!==r?" entries":" entry"))}function parseQueue(a){var b,c,d=(parseInt(GUI.json.song)+1,GUI.json.state,a.split("\n")),e=1,f=[],g={};for(b=0;c=d[b];b+=1){var h=c.split(": ");"file"===h[0]?(g.file=h[1],g.fileExt=h[1].split(".").pop()):"Name"===h[0]?g.name=h[1]:"Last-Modified"===h[0]?g.lastModified=h[1]:"Time"===h[0]?(g.time=parseInt(h[1]),g.timeFormatted=timeConvert(g.time)):"Artist"===h[0]?g.artist=h[1]:"Title"===h[0]?g.title=h[1]:"Album"===h[0]?g.album=h[1]:"Track"===h[0]?g.track=h[1]:"Date"===h[0]?g.date=h[1]:"Genre"===h[0]?g.genre=h[1]:"Id"===h[0]&&(g.id=h[1],g.pos=e++,g.name&&(g.webradio=!0),f.push(g),g={})}return f}function getPlaylistCmd(){loadingSpinner("pl"),$.ajax({url:"/db/?cmd=playlist",success:function(a){a.length>4?($(".playlist").addClass("hide"),$("#playlist-entries").removeClass("hide"),queueTracks=parseQueue(a),$("#playlist").height(queueTracks.length*listEntryHeight),setQueuePos(),$("#open-panel-dx").hasClass("active")&&GUI.currentsong!==GUI.json.currentsong&&customScroll("pl",GUI.currentqueuepos,500),m.redraw()):($(".playlist").addClass("hide"),$("#playlist-warning").removeClass("hide"),$("#pl-filter-results").addClass("hide").html(""),$("#pl-count").removeClass("hide").html("0 entries")),loadingSpinner("pl","hide")}})}function renderQueue(a){data=a[0],data.length>4?($(".playlist").addClass("hide"),$("#playlist-entries").removeClass("hide"),queueTracks=parseQueue(data),$("#playlist").height(queueTracks.length*listEntryHeight),setQueuePos(),$("#open-panel-dx").hasClass("active")&&GUI.currentsong!==GUI.json.currentsong&&customScroll("pl",GUI.currentqueuepos,500),m.redraw()):($(".playlist").addClass("hide"),$("#playlist-warning").removeClass("hide"),$("#pl-filter-results").addClass("hide").html(""),$("#pl-count").removeClass("hide").html("0 entries")),loadingSpinner("pl","hide")}function renderUI(a){if(toggleLoader("close"),GUI.json=a[0],GUI.state=GUI.json.state,updateGUI(),$("#section-index").length){var b=""!==GUI.json.elapsed&&void 0!==GUI.json.elapsed?GUI.json.elapsed:0,c=""!==GUI.json.time&&void 0!==GUI.json.time&&null!==GUI.json.time?GUI.json.time:0;refreshTimer(parseInt(b),parseInt(c),GUI.json.state),"radio"!==GUI.stream?refreshKnob():$("#time").val(0,!1).trigger("update"),GUI.json.playlist!==GUI.playlist&&(getPlaylistCmd(),GUI.playlist=GUI.json.playlist)}}function renderPlaylists(a){var b,c,d="",e="",f=a.split("\n"),g=[];for(b=0;c=f[b];b+=1)g=c.split(": "),"playlist"===g[0]&&(e=g[1],d+='
  • '+e+"
  • ",e="");document.getElementById("playlist-entries").innerHTML="",$(".playlist").addClass("hide"),$("#pl-manage").addClass("hide"),$("#pl-count").addClass("hide"),$("#pl-filter-results").removeClass("hide").addClass("back-to-queue").html(' to queue'),$("#pl-currentpath").removeClass("hide"),$("#pl-editor").removeClass("hide"),document.getElementById("pl-editor").innerHTML=d,loadingSpinner("pl","hide")}function getPlaylists(){loadingSpinner("pl"),$.ajax({url:"/command/?cmd=listplaylists",success:function(a){renderPlaylists(a)}})}function parseResponse(a){var b=a.inputArr||"",c=a.respType||"",d=a.i||0,e=a.inpath||"",f=a.querytype||"",g="";switch(c){case"playlist":break;case"db":"file"===GUI.browsemode?(""===e&&void 0!==b.file&&(e=parsePath(b.file)),void 0!==b.file||"Webradio"===e?(g='
  • ',g+=b.Title+" "+timeConvert(b.Time)+"",g+=' ',g+=b.Artist,g+=" - ",g+=b.Album):"Webradio"!==e?(g+=b.file,g+='">',g+=b.file.replace(e+"/","")+" "+timeConvert(b.Time)+"",g+='',g+=" path: ",g+=e):(g+=b.playlist,g+='">',g+=''+b.playlist.replace(e+"/","").replace("."+b.fileext,""),g+='webradio'),g+="
  • "):void 0!==b.playlist?"cue"===b.fileext&&(g='
  • ',g+=b.playlist.replace(e+"/","")+" [CUE file]",g+='',g+=" path: ",g+=e,g+="
  • "):(g='
  • ':'">',g+=b.directory.replace(e+"/",""),g+="
  • ")):"album"===GUI.browsemode||"albumfilter"===GUI.browsemode?void 0!==b.file?(g='
  • ',g+=b.Title+" "+timeConvert(b.Time)+"",g+=' ',g+=b.Artist,g+=" - ",g+=b.Album,g+="
  • "):""!==b.album&&(g='
  • "):"artist"===GUI.browsemode?void 0!==b.album?(g='
  • ',g+=""!==b.album?b.album:"Unknown album",g+="
  • "):""!==b.artist&&(g='
  • ',g+=b.artist,g+="
  • "):"genre"===GUI.browsemode&&(void 0!==b.artist?(g='
  • ',g+=""!==b.artist?b.artist:"Unknown artist",g+="
  • "):""!==b.genre&&(g='
  • ',g+=b.genre,g+="
  • "));break;case"Spotify":""===f?(g='
  • ',g+=""!==b.name?b.name:"Favorites",g+=" (",g+=b.tracks,g+=")
  • "):"tracks"===f&&(g='
  • ',g+=b.title+" "+timeConvert(b.duration/1e3)+"",g+=' ',g+=b.artist,g+=" - ",g+=b.album,g+="
  • ");break;case"Dirble":""===f?(g='
  • ',g+=b.name,g+="
  • "):"stations"===f&&(g='
  • "+b.bitrate+"",g+='',g+=b.website,g+="
  • ");break;case"Jamendo":g='
  • ',g+=b.dispname+"
  • "}return g}function populateDB(a){var b=a.data||"",c=a.path||"",d=a.uplevel||0,e=a.keyword||"",f=a.plugin||"",g=a.querytype||"",h=a.args||"",i="",j=0,k=[];if(""!==f){if("Spotify"===f){for($("#database-entries").removeClass("hide"),$("#db-level-up").removeClass("hide"),$("#home-blocks").addClass("hide"),c&&(GUI.currentpath=c),document.getElementById("database-entries").innerHTML="",b="tracks"===g?b.tracks:b.playlists,j=0;k=b[j];j+=1)i+=parseResponse({inputArr:k,respType:"Spotify",i:j,querytype:g,inpath:h});document.getElementById("database-entries").innerHTML=i}if("Dirble"===f){for($("#database-entries").removeClass("hide"),$("#db-level-up").removeClass("hide"),$("#home-blocks").addClass("hide"),c&&(GUI.currentpath=c),document.getElementById("database-entries").innerHTML="",j=0;k=b[j];j+=1)i+=parseResponse({inputArr:k,respType:"Dirble",i:j,querytype:g});document.getElementById("database-entries").innerHTML=i}if("Jamendo"===f){for($("#database-entries").removeClass("hide"),$("#db-level-up").removeClass("hide"),$("#home-blocks").addClass("hide"),c&&(GUI.currentpath=c),document.getElementById("database-entries").innerHTML="",j=0;k=b[j];j+=1)i+=parseResponse({inputArr:k,respType:"Jamendo",i:j,querytype:g});document.getElementById("database-entries").innerHTML=i}}else{if(""===c&&""===e)return void renderLibraryHome();if($("#database-entries").removeClass("hide"),$("#db-level-up").removeClass("hide"),$("#home-blocks").addClass("hide"),c&&(GUI.currentpath=c),document.getElementById("database-entries").innerHTML="",""!==e){var l=b.length?b.length:"0",m=1===b.length?"":"s";$("#db-level-up").addClass("hide"),$("#db-search-results").removeClass("hide").html('back')}for(j=0;k=b[j];j+=1)i+=parseResponse({inputArr:k,respType:"db",i:j,inpath:c});"Webradio"===c&&(i+='
  • add newadd a webradio to your library
  • '),document.getElementById("database-entries").innerHTML=i}var n=$("span","#db-currentpath");if(n.html("album"===GUI.browsemode?"Albums"===c?c:"Albums/"+c:"artist"===GUI.browsemode?"Artists"===c?c:"Artists/"+c:"genre"===GUI.browsemode?"Genres"===c?c:"Genres/"+c:c),$("#db-homeSetup").addClass("hide"),d){var o=GUI.currentDBpos[GUI.currentDBpos[10]];$("#db-"+o).addClass("active"),customScroll("db",o,0)}else customScroll("db",0,0);loadingSpinner("db","hide")}function getDB(a){var b=a.cmd||"browse",c=a.path||"",d=a.browsemode||"file",e=a.uplevel||"",f=a.plugin||"",g=a.querytype||"",h=a.args||"";if(loadingSpinner("db"),GUI.browsemode=d,""!==f)"Spotify"===f?$.post("/db/?cmd=spotify",{plid:h},function(a){populateDB({data:a,path:c,plugin:f,querytype:g,uplevel:e,args:h})},"json"):"Dirble"===f?$.post("/db/?cmd=dirble",{querytype:""===g?"categories":g,args:h},function(a){populateDB({data:a,path:c,plugin:f,querytype:g,uplevel:e})},"json"):"Jamendo"===f&&$.post("/db/?cmd=jamendo",{querytype:""===g?"radio":g,args:h},function(a){populateDB({data:a.results,path:c,plugin:f,querytype:g})},"json");else if("search"===b){var i=$("#db-search-keyword").val();$.post("/db/?querytype="+GUI.browsemode+"&cmd=search",{query:i},function(a){populateDB({data:a,path:c,uplevel:e,keyword:i})},"json")}else"browse"===b?$.post("/db/?cmd=browse",{path:c,browsemode:GUI.browsemode},function(a){populateDB({data:a,path:c,uplevel:e})},"json"):(loadingSpinner("db","hide"),$.post("/db/?cmd="+b,{path:c,querytype:g},function(){},"json"))}function onreleaseKnob(a){if("stop"!==GUI.state&&""!==GUI.state)if("radio"!==GUI.stream){window.clearInterval(GUI.currentKnob);var b=Math.floor(a*parseInt(GUI.json.time)/1e3);sendCmd("seek "+GUI.json.song+" "+b),$("#time").val(a),$("#countdown-display").countdown("destroy"),$("#countdown-display").countdown({since:-b,compact:!0,format:"MS"})}else $("#time").val(0).trigger("change")}function commandButton(a){var b,c=a.data("cmd");if("stop"===c)a.addClass("btn-primary"),$("#play").removeClass("btn-primary"),$("#section-index").length&&(refreshTimer(0,0,"stop"),window.clearInterval(GUI.currentKnob),$(".playlist").find("li").removeClass("active"),$("#total").html("00:00"));else{if("play"===c){var d=GUI.state;return"play"===d?(b="pause",$("#section-index").length&&$("#countdown-display").countdown("pause")):"pause"===d?(b="play",$("#section-index").length&&$("#countdown-display").countdown("resume")):"stop"===d&&(b="play",$("#section-index").length&&$("#countdown-display").countdown({since:0,compact:!0,format:"MS"})),window.clearInterval(GUI.currentKnob),void sendCmd(b)}if("previous"===c||"next"===c)$("#section-index").length&&($("#countdown-display").countdown("pause"),window.clearInterval(GUI.currentKnob));else if(a.hasClass("btn-volume")){var e,f=parseInt($("#volume").val());return null===GUI.volume&&(GUI.volume=f),"volumedn"===c&&parseInt(GUI.volume)>0?(e=parseInt(GUI.volume)-1,GUI.volume=e,$("#volumemute").removeClass("btn-primary")):"volumeup"===c&&parseInt(GUI.volume)<100?(e=parseInt(GUI.volume)+1,GUI.volume=e,$("#volumemute").removeClass("btn-primary")):"volumemute"===c&&(0!==f?(GUI.volume=f,a.addClass("btn-primary"),e=0):(a.removeClass("btn-primary"),setvol(GUI.volume))),void(e>=0&&100>=e&&sendCmd("setvol "+e))}}a.hasClass("btn-toggle")?(b=c+(a.hasClass("btn-primary")?" 0":" 1"),a.toggleClass("btn-primary")):b=c,sendCmd(b)}function libraryHome(a){GUI.libraryhome=a[0],renderLibraryHome()}function listWLANs(a){var b="",c="",d="",e=a[0];$.each(e,function(a){b+='

    ',0!==e[a].connected&&(b+=''),1===e[a].storedprofile&&"on"===e[a].encryption?b+='':"on"===e[a].encryption?b+='':1!==e[a].storedprofile&&(b+=''),b+=""+e[a].ESSID+"

    ",1===e[a].storedprofile?d+=b:c+=b,b=""}),""===c&&(c='

    scanning for networks...

    '),document.getElementById("wifiNetworks").innerHTML=c,document.getElementById("wifiStored").innerHTML=d,$.ajax({url:"/command/?cmd=wifiscan"})}function nicsDetails(a){var b="",c=a[0];$.each(c,function(a){a===$("#nic-details").data("name")&&(b+="Name:"+a+"",b+="Type:wireless","off/any"===c[a].currentssid?b+='Status:no network connected':(b+='Status:connected',b+="Associated SSID:"+c[a].currentssid+""),b+="Assigned IP:"+(null!==c[a].ip?""+c[a].ip+"":"none")+"",b+="Speed:"+(null!==c[a].speed?c[a].speed:"unknown")+"")}),$("#nic-details tbody").html(b)}function playbackChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode,reconnectOnChannelUnavailableInterval:5e3});a.onmessage=renderUI,a.onstatuschange=function(a){2===a?($("#loader").addClass("hide"),sendCmd("renderui")):0===a&&toggleLoader()},a.addChannel("playback"),a.connect()}function queueChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=renderQueue,a.addChannel("queue"),a.connect()}function libraryChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=libraryHome,a.addChannel("library"),a.connect()}function notifyChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=renderMSG,a.addChannel("notify"),a.connect()}function wlansChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=listWLANs,a.addChannel("wlans"),a.connect(),$.ajax({url:"/command/?cmd=wifiscan"})}function nicsChannel(){var a=new PushStream({host:window.location.hostname,port:window.location.port,modes:GUI.mode});a.onmessage=nicsDetails,a.addChannel("nics"),a.connect()}function overlayTrigger(a){function b(){if(c.hasClass("open")){c.removeClass("open"),c.addClass("closed")}else if(c.hasClass("closed")&&(c.addClass("open"),"#overlay-social"===a)){var b="https://twitter.com/home?status=Listening+to+"+GUI.json.currentsong.replace(/\s+/g,"+")+"+by+"+GUI.json.currentartist.replace(/\s+/g,"+")+"+on+%40RuneAudio+http%3A%2F%2Fwww.runeaudio.com%2F+%23nowplaying",d="https://www.facebook.com/sharer.php?u=http%3A%2F%2Fwww.runeaudio.com%2F&display=popup",e="https://plus.google.com/share?url=http%3A%2F%2Fwww.runeaudio.com%2F";$("#urlTwitter").attr("href",b),$("#urlFacebook").attr("href",d),$("#urlGooglePlus").attr("href",e) +}}var c=$(a),d=$(a+"-open"),e=$(a+"-close");transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"},d.click(function(){b()}),e.click(function(){b()})}function getHiddenProp(){var a=["webkit","moz","ms","o"];if("hidden"in document)return"hidden";for(var b=0;bback')):(a("#pl-count").removeClass("hide"),a("#pl-filter-results").addClass("hide").html(""))}),a("#pl-filter-results").click(function(){a(this).addClass("hide"),a("#pl-count").removeClass("hide"),a(this).hasClass("back-to-queue")?(a(".playlist").addClass("hide"),getPlaylistCmd(),a("#pl-currentpath").addClass("hide"),a("#pl-manage").removeClass("hide")):(a("li","#playlist-entries").each(function(){var b=a(this);b.show()}),a("#pl-currentpath").removeClass("hide"),a("#pl-filter").val("")),customScroll("pl",parseInt(GUI.json.song),500)}),a("#pl-manage-list").click(function(){getPlaylists()}),a("#modal-pl-save-btn").click(function(){var b=a("#pl-save-name").val();sendCmd('save "'+b+'"')}),a("#pl-editor").on("click",".pl-action",function(b){b.preventDefault();var c=a(this).parent().attr("data-path");GUI.DBentry[0]=c}),a("#pl-rename-button").click(function(){var b=a("#pl-rename-oldname").text(),c=a("#pl-rename-name").val();sendCmd('rename "'+b+'" "'+c+'"'),getPlaylists()});var e=document.getElementById("playlist-entries");new Sortable(e,{ghostClass:"sortable-ghost",onUpdate:function(a){sortOrder(a.item.getAttribute("id"))}}),a("a","#open-panel-sx").click(function(){a("#open-panel-sx").hasClass("active")&&customScroll("pl",parseInt(GUI.json.song),500)}).on("shown.bs.tab",function(){customScroll("db",GUI.currentDBpos[GUI.currentDBpos[10]],0)}),a("#home-blocks").on("click",".home-block",function(b){if(a(this).hasClass("inactive"))a("#overlay-playsource-open").trigger("click");else if(a(this).is("#home-spotify-switch"))a("#overlay-playsource-open").trigger("click");else if(a(b.target).is("span.block-remove")){var c=a(this).attr("id");c=c.replace("home-bookmark-","");var d=a(this).find("h3").text();a.post("/db/?cmd=bookmark",{id:c,name:d})}else++GUI.currentDBpos[10],getDB({browsemode:a(this).data("browsemode"),path:a(this).data("path"),uplevel:0,plugin:a(this).data("plugin")})}),a("#db-homeSetup").click(function(){var b=a(this);b.hasClass("btn-primary")?(b.removeClass("btn-primary").addClass("btn-default"),a(".home-block-remove").remove()):(b.removeClass("btn-default").addClass("btn-primary"),a(".home-block.home-bookmark").append('
    ×
    '))});var f=a("#database-entries");f.on("click","li",function(b){var c="",d="",e=a(this);if(a(b.target).hasClass("db-action"))b.preventDefault(),c="spotify-track"===e.data("type")?e.data("plid")+"-"+e.data("path"):e.data("path"),GUI.DBentry[0]=c;else if(a("li.active","#database-entries").removeClass("active"),e.addClass("active"),e.hasClass("db-folder")){c=e.data("path"),e.hasClass("db-album")?""!==c?getDB({path:c,uplevel:0,browsemode:"album"}):(c=GUI.currentDBpath[GUI.currentDBpos[10]-1],getDB({path:c,uplevel:0,browsemode:"albumfilter"})):e.hasClass("db-artist")?getDB({path:c,uplevel:0,browsemode:"artist"}):e.hasClass("db-genre")?getDB({path:c,uplevel:0,browsemode:"genre"}):e.hasClass("db-spotify")?(c=GUI.currentpath+"/"+e.find("span").text(),getDB({path:c,plugin:"Spotify",args:e.data("path").toString(),querytype:"tracks"}),GUI.plugin="Spotify"):e.hasClass("db-dirble")?(c=GUI.currentpath+"/"+e.find("span").text(),getDB({path:c,plugin:"Dirble",querytype:"stations",args:e.data("path")}),GUI.plugin="Dirble"):e.hasClass("db-jamendo")||(d=e.data("browsemode"),getDB({path:c,uplevel:0,browsemode:d}));var f=e.attr("id");f=f.replace("db-",""),GUI.currentDBpos[GUI.currentDBpos[10]]=f,GUI.currentDBpath[GUI.currentDBpos[10]]=c,++GUI.currentDBpos[10]}else e.hasClass("db-webradio-add")&&a("#modal-webradio-add").modal()}),f.on("dblclick","li",function(b){var c=a(this);if(!a(b.target).hasClass("db-action")){a("li.active","#database-entries").removeClass("active"),c.addClass("active");var d=c.data("path");c.hasClass("db-spotify")?(d=c.attr("data-plid")+"-"+c.attr("data-path"),getDB({cmd:"spaddplay",path:d,querytype:"spotify-track"})):(d=c.hasClass("db-dirble")?d.split(" | ")[1]:d,getDB({cmd:"addplay",path:d}))}}),a("#db-level-up").click(function(){--GUI.currentDBpos[10];var a=GUI.currentpath;if(0===GUI.currentDBpos[10])a="";else if("file"===GUI.browsemode){var b=a.lastIndexOf("/");a=-1!==b?a.slice(0,b):""}else"album"===GUI.browsemode?(a=GUI.currentDBpath[GUI.currentDBpos[10]-1],""===a?a="Albums":GUI.browsemode="artist"):"artist"===GUI.browsemode?(a=GUI.currentDBpath[GUI.currentDBpos[10]-1],""===a?a="Artists":GUI.browsemode="genre"):"genre"===GUI.browsemode?a="Genres":"albumfilter"===GUI.browsemode&&(GUI.browsemode="artist",a=GUI.currentDBpath[GUI.currentDBpos[10]-1]);getDB({browsemode:GUI.browsemode,path:a,plugin:GUI.plugin,uplevel:1}),GUI.plugin=""}),a("#db-search-results").click(function(){a(this).addClass("hide"),a("#db-level-up").removeClass("hide"),getDB({path:GUI.currentpath})}),a("a",".context-menu").click(function(){var b=a(this).data("cmd"),c=a(this).data("type"),d=GUI.DBentry[0];switch(GUI.DBentry[0]="",b){case"pl-add":sendCmd('load "'+d+'"');break;case"pl-replace":sendCmd("clear"),sendCmd('load "'+d+'"');break;case"pl-rename":a("#modal-pl-rename").modal(),a("#pl-rename-oldname").text(d);break;case"pl-rm":a.ajax({url:"/command/?cmd=rm%20%22"+d+"%22",success:function(a){getPlaylists(a)}});break;case"wradd":d=d.split(" | ")[1],getDB({cmd:"add",path:d});break;case"wraddplay":d=d.split(" | ")[1],getDB({cmd:"addplay",path:d});break;case"wraddreplaceplay":d=d.split(" | ")[1],getDB({cmd:"addreplaceplay",path:d});break;case"wredit":a("#modal-webradio-edit").modal(),a.post("/db/?cmd=readradio",{filename:d},function(b){var c=a("#webradio-edit-name");c.val(b.name),c.data("file-name",b.name),a("#webradio-edit-url").val(b.url)},"json");break;case"wrdelete":a("#modal-webradio-delete").modal(),a("#webradio-delete-name").text(d.replace("Webradio/",""));break;case"wrsave":var e=d.split(" | ");a.post("/db/?cmd=addradio",{"radio[label]":e[0],"radio[url]":e[1]});break;default:getDB({cmd:b,path:d,browsemode:GUI.browsemode,querytype:c})}}),a("#webradio-add-button").click(function(){var b=a("#webradio-add-name").val(),c=a("#webradio-add-url").val();""===b||""===c?renderMSG([{title:"Missing fields",text:"Please fill both fields to continue",icon:"fa fa-warning"}]):(a.post("/db/?cmd=addradio",{"radio[label]":b,"radio[url]":c},function(){},"json"),a("#modal-webradio-add").modal("hide"),a("#webradio-add-name").val(""),a("#webradio-add-url").val(""))}),a("#webradio-edit-button").click(function(){var b=a("#webradio-edit-name");a.post("/db/?cmd=editradio",{"radio[newlabel]":b.val(),"radio[label]":b.data("file-name"),"radio[url]":a("#webradio-edit-url").val()},function(){},"json")}),a("#webradio-delete-button").click(function(){var b=a("#webradio-delete-name").text();a.post("/db/?cmd=deleteradio",{"radio[label]":b},function(){},"json")}),a("#db-firstPage").click(function(){a.scrollTo(0,500)}),a("#db-prevPage").click(function(){var b="-="+a(window).height()+"px";a.scrollTo(b,500)}),a("#db-nextPage").click(function(){var b="+="+a(window).height()+"px";a.scrollTo(b,500)}),a("#db-lastPage").click(function(){a.scrollTo("100%",500)}),a("#pl-firstPage").click(function(){isCustomScroll=!0,a.scrollTo(0,500,{onAfter:function(){m.redraw(),isCustomScroll=!1}})}),a("#pl-prevPage").click(function(){var b=a(window).scrollTop(),c=b-a(window).height()+160;a.scrollTo(c,500)}),a("#pl-nextPage").click(function(){var b=a(window).scrollTop(),c=b+a(window).height()-160;a.scrollTo(c,500)}),a("#pl-lastPage").click(function(){isCustomScroll=!0,a.scrollTo("100%",500,{onAfter:function(){m.redraw(),isCustomScroll=!1}})});var g=document.location.toString();g.match("#")&&a('#menu-bottom a[href="/#'+g.split("#")[1]+'"]').tab("show"),a("#menu-bottom a").on("shown",function(a){history.pushState?history.pushState(null,null,a.target.hash):window.location.hash=a.target.hash}).on("click",function(){a("#overlay-social").hasClass("open")&&a(".overlay-close").trigger("click")}),a(".ttip").length&&a(".ttip").tooltip(),FastClick.attach(document.body),a("#syscmd-poweroff").click(function(){a.post("/settings/",{syscmd:"poweroff"}),toggleLoader()}),a("#syscmd-reboot").click(function(){a.post("/settings/",{syscmd:"reboot"}),toggleLoader()}),overlayTrigger("#overlay-social"),overlayTrigger("#overlay-playsource"),a("#playsource-mpd").click(function(){a(this).hasClass("inactive")&&(a.ajax({url:"/command/?switchplayer=MPD"}),a("#overlay-playsource-close").trigger("click"))}),a("#playsource-spotify").click(function(){a(this).hasClass("inactive")&&("1"===GUI.libraryhome.Spotify?(a.ajax({url:"/command/?switchplayer=Spotify"}),a("#overlay-playsource-close").trigger("click")):new PNotify({title:"Spotify not enabled",text:"Enable and configure it under the Settings screen",icon:"fa fa-exclamation-circle"}))})}:function(a){"use strict";if(GUI.mode=checkWebSocket(),playbackChannel(),PNotify.prototype.options.styling="fontawesome",PNotify.prototype.options.stack.dir1="up",PNotify.prototype.options.stack.dir2="left",PNotify.prototype.options.stack.firstpos1=90,PNotify.prototype.options.stack.firstpos2=50,PNotify.prototype.options.stack.spacing1=10,PNotify.prototype.options.stack.spacing2=10,notifyChannel(),a(".btn-cmd").click(function(){var b=a(this);commandButton(b)}),a("#syscmd-poweroff").click(function(){a.post("/settings/",{syscmd:"poweroff"}),toggleLoader()}),a("#syscmd-reboot").click(function(){a.post("/settings/",{syscmd:"reboot"}),toggleLoader()}),a(".selectpicker").selectpicker(),a("#section-sources").length&&("nfs"===a("#mount-type").val()&&a("#mount-cifs").addClass("disabled").children(".disabler").removeClass("hide"),a("#mount-type").change(function(){"cifs"===a(this).val()||"osx"===a(this).val()?a("#mount-cifs").removeClass("disabled").children(".disabler").addClass("hide"):a("#mount-cifs").addClass("disabled").children(".disabler").removeClass("hide")}),a("#nas-guest").change(function(){a(this).prop("checked")?a("#mount-auth").addClass("disabled").children(".disabler").removeClass("hide"):a("#mount-auth").removeClass("disabled").children(".disabler").addClass("hide")}),a("#nas-advanced").change(function(){a(this).prop("checked")?a("#mount-advanced-config").removeClass("hide"):a("#mount-advanced-config").addClass("hide")}),a("#show-mount-advanced-config").click(function(b){b.preventDefault(),a(this).hasClass("active")?(a("#mount-advanced-config").toggleClass("hide"),a(this).removeClass("active"),a(this).find("i").removeClass("fa fa-minus-circle").addClass("fa fa-plus-circle"),a(this).find("span").html("show advanced options")):(a("#mount-advanced-config").toggleClass("hide"),a(this).addClass("active"),a(this).find("i").removeClass("fa fa-plus-circle").addClass("fa fa-minus-circle"),a(this).find("span").html("hide advanced options"))}),a("#usb-mount-list a").click(function(){var b=a(this).data("mount");a("#usb-umount-name").html(b),a("#usb-umount").val(b)})),a("#section-settings").length&&(a("#airplay").change(function(){a(this).prop("checked")?(a("#airplayName").removeClass("hide"),a("#airplayBox").addClass("boxed-group")):(a("#airplayName").addClass("hide"),a("#airplayBox").removeClass("boxed-group"))}),a("#scrobbling-lastfm").change(function(){a(this).prop("checked")?(a("#lastfmAuth").removeClass("hide"),a("#lastfmBox").addClass("boxed-group")):(a("#lastfmAuth").addClass("hide"),a("#lastfmBox").removeClass("boxed-group"))}),a("#proxy").change(function(){a(this).prop("checked")?(a("#proxyAuth").removeClass("hide"),a("#proxyBox").addClass("boxed-group")):(a("#proxyAuth").addClass("hide"),a("#proxyBox").removeClass("boxed-group"))}),a("#dlna").change(function(){a(this).prop("checked")?(a("#dlnaName").removeClass("hide"),a("#dlnaBox").addClass("boxed-group")):(a("#dlnaName").addClass("hide"),a("#dlnaBox").removeClass("boxed-group"))}),a("#spotify").change(function(){a(this).prop("checked")?(a("#spotifyAuth").removeClass("hide"),a("#spotifyBox").addClass("boxed-group")):(a("#spotifyAuth").addClass("hide"),a("#spotifyBox").removeClass("boxed-group"))})),a("#section-network").length){var b=a("#network-manual-config");"0"===a("#dhcp").val()&&b.removeClass("hide"),a("#dhcp").change(function(){"0"===a(this).val()?b.removeClass("hide"):b.addClass("hide")});var c=a("#wifi-security-key");"open"!==a("#wifi-security").val()&&c.removeClass("hide"),a("#wifi-security").change(function(){"open"!==a(this).val()?c.removeClass("hide"):c.addClass("hide")}),a("#wifiNetworks").length&&(wlansChannel(),nicsChannel()),a("#wifiProfiles").change(function(){a(this).prop("checked")?a("#wifiProfilesBox").addClass("hide"):a("#wifiProfilesBox").removeClass("hide")})}if(a("#section-mpd").length&&(a("#audio-output-interface").change(function(){renderMSG([{title:"Switching audio output",text:"Please wait for the config update...",icon:"fa fa-cog fa-spin",delay:5e3}]);var b=a(this).val();a.ajax({type:"POST",url:"/mpd/",data:{ao:b}})}),a(".manual-edit-confirm").find(".btn-primary").click(function(){a("#mpdconf_editor").removeClass("hide"),a("#manual-edit-warning").addClass("hide")})),a("#section-debug").length){ZeroClipboard.config({swfPath:"/assets/js/vendor/ZeroClipboard.swf"});var d=new ZeroClipboard(document.getElementById("copy-to-clipboard"));d.on("ready",function(){d.on("aftercopy",function(){new PNotify({title:"Copied to clipboard",text:"The debug output was copied successfully in your clipboard.",icon:"fa fa-check"})})})}});var pageY=0,pageHeight=window.innerHeight-160,visibleEntries=Math.floor(pageHeight/listEntryHeight||2),pageYold=0,pageYdiff=0;window.resize=function(){pageHeight=window.innerHeight-160,visibleEntries=Math.floor(pageHeight/listEntryHeight||2)},window.onscroll=function(){isCustomScroll||(pageY=window.pageYOffset,pageYdiff=Math.abs(pageYold-pageY),pageYdiff>6*pageHeight&&(pageYold=pageY,console.log("REDRAW ",pageY),m.redraw()))},m.module(document.getElementById("playlist"),{controller:function(){},view:function(){var a=Math.floor(pageY/listEntryHeight)||0,b=a+visibleEntries,c=8,d=Math.max(a-visibleEntries*c,0),e=Math.min(b+visibleEntries*c,Math.max(queueTracks.length-1,0)),f=d*listEntryHeight;return m("#queue-entries-container",[m("ul#playlist-entries",{style:"position:relative;top:"+f+"px"},[queueTracks?queueTracks.slice(d,e).map(function(a,b){var c=null,e=null;return a.webradio?(c=m("i.fa.fa-microphone"),e="URL: "+a.file):a.artist?e=a.artist+" - "+a.album:a.file?e="path: "+a.file.split("/").pop():console.log(a),m("li",{id:"pl-"+a.id,"data-queuepos":d+b,"class":a.current?"active":""},[m('i.fa.fa-times-circle.pl-action[title="Remove song from playlist"]'),m("span.sn",[a.title,m("span",a.timeFormatted)]),m("span.bl",e)])}):null])])}}); \ No newline at end of file diff --git a/assets/js/runeui.mpd.js b/assets/js/runeui.mpd.js new file mode 100644 index 00000000..42125963 --- /dev/null +++ b/assets/js/runeui.mpd.js @@ -0,0 +1,156 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; + +window.mpd = new mithril.RuneModule('/mpd'); + +mpd.vm.saveAudioOutput = function (e) { + data.postData(mpd.vm.url, mpd.vm.ao); +}; + +mpd.vm.reset = function (e) { + mithril.showModal(modal.resetmpd); +}; + + +// 'MPD' view +mpd.view = function (ctrl) { + return [m('h1', 'MPD Configuration'), '\n', m('p', ['\n If you mess up with this configuration you can ', m('a[href="javascript:;"]', { onclick: mpd.vm.reset }, 'reset to default'), '.\n']), '\n', + m('fieldset', [ + m('legend', 'Audio Settings'), + m('.form-group', [ + mithril.createLabel('ao', 'Audio output interface'), + m('.col-sm-10', [ + //(id, container, field, list, valueField, displayField, config) + m('input.form-control.input-lg[data-trigger="change"][id="ao"][type="text"]', mithril.createInput(mpd.vm.data, 'ao', null, true, true)), + //createSelect('audio-output-interface', mpd.vm.data, 'ao', 'acards', 'name', 'extlabel', selectpicker), + m('span.help-block', ['This is the current output interface. It can be ', m('a[href="/audio"]', { config: m.route }, 'configured here'), '.' + ]) + ]), + mithril.createLabel('mixer-type', 'Volume control'), + m('.col-sm-10', [ + m('input.form-control.input-lg[id="mixer-type"][type="text"]', mithril.createInput(mpd.vm.data.conf, 'mixer_type', null, true)), + m('span.help-block', [ + 'This is the current volume control setting. It can be ', m('a[href="/audio"]', { config: m.route }, 'configured here'), '.' + ]) + ]) + ]) + ]), + //m('fieldset.form-horizontal', [ + // m('legend', 'Volume control'), + // m('.form-group', [ + // mithril.createLabel('mixer-type', 'Volume control'), + // m('.col-sm-10', [ + // m('input.form-control.input-lg[id="mixer-type"][type="text"]', mithril.createInput(mpd.vm.data.conf, 'mixer_type', null, true)), + // m('span.help-block', [ + // 'This is the current volume control setting. It can be ', m('a[href="/audio"]', { config: m.route }, 'configured here'), '.' + // ]) + // ]) + // ]) + //]), + m('fieldset.form-horizontal', [ + m('legend', 'General music daemon options'), + m('.form-group', [ + m('label.col-sm-2.control-label[for="port"]', 'Port'), + m('.col-sm-10', [ + m('input.form-control.input-lg[data-trigger="change"][disabled=""][id="port"][name="conf[port]"][type="text"]', mithril.createInput(mpd.vm.data.conf, 'port')), + m('span.help-block', 'This setting is the TCP port that is desired for the daemon to get assigned to.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="daemon-user"]', 'Daemon user : group'), + m('.col-sm-10', [ + m('select[data-style="btn-default btn-lg"][id="user"][name="conf[user]"]', mithril.createInput(mpd.vm.data.conf, 'user', helpers.selectpicker), [ + m('option[selected=""][value="mpd"]', 'mpd : audio (default)'), + m('option[value="root"]', 'root : root') + ]), + m('span.help-block', 'This specifies the system user : group that MPD will run as.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="log-level"]', 'Log level'), + m('.col-sm-10', [ + m('select[data-style="btn-default btn-lg"][id="log-level"][name="conf[log_level]"]', mithril.createInput(mpd.vm.data.conf, 'log_level', helpers.selectpicker), [ + m('option[selected=""][value="none"]', 'disabled'), + m('option[value="default"]', 'default'), + m('option[value="secure"]', 'secure'), + m('option[value="verbose"]', 'verbose') + ]), + m('span.help-block', 'This setting controls the type of information which is logged. Available setting arguments are \'disabled\', \'default\', \'secure\' or \'verbose\'. The \'verbose\' setting argument is recommended for troubleshooting, though can quickly stretch available resources on limited hardware storage.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="state-file"]', 'State file'), + m('.col-sm-10', [ + // (id, container, field, config) + mithril.createYesNo('state_file', mpd.vm.data.conf, 'state_file'), + m('span.help-block', 'This setting specifies if a state file is used. If the state file is active, the state of mpd will be saved when mpd is terminated by a TERM signal or by the \'kill\' command. When mpd is restarted, it will read the state file and restore the state of mpd (including the playlist).') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="ffmpeg"]', 'FFmpeg decoder plugin'), + m('.col-sm-10', [ + mithril.createYesNo('ffmpeg', mpd.vm.data.conf, 'ffmpeg'), + m('span.help-block', 'FFmpeg decoder plugin. Enable this setting if you need AAC / ALAC support. May slow down MPD database refresh.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="gapless-mp3-playback"]', 'Gapless mp3 playback'), + m('.col-sm-10', [ + mithril.createYesNo('gapless-mp3-playback', mpd.vm.data.conf, 'gapless_mp3_playback'), + m('span.help-block', 'If you have a problem with your MP3s ending abruptly it is recommended that you set this argument to \'no\' to attempt to fix the problem. If this solves the problem, it is highly recommended to fix the MP3 files with vbrfix (available as vbrfix in the debian archive), at which point gapless MP3 playback can be enabled.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="dsd-usb"]', 'DSD support'), + m('.col-sm-10', [ + mithril.createYesNo('dsd-usb', mpd.vm.data.conf, 'dsd_usb'), + m('span.help-block', 'Enable DSD audio support.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="volume-normalization"]', 'Volume normalization'), + m('.col-sm-10', [ + mithril.createYesNo('volume-normalization', mpd.vm.data.conf, 'volume_normalization'), + m('span.help-block', 'If yes, mpd will normalize the volume of songs as they play. The default is no') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="port"]', 'Audio buffer size'), + m('.col-sm-10', [ + m('input.form-control.input-lg[data-trigger="change"][id="audio-buffer-size"][min="512"][name="conf[audio_buffer_size]"][type="number"]', mithril.createInput(mpd.vm.data.conf, 'audio_buffer_size')), + m('span.help-block', 'This specifies the size of the audio buffer in kibibytes. The default is 2048, large enough for nearly 12 seconds of CD-quality audio.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="dsd-usb"]', 'Buffer before play'), + m('.col-sm-10', [ + m('select[data-style="btn-default btn-lg"][id="buffer-before-play"][name="conf[buffer_before_play]"]', mithril.createInput(mpd.vm.data.conf, 'buffer_before_play', helpers.selectpicker), [ + m('option[value="0%"]', 'disabled'), + '\n \n\';\n ', + m('option[selected=""][value="10%"]', '10%'), + '\n \n\';\n ', + m('option[value="20%"]', '20%'), + '\n \n\';\n ', + m('option[value="30%"]', '30%'), + '\n \n\';\n ' + ]), + m('span.help-block', ' This specifies how much of the audio buffer should be filled before playing a song. Try increasing this if you hear skipping when manually changing songs. The default is 10%, a little over 1 second of CD-quality audio with the default buffer size') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="auto-update"]', 'Auto update'), + m('.col-sm-10', [ + mithril.createYesNo('auto-update', mpd.vm.data.conf, 'auto_update'), + m('span.help-block', 'This setting enables automatic update of MPD"s database when files in music_directory are changed.') + ]) + ]) + ]), + m('.form-group.form-actions', [ + m('.col-sm-offset-2.col-sm-10', [ + m('button.btn.btn-default.btn-lg[name="cancel"][value="cancel"][type="button"]', { onclick: function (e) { mpd.vm.cancel('conf'); } }, 'Cancel'), + m('button.btn.btn-primary.btn-lg[name="save"][value="save"][type="button"]', { onclick: function (e) { mpd.vm.save('conf'); } }, 'Save and apply') + ]) + ]) + ]; +}; diff --git a/assets/js/runeui.navigation.js b/assets/js/runeui.navigation.js new file mode 100644 index 00000000..53ba43a9 --- /dev/null +++ b/assets/js/runeui.navigation.js @@ -0,0 +1,59 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; + +window.navigation = { + Page: function(data) { + this.name = m.prop(data.name); + this.url = m.prop(data.url); + this.icon = m.prop(data.icon); + this.selected = m.prop(data.selected || false); + this.action = function() { data.action(); }; + }, + vm: (function(data) { + + var vm = {}; + vm.pages = []; + + vm.add = function(name, url, icon, action) { + vm.pages.push(new navigation.Page({ name: name, url: url, icon: icon, action: action })); + }; + + vm.navigate = function(url) { + + for (i = 0; i < vm.pages.length - 1; i++) { + if (vm.pages[i].url() === url) { + vm.pages[i].selected(true); + } else { + vm.pages[i].selected(false); + } + } + }; + + vm.init = function() { + this.add('Playback', '/', 'play'); // Out of Scope + this.add('Audio', '/audio', 'volume-up'); // TODO: Needs a controller [ao, mixer_type, sound profile from settings] + this.add('MPD', '/mpd', 'cogs'); // + this.add('Settings', '/settings', 'wrench'); // + this.add('Sources', '/sources', 'folder-open'); // + this.add('Network', '/network', 'sitemap'); // HOLD until backend WiFi is complete + this.add('Debug', '/debug', 'bug'); // + this.add('Credits', '/credits', 'trophy'); // + this.add('Turn off', '', 'power-off', function() { m.module(document.getElementById('dialog'), modal.turnoff); }); // + }; + + return vm; + + }()), + controller: function() { + navigation.vm.init(); + }, + view: function(ctrl) { + return [m('a.dropdown-toggle[data-target="#"][data-toggle="dropdown"][href="#"][id="menu-settings"][role="button"]', + ['MENU ', m('i.fa.fa-bars.dx')]), '\n', m('ul.dropdown-menu[aria-labelledby="menu-settings"][role="menu"]', + [navigation.vm.pages.map(function(item, index) { + return m('li', { className: item.selected() ? 'active' : '' }, + item.url() ? [m('a[href="' + item.url() + '"]', { config: m.route }, [m('i.fa.fa-' + item.icon()), ' ' + item.name()])] : [m('a[href="javascript:;"]', { onclick: function(e) { item.action(); } }, [m('i.fa.fa-' + item.icon()), ' ' + item.name()])]); + })])]; + } +}; diff --git a/assets/js/runeui.network.js b/assets/js/runeui.network.js new file mode 100644 index 00000000..9b7ba88e --- /dev/null +++ b/assets/js/runeui.network.js @@ -0,0 +1,48 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; + +window.network = new mithril.RuneModule('/network'); + +network.vm.edit = function(item) { + var path = (item.wireless) ? 'wireless/' : 'wired/'; + m.route('/network/' + path + item.id); +}; + +// 'Network' view +network.view = function(ctrl) { + return [ + m('h1', 'Network configuration'), + m('.boxed', [ + m('p', [ + 'Configure wired and wireless connections. See below for the list of the active network interfaces as detected by the system.', + m('br'), + 'If your interface is connected but does not show, then try to refresh the list forcing the detect.' + ]), + m('div[id="network-refresh"]', [ + m('button.btn.btn-lg.btn-primary', { onclick: function(e) { network.vm.refresh(); } }, [ + m('i.fa.fa-refresh.sx'), + 'Refresh interfaces' + ]) + ]) + ]), + m('h2', 'Network interfaces'), + m('p', 'List of active network interfaces. Click on an entry to configure the corresponding connection.'), + m('.button-list[id="network-interface-list"]', [ + network.vm.data.nics.map(function(item, index) { + return m('p', [ + m('button.btn.btn-lg.btn-default.btn-block', { onclick: function(e) { network.vm.edit(item); } }, [ + ' ', + m('i.fa.sx', { className: (item.ip) ? 'fa-check green' : 'fa-times red' }), + ' ', + m('strong', item.id), + m('span', [ + m.trust('    ') + ]), + m('span', (item.ip) ? '[' + item.ip + ']' : '[no IP assigned]') + ]) + ]); + }) + ]) + ]; +}; \ No newline at end of file diff --git a/assets/js/runeui.network_wired.js b/assets/js/runeui.network_wired.js new file mode 100644 index 00000000..71ea84e3 --- /dev/null +++ b/assets/js/runeui.network_wired.js @@ -0,0 +1,102 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; + +window.network_wired = new mithril.RuneModule('/network'); + +// 'Wired Network' view +network_wired.view = function (ctrl) { + + if (network_wired.vm.data.nic.wireless) { + error.vm.showError('URL Error'); + return; + } + + return [ + m('h1', 'Wired network (' + network_wired.vm.data.profile.name + ')'), + m('input[name="nic[name]"][type="hidden"][value="eth0"]'), + m('input[name="nic[wireless]"][type="hidden"][value="0"]'), + m('fieldset.form-horizontal', [ + m('legend', 'Interface properties'), + m('.boxed', [ + m('table.info-table[data-name="eth0"][id="nic-details"]', [ + m('tbody', [ + m('tr', [ + m('th', 'Name:'), + m('td', [m('strong', network_wired.vm.data.profile.name)]) + ]), + m('tr', [ + m('th', 'Type:'), + m('td', 'wired ethernet') + ]), + m('tr', [ + m('th', 'Status:'), + m('td', [m('i.fa.sx', { className: (network_wired.vm.data.nic.ip) ? 'fa-check green' : 'fa-times red' }), network_wired.vm.data.nic.ip ? 'connected' : 'disconnected']) + ]), + m('tr', [ + m('th', 'Assigned IP:'), + m('td', [m('strong', network_wired.vm.data.nic.ip ? network_wired.vm.data.nic.ip : 'none')]) + ]), + m('tr', [ + m('th', 'Speed:'), + m('td', network_wired.vm.data.nic.ip ? network_wired.vm.data.nic.speed : 'N/A')]) + ]) + ]) + ]) + ]), + m('fieldset.form-horizontal', [ + m('legend', 'Interface configuration'), + m('.form-group', [ + m('label.col-sm-2.control-label', 'IP assignment'), + m('.col-sm-10', [ + mithril.createYesNo('dhcp', network_wired.vm.data.profile, 'dhcp', null, 'DHCP', 'Static'), + m('span.help-block', 'Choose between DHCP and Static configuration') + ]) + ]), + m('[id="network-manual-config"]', { + className: network_wired.vm.data.profile.dhcp ? 'hide' : '' + }, [ + m('.form-group', [ + m('label.col-sm-2.control-label]', 'IP address'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wired.vm.data.nic.ip + '"][required=""][type="text"]', mithril.createInput(network_wired.vm.data.profile, 'ip')), + m('span.help-block', 'Manually set the IP address.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label', 'Netmask'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wired.vm.data.nic.netmask + '"][required=""][type="text"]', mithril.createInput(network_wired.vm.data.profile, 'netmask')), + m('span.help-block', 'Manually set the network mask.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label', 'Gateway'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wired.vm.data.nic.gw + '"][required=""][type="text"]', mithril.createInput(network_wired.vm.data.profile, 'gw')), + m('span.help-block', 'Manually set the gateway.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label]', 'Primary DNS'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wired.vm.data.nic.dns1 + '"][type="text"]', mithril.createInput(network_wired.vm.data.profile, 'dns1')) + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label', 'Secondary DNS'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wired.vm.data.nic.dns2 + '"][type="text"]', mithril.createInput(network_wired.vm.data.profile, 'dns2')), + m('span.help-block', 'Manually set the primary and secondary DNS.') + ]) + ]) + ]), + m('.form-group.form-actions', [ + m('.col-sm-offset-2.col-sm-10', [ + m('button.btn.btn-default.btn-lg[type="button"]', { onclick: function(e) { network_wired.vm.cancel(); } }, 'Cancel'), ' ', + m('button.btn.btn-primary.btn-lg[type="button"]', { onclick: function(e) { network_wired.vm.save(); } }, 'Save and apply') + ]) + ]) + ]) + ]; +}; \ No newline at end of file diff --git a/assets/js/runeui.network_wireless.js b/assets/js/runeui.network_wireless.js new file mode 100644 index 00000000..55c55e3b --- /dev/null +++ b/assets/js/runeui.network_wireless.js @@ -0,0 +1,124 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; + +window.network_wireless = new mithril.RuneModule('/network'); + +// 'Wired Network' view +network_wireless.view = function (ctrl) { + + if (!network_wireless.vm.data.nic.wireless) { + error.vm.showError('URL Error'); + return; + } + + return [ + m('h1', 'Wireless network (' + network_wireless.vm.data.profile.name + ')'), + m('legend', 'Wi-Fi networks in range'), + m('span.help-block', 'The list of available Wi-Fi networks is automatically refreshed while you are on this page (so don"t forget it open in your browser to avoid unnecessary system load).'), + m('fieldset', [ + m('.boxed[id="wifiNetworks"]', [ + m('p', [m('div.btn.btn-lg.btn-default.btn-block', [m('i.fa.fa-cog.fa-spin.sx'),'scanning for networks...'])]) + ]) + ]), + m('legend', 'Wi-Fi stored profiles'), + m('fieldset', [ + m('.boxed', [ + m('label.switch-light.switch-block.well[onclick=""]', [ + m('input[checked="checked"][id="wifiProfiles"][name="features[airplay][enable]"][type="checkbox"][value="1"]'), + m('span', [m('span', ['SHOW',m('i.fa.fa-chevron-down.dx')]),m('span', ['HIDE',m('i.fa.fa-chevron-up.dx')])]), + m('a.btn.btn-primary') + ]), + m('.hide[id="wifiProfilesBox"]', [ + m('span.help-block', 'Add, edit or delete stored Wi-Fi profiles.'), + m('[id="wifiStored"]', [ + m('p', [m('a.btn.btn-lg.btn-default.btn-block]', [m('i.fa.fa-check.green.sx'),m('i.fa.fa-lock.sx'),m('strong', 'Test connection')])]) + ]), + m('p', [m('a.btn.btn-primary.btn-lg.btn-block[href="/network/wlan/wlan0/add"]', [m('i.fa.fa-plus.sx'),' Add new profile'])]) + ]) + ]) + ]), + m('fieldset.form-horizontal', [ + m('legend', 'Interface properties'), + m('.boxed', [ + m('table.info-table[data-name="eth0"][id="nic-details"]', [ + m('tbody', [ + m('tr', [ + m('th', 'Name:'), + m('td', [m('strong', network_wireless.vm.data.profile.name)]) + ]), + m('tr', [ + m('th', 'Type:'), + m('td', 'wi-fi') + ]), + m('tr', [ + m('th', 'Status:'), + m('td', [m('i.fa.sx', { className: (network_wireless.vm.data.nic.ip) ? 'fa-check green' : 'fa-times red' }), network_wireless.vm.data.nic.ip ? 'connected' : 'disconnected']) + ]), + m('tr', [ + m('th', 'Assigned IP:'), + m('td', [m('strong', network_wireless.vm.data.nic.ip ? network_wireless.vm.data.nic.ip : 'none')]) + ]), + m('tr', [ + m('th', 'Speed:'), + m('td', network_wireless.vm.data.nic.ip ? network_wireless.vm.data.nic.speed : 'N/A')]) + ]) + ]) + ]) + ]), + m('fieldset.form-horizontal', [ + m('legend', 'Interface configuration'), + m('.form-group', [ + m('label.col-sm-2.control-label', 'IP assignment'), + m('.col-sm-10', [ + mithril.createYesNo('dhcp', network_wireless.vm.data.profile, 'dhcp', null, 'DHCP', 'Static'), + m('span.help-block', 'Choose between DHCP and Static configuration') + ]) + ]), + m('[id="network-manual-config"]', { + className: network_wireless.vm.data.profile.dhcp ? 'hide' : '' + }, [ + m('.form-group', [ + m('label.col-sm-2.control-label]', 'IP address'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wireless.vm.data.nic.ip + '"][required=""][type="text"]', mithril.createInput(network_wireless.vm.data.profile, 'ip')), + m('span.help-block', 'Manually set the IP address.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label', 'Netmask'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wireless.vm.data.nic.netmask + '"][required=""][type="text"]', mithril.createInput(network_wireless.vm.data.profile, 'netmask')), + m('span.help-block', 'Manually set the network mask.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label', 'Gateway'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wireless.vm.data.nic.gw + '"][required=""][type="text"]', mithril.createInput(network_wireless.vm.data.profile, 'gw')), + m('span.help-block', 'Manually set the gateway.') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label]', 'Primary DNS'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wireless.vm.data.nic.dns1 + '"][type="text"]', mithril.createInput(network_wireless.vm.data.profile, 'dns1')) + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label', 'Secondary DNS'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="' + network_wireless.vm.data.nic.dns2 + '"][type="text"]', mithril.createInput(network_wireless.vm.data.profile, 'dns2')), + m('span.help-block', 'Manually set the primary and secondary DNS.') + ]) + ]) + ]), + m('.form-group.form-actions', [ + m('.col-sm-offset-2.col-sm-10', [ + m('button.btn.btn-default.btn-lg[type="button"]', { onclick: function(e) { network_wireless.vm.cancel(); } }, 'Cancel'), ' ', + m('button.btn.btn-primary.btn-lg[type="button"]', { onclick: function(e) { network_wireless.vm.save(); } }, 'Save and apply') + ]) + ]) + ]) + ]; +}; \ No newline at end of file diff --git a/assets/js/runeui.playback_controls.js b/assets/js/runeui.playback_controls.js new file mode 100644 index 00000000..cf3641de --- /dev/null +++ b/assets/js/runeui.playback_controls.js @@ -0,0 +1,88 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; +// window.playback_controls = window.playback_controls || {}; + +window.playback_controls = {}; + +playback_controls.vm = (function() { + + var vm = {}; + + vm.state = m.prop(''); + + vm.init = function() { + // a slot to store the state + vm.state = m.prop(''); + + // valid states from the backend + vm.validStates = {stop: 'stop', play: 'play', pause: 'pause'}; + + vm.setState = function(state) { + m.startComputation(); + playback_controls.vm.state(vm.validStates[state]); + // console.log(vm.validStates[state]); + m.endComputation(); + //m.redraw(); + }; + + // valid commands to the backend + vm.validCommands = {prev: 'previous', stop: 'stop', playpause: 'playpause', next: 'next'}; + + // filter playback control commands before sending them to the backend + vm.filterCmd = function(cmd) { + if (vm.validCommands[cmd] === 'playpause') { + if (playback_controls.vm.state() === 'play') { + vm.sendCmd('pause'); + } else if (playback_controls.vm.state() === 'stop' || playback_controls.vm.state() === 'pause') { + vm.sendCmd('play'); + } + } else if (vm.validCommands[cmd] === 'stop') { + if (vm.state() !== 'stop') { + vm.sendCmd('stop'); + } + } else { + vm.sendCmd(cmd); // check this + } + }; + + // send playback control commands to the backend + vm.sendCmd = function(cmd) { + var request = m.request({ + background: true, + method: 'GET', + url: '/command/?cmd=' + cmd, + deserialize: function(value) {return value;} + }); + }; + + }; + + return vm; + +}()); + +playback_controls.controller = function() { + playback_controls.vm.init(); +}; + +playback_controls.view = function(ctrl) { + return [ + m('button.btn.btn-default[title="Previous"]', + { onclick: function(e) { playback_controls.vm.filterCmd('previous'); }}, [ + m('i.fa.fa-step-backward', '') + ]),'\n\n', + m('button.btn.btn-default[title="Stop"]', { onclick: function(e) { playback_controls.vm.filterCmd('stop'); }, className: (playback_controls.vm.state() === 'stop') ? 'btn-primary' : '' }, [ + m('i.fa.fa-stop', '') + ]),'\n\n', + m('button.btn.btn-default[title="Play/Pause"]', + { onclick: function(e) { playback_controls.vm.filterCmd('playpause'); }, + className: (playback_controls.vm.state() === 'play' || playback_controls.vm.state() === 'pause') ? 'btn-primary' : '' }, [ + m('i.fa', { className: (playback_controls.vm.state() === 'pause') ? 'fa-pause' : 'fa-play' }, '') + ]),'\n\n', + m('button.btn.btn-default[title="Next"]', + { onclick: function(e) { playback_controls.vm.filterCmd('next'); }}, [ + m('i.fa.fa-step-forward', '') + ]) + ]; +}; diff --git a/assets/js/runeui.settings.js b/assets/js/runeui.settings.js new file mode 100644 index 00000000..048246a1 --- /dev/null +++ b/assets/js/runeui.settings.js @@ -0,0 +1,312 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; + +window.settings = new mithril.RuneModule('/settings'); +settings.vm.validate = function(){ + console.log('Settings view'); + return true; +}; + +settings.timezonesSelectOptions = Array; +settings.vm.options = new settings.timezonesSelectOptions(); + +// retrieve the timezones +settings.timezonesSelect = function(element, isInitialized, context) { + if (isInitialized) { + return; + } + // m.request({ + // method: 'GET', + // url: '/api/settings/timezones/' + // }).then(function(response) { + $.ajax({ + type: 'GET', + url: '/api/settings/timezones/', + dataType: 'json' + }).done(function(response) { + var options = response.timezones; + // console.log(options); + m.render(document.getElementById('timezone-select'), [ + m('select[data-style="btn-default btn-lg"][id="timezone"]', + { config: helpers.selectpicker }, + options.map(function(option) { + return m('option[value="' + option.value + '"]', option.name); + }) + ) + ]); + // console.log('RENDER!'); + }); +}; + +// 'Settings' view +settings.view = function(ctrl) { + return [ + m('h1', 'Settings'), + m('fieldset.form-horizontal', [ + m('legend', 'Environment'), + m('.form-group[id="systemstatus"]', [ + m('label.control-label.col-sm-2', 'Check system status'), + m('.col-sm-10', [ + m('a.btn.btn-default.btn-lg[data-toggle="modal"][href="#modal-sysinfo"]', { onclick: function(e) { m.module(document.getElementById('dialog'), modal.sysinfo); } }, [ + m('i.fa.fa-info-circle.sx'), + 'show status' + ]), + m('span.help-block', 'See information regarding the system and its status.') + ]) + ]), + m('.form-group[id="environment"]', [ + m('label.control-label.col-sm-2[for="hostname"]', 'Player hostname'), + m('.col-sm-10', [ + m('input.form-control.input-lg[autocomplete="off"][id="hostname"][placeholder="runeaudio"][type="text"]', mithril.createInput(settings.vm.data.environment, 'hostname')), + m('span.help-block', 'Set the player hostname. This will change the address used to reach the RuneUI.') + ]) + ]), + m('.form-group', [ + m('label.control-label.col-sm-2[for="ntpserver"]', 'NTP server'), + m('.col-sm-10', [ + m('input.form-control.input-lg[autocomplete="off"][id="ntpserver"][placeholder="pool.ntp.org"][type="text"]', mithril.createInput(settings.vm.data.environment, 'ntpserver')), + m('span.help-block', ['Set your reference time sync server ', m('i', '(NTP server)'), '.']) + ]) + ]), + m('.form-group', [ + m('label.control-label.col-sm-2[for="timezone"]', 'Timezone'), + m('.col-sm-10', { config: settings.timezonesSelect }, [ + m('#timezone-select', [ + m('.btn-group.bootstrap-select', [ + m('.btn.btn-default.btn-lg', [ + m('span.filter-option.pull-left', [ + m('i.fa.fa-spinner.fa-spin') + ]), + m('span.caret') + ]) + ]) + ]), + m('span.help-block', 'Set the system timezone.') + ]) + ]), + m('.form-group.form-actions', [ + m('.col-sm-offset-2.col-sm-10', [ + m('button.btn.btn-default.btn-lg[type="button"]', { + onclick: function(e) { + settings.vm.cancel('environment'); + } + }, 'Cancel'), + ' ', + m('button.btn.btn-primary.btn-lg[type="button"]', { + onclick: function(e) { + settings.vm.save('environment'); + } + }, 'Save and apply') + ]) + ]) + ]), + m('fieldset.form-horizontal', [ + m('legend', 'RuneOS kernel settings'), + m('.form-group', [ + m('label.control-label.col-sm-2[for="i2smodule"]', 'Linux Kernel'), + m('.col-sm-10', [ + m('select.selectpicker[data-style="btn-default btn-lg"][name="kernel"]', { + style: { + 'display': ' none' // [TODO] mithril.createInput instead + } + }, [ + m('option[selected=""][value="linux-arch-rpi_3.12.26-1-ARCH"]', 'Linux kernel 3.12.26-1 ARCH [RuneAudio v0.3-beta]'), + m('option[value="linux-rune-rpi_3.12.19-2-ARCH"]', 'Linux kernel 3.12.19-2 RUNE [RuneAudio v0.3-alpha]'), + m('option[value="linux-rune-rpi_3.6.11-18-ARCH+"]', 'Linux kernel 3.6.11-18 ARCH+ [RuneAudio v0.1-beta/v0.2-beta]'), + m('option[value="linux-rune-rpi_3.12.13-rt21_wosa"]', 'Linux kernel 3.12.13-rt RUNE-RT [Wolfson Audio Card]') + ]), + m('.btn-group.bootstrap-select', [m('button.btn.dropdown-toggle.selectpicker.btn-default.btn-lg[data-toggle="dropdown"][title="Linux kernel 3.12.26-1   ARCH [RuneAudio v0.3-beta]"][type="button"]', [m('span.filter-option.pull-left', 'Linux kernel 3.12.26-1 ARCH [RuneAudio v0.3-beta]'), ' ', m('span.caret')]), m('.dropdown-menu.open', [m('ul.dropdown-menu.inner.selectpicker[role="menu"]', [m('li.selected[rel="0"]', [m('a[tabindex="0"]', [m('span.text', 'Linux kernel 3.12.26-1 ARCH [RuneAudio v0.3-beta]'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="1"]', [m('a[tabindex="0"]', [m('span.text', 'Linux kernel 3.12.19-2 RUNE [RuneAudio v0.3-alpha]'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="2"]', [m('a[tabindex="0"]', [m('span.text', 'Linux kernel 3.6.11-18 ARCH+ [RuneAudio v0.1-beta/v0.2-beta]'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="3"]', [m('a[tabindex="0"]', [m('span.text', 'Linux kernel 3.12.13-rt RUNE-RT [Wolfson Audio Card]'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])])])])]), + m('span.help-block', ['Switch Linux Kernel version (REBOOT REQUIRED). ', m('strong', 'Linux kernel 3.12.26-1'), ' is the default kernel in the current release, ', m('strong', 'Linux kernel 3.12.19-2'), ' is the kernel used in RuneAudio v0.3-alpha, ', m('strong', 'Linux kernel 3.6.11-18'), ' is the kernel used in RuneAudio v0.1-beta/v0.2-beta (it has no support for I²S), ', m('strong', 'Linux kernel 3.12.13-rt'), ' is an EXPERIMENTAL kernel (not suitable for all configurations), it is optimized for ', m('strong', 'Wolfson Audio Card'), ' support and it is the default option for that type of soundcard.']) + ]), + m('label.control-label.col-sm-2[for="i2smodule"]', 'I²S kernel modules'), + m('.col-sm-10', [ + m('select.selectpicker[data-style="btn-default btn-lg"][name="i2smodule"]', { + style: { + 'display': ' none'// [TODO] mithril.createInput instead + } + }, [ + m('option[value="none"]', 'I²S disabled (default)'), + m('option[value="berrynos"]', 'G2Labs BerryNOS'), + m('option[value="berrynosmini"]', 'G2Labs BerryNOS mini'), + m('option[value="hifiberrydac"]', 'HiFiBerry DAC'), + m('option[value="hifiberrydacplus"]', 'HiFiBerry DAC+'), + m('option[value="hifiberrydigi"]', 'HiFiBerry Digi / Digi+'), + m('option[value="iqaudiopidac"]', 'IQaudIO Pi-DAC / Pi-DAC+'), + m('option[value="raspyplay3"]', 'RaspyPlay3'), + m('option[value="raspyplay4"]', 'RaspyPlay4'), + m('option[selected=""][value="transducer"]', 'Transducer') + ]), + m('.btn-group.bootstrap-select', [m('button.btn.dropdown-toggle.selectpicker.btn-default.btn-lg[data-toggle="dropdown"][title="Transducer"][type="button"]', [m('span.filter-option.pull-left', 'Transducer'), ' ', m('span.caret')]), m('.dropdown-menu.open', [m('ul.dropdown-menu.inner.selectpicker[role="menu"]', [m('li[rel="0"]', [m('a[tabindex="0"]', [m('span.text', 'I²S disabled (default)'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="1"]', [m('a[tabindex="0"]', [m('span.text', 'G2Labs BerryNOS'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="2"]', [m('a[tabindex="0"]', [m('span.text', 'G2Labs BerryNOS mini'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="3"]', [m('a[tabindex="0"]', [m('span.text', 'HiFiBerry DAC'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="4"]', [m('a[tabindex="0"]', [m('span.text', 'HiFiBerry DAC+'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="5"]', [m('a[tabindex="0"]', [m('span.text', 'HiFiBerry Digi / Digi+'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="6"]', [m('a[tabindex="0"]', [m('span.text', 'IQaudIO Pi-DAC / Pi-DAC+'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="7"]', [m('a[tabindex="0"]', [m('span.text', 'RaspyPlay3'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li[rel="8"]', [m('a[tabindex="0"]', [m('span.text', 'RaspyPlay4'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])]), m('li.selected[rel="9"]', [m('a[tabindex="0"]', [m('span.text', 'Transducer'), m('i.glyphicon.glyphicon-ok.icon-ok.check-mark')])])])])]), + m('span.help-block', ['Enable I²S output selecting one of the available sets of modules, specific for each hardware. Once set, the output interface will appear in the ', m('a[href="/mpd/"]', 'MPD configuration select menu'), ', and modules will also auto-load from the next reboot.']) + ]) + ]), + m('.form-group', [ + m('label.control-label.col-sm-2[for="orionprofile"]', 'Sound Signature (optimization profiles)'), + m('.col-sm-10', [ + m('input.form-control.input-lg[id="orionprofile"][type="text"]', mithril.createInput(settings.vm.data.kernel, 'orionprofile', null, true)), + m('span.help-block', [ + 'These profiles include a set of performance tweaks that act on some system kernel parameters. They can be ', m('a[href="/audio"]', { config: m.route }, 'configured here'), '.' + ]) + ]) + ]), + m('.form-group.form-actions', [ + m('.col-sm-offset-2.col-sm-10', [ + m('button.btn.btn-primary.btn-lg[type="submit"]', 'Apply settings') + ]) + ]), + m('fieldset.form-horizontal[id="features-management"]', [ + m('legend', 'Features management'), + m('p', 'Enable/disable optional modules that best suit your needs. Disabling unusued features will free system resources and might improve the overall performance.'), + m('[id="airplayBox"]', { + className: settings.vm.data.features.airplay.enable ? 'boxed-group' : '' + }, [ + m('.form-group', [ + m('label.control-label.col-sm-2[for="airplay"]', 'AirPlay'), + m('.col-sm-10', [ + mithril.createYesNo('airplay', settings.vm.data.features.airplay, 'enable'), + m('span.help-block', 'Toggle the capability of receiving wireless streaming of audio via AirPlay protocol') + ]) + ]), + m('[id="airplayName"]', { + className: settings.vm.data.features.airplay.enable ? '' : 'hide' + }, [ + m('.form-group', [ + m('label.control-label.col-sm-2[for="airplay-name"]', 'AirPlay name'), + m('.col-sm-10', [ + m('input.form-control.input-lg[id="airplay_name"][name="features[airplay][name]"][placeholder="runeaudio"][type="text"][value="RuneAudio"]'), + m('span.help-block', 'AirPlay broadcast name') + ]) + ]) + ]) + ]), + m('[id="spotifyBox"]', { + className: settings.vm.data.features.spotify.enable ? 'boxed-group' : '' + }, [ + m('.form-group', [ + m('label.control-label.col-sm-2[for="spotify"]', 'Spotify'), + m('.col-sm-10', [ + mithril.createYesNo('spotify', settings.vm.data.features.spotify, 'enable'), + m('span.help-block', ['Enable Spotify client [EXPERIMENTAL]. You must have a ', m('strong', [m('a[href="https://www.spotify.com/uk/premium/"][target="_blank"]', 'Spotify PREMIUM')]), ' account.']) + ]) + ]), + m('[id="spotifyAuth"]', { + className: settings.vm.data.features.spotify.enable ? '' : 'hide' + }, [ + m('.form-group', [ + m('label.control-label.col-sm-2[for="spotify-usr"]', 'Username'), + m('.col-sm-10', [ + m('input.form-control.input-lg[autocomplete="off"][id="spotify_user"][name="features[spotify][user]"][placeholder="user"][type="text"][value="user"]'), + m('span.help-block', ['Insert your Spotify ', m('i', 'username')]) + ]) + ]), + m('.form-group', [ + m('label.control-label.col-sm-2[for="spotify-pasw"]', 'Password'), + m('.col-sm-10', [ + m('input.form-control.input-lg[autocomplete="off"][id="spotify_pass"][name="features[spotify][pass]"][placeholder="pass"][type="password"][value="pass"]'), + m('span.help-block', ['Insert your Spotify ', m('i', 'password'), ' (case sensitive)']) + ]) + ]) + ]) + ]), + m('[id="dlnaBox"]', { + className: settings.vm.data.features.dlna.enable ? 'boxed-group' : '' + }, [ + m('.form-group', [ + m('label.control-label.col-sm-2[for="dlna"]', 'UPnP / DLNA'), + m('.col-sm-10', [ + mithril.createYesNo('dlna', settings.vm.data.features.dlna, 'enable'), + m('span.help-block', 'Toggle the capability of receiving wireless streaming of audio via UPnP / DLNA protocol') + ]) + ]), + m('[id="dlnaName"]', { + className: settings.vm.data.features.dlna.enable ? '' : 'hide' + }, [ + m('.form-group', [ + m('label.control-label.col-sm-2[for="dlna-name"]', 'UPnP / DLNA name'), + m('.col-sm-10', [ + m('input.form-control.input-lg[id="dlna_name"][name="features[dlna][name]"][placeholder="runeaudio"][type="text"][value="RuneAudio"]'), + m('span.help-block', 'UPnP / DLNA broadcast name') + ]) + ]) + ]) + ]), + m('.form-group', [ + m('label.control-label.col-sm-2[for="udevil"]', 'USB Automount'), + m('.col-sm-10', [ + mithril.createYesNo('udevil', settings.vm.data.features, 'udevil'), + m('span.help-block', 'Toggle automount for USB drives') + ]) + ]), + m('.form-group', [ + m('label.control-label.col-sm-2[for="coverart"]', 'Display album cover'), + m('.col-sm-10', [ + mithril.createYesNo('coverart', settings.vm.data.features, 'coverart'), + m('span.help-block', 'Toggle the display of album art on the Playback main screen') + ]) + ]), + m('[id="lastfmBox"]', { + className: settings.vm.data.features.lastfm.enable ? 'boxed-group' : '' + }, [ + m('.form-group', [ + m('label.control-label.col-sm-2[for="lastfm"]', [m('i.fa.fa.fa-lastfm-square'), ' Last.fm']), + m('.col-sm-10', [ + mithril.createYesNo('lastfm', settings.vm.data.features.lastfm, 'enable'), + m('span.help-block', 'Send to Last.fm informations about the music you are listening to (requires a Last.fm account)') + ]) + ]), + m('[id="lastfmAuth"]', { + className: settings.vm.data.features.lastfm.enable ? '' : 'hide' + }, [ + m('.form-group', [ + m('label.control-label.col-sm-2[for="lastfm-usr"]', 'Username'), + m('.col-sm-10', [ + m('input.form-control.input-lg[autocomplete="off"][id="lastfm_user"][placeholder="user"][type="text"][value="user"]'), + m('span.help-block', ['Insert your Last.fm ', m('i', 'username')]) + ]) + ]), + m('.form-group', [ + m('label.control-label.col-sm-2[for="lastfm-pasw"]', 'Password'), + m('.col-sm-10', [ + m('input.form-control.input-lg[autocomplete="off"][id="lastfm_pass"][placeholder="pass"][type="password"][value="pass"]'), + m('span.help-block', ['Insert your Last.fm ', m('i', 'password'), ' (case sensitive)']) + ]) + ]) + ]) + ]), + m('.form-group.form-actions', [ + m('.col-sm-offset-2.col-sm-10', [ + m('button.btn.btn-primary.btn-lg[type="button"]', {onclick: function(e) { settings.vm.save('features'); } }, 'apply settings') + ]) + ]) + ]), + m('fieldset.form-horizontal', [ + m('legend', 'Compatibility fixes'), + m('p', 'For people suffering problems with some receivers and DACs.'), + m('.form-group', [ + m('label.control-label.col-sm-2[for="cmediafix"]', 'CMedia fix'), + m('.col-sm-10', [ + mithril.createYesNo('cmediafix', settings.vm.data, 'cmediafix'), + m('span.help-block', ['For those who have a CM6631 receiver and experiment issues (noise, crackling) between tracks with different sample rates and/or bit depth.', m('br'), ' \n A \'dirty\' fix that should avoid the problem, do NOT use if everything works normally.']) + ]) + ]), + m('.form-group.form-actions', [ + m('.col-sm-offset-2.col-sm-10', [ + m('button.btn.btn-primary.btn-lg[type="button"]', 'Apply fixes') // [TODO] make it work + ]) + ]) + ]), + m('fieldset.form-horizontal', [ + m('legend', 'Backup / Restore configuration'), + m('p', 'Transfer settings between multiple RuneAudio installations, saving time during new/upgrade installations.'), + m('.form-group', [ + m('label.control-label.col-sm-2', 'Backup player config'), + m('.col-sm-10', [ + m('button.btn.btn-primary.btn-lg[id="syscmd-backup"][type="button"]', 'Backup'), // [TODO] make it work + m('span.help-block', 'NOTE: restore feature will come in 0.4 release.') + ]) + ]) + ]) + ]) + ]; +}; \ No newline at end of file diff --git a/assets/js/runeui.source.js b/assets/js/runeui.source.js new file mode 100644 index 00000000..56cf6d29 --- /dev/null +++ b/assets/js/runeui.source.js @@ -0,0 +1,183 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; + +window.source = new mithril.RuneModule('/sources'); + +source.vm.internal = {}; +source.vm.internal.guest = true; +source.vm.internal.advanced = false; + +window.source.vm.onload = function (status) { + if (status) { + if (source.vm.data.mount.username && (source.vm.data.mount.type != 'cfs')) { + source.vm.internal.guest = false; + } else if (source.vm.data.mount.type === 'cfs') { + source.vm.internal.guest = true; + source.vm.data.mount.username = ''; + source.vm.data.mount.password = ''; + } + } +}; + +source.vm.remove = function (e) { + mithril.showModal(modal.removeSource); +}; + +source.vm.saveAndClose = function () { + source.vm.save(); + m.route('/sources'); +}; + +source.Source = function (data) { + this.name = m.prop(data.name || ''); + this.type = m.prop(data.type || ''); + this.address = m.prop(data.address || ''); + this.remotedir = m.prop(data.remotedir || ''); + +}; + +source.vm.validate = function () { + var d = new source.Source(source.vm.data.mount); + if (d.name() === '') { + alert('The Source name is Required'); + return false; + } + return true; +}; + + + + +// single source view +source.view = function (ctrl) { + return [m('h1', 'NAS mounts'), + m('fieldset', [ + m('legend', [(ctrl.id === "0") ? 'Add new network mount' : 'Edit network mount', m('span', { className: (ctrl.id === "0") ? 'hide' : '' }, [' (', m('a[href="javascript:;"]', { onclick: source.vm.remove }, 'remove this mount'), ')'])]), + // (remove this mount) + m('.form-group', [ + m('.alert.alert-info', {className: (source.vm.data.mount.error) ? '' : 'hide'} ,[ + m('i.fa.fa-times.red.sx'), + m('span', source.vm.data.mount.error ) + ]), + m('label.col-sm-2.control-label[for="source-name"]', 'Source name'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="eg: Classical"][id="source-name"]', mithril.createInput(source.vm.data.mount, 'name')), + m('span.help-block', 'The name you want to give to this source. It will appear in your database tree structure') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-type"]', 'Fileshare protocol'), + m('.col-sm-10', [ + m('select.selectpicker[data-style="btn-default btn-lg"][id="source-type"]', mithril.createInput(source.vm.data.mount, 'type', helpers.selectpicker), [ + m('option[value="cifs"]', 'Windows (SMB/CIFS)'), + m('option[value="osx"]', 'OS X (SMB/CIFS)'), + m('option[value="nfs"]', 'Linux / Unix (NFS)') + ]), + m('span.help-block', 'Select SMB/CIFS for connect Windows file shares or NFS for unix file shares') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-address"]', 'IP address'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="eg: 192.168.1.250"][type="text"][id="source-address"]', mithril.createInput(source.vm.data.mount, 'address')), + m('ul.parsley-errors-list[id="parsley-id-0037"]'), + m('span.help-block', 'Specify your NAS address') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-remotedir"]', 'Remote directory'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="eg: Music/Classical"][type="text"][id="source-remotedir"]', mithril.createInput(source.vm.data.mount, 'remotedir')), + m('span.help-block', 'Specify the directory name on the NAS where to scan music files (case sensitive)') + ]) + ]), + m('.optional[id="mount-cifs"]', [ + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-guest"]', 'Guest access'), + m('.col-sm-10', [ + mithril.createYesNo('source-guest', source.vm.internal, 'guest'), + m('span.help-block', 'Log with guest account (no user/password required)') + ]) + ]) + ]), + m('.optional[id="mount-auth"]', { + className: (source.vm.internal.guest || (source.vm.data.mount.type==='nfs')) ? 'disabled' : '' + }, [ + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-username"]', 'Username'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="user"][type="text"][id="source-username"]', mithril.createInput(source.vm.data.mount, 'username')), + m('span.help-block', 'If required, specify username to grant access to the NAS (case sensitive)') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-password"]', 'Password'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="pass"][type="password"][id="source-password"]', mithril.createInput(source.vm.data.mount, 'password')), + m('span.help-block', 'If required, specify password to grant access to the NAS (case sensitive)') + ]) + ]), + m('.disabler', { + className: (source.vm.internal.guest || (source.vm.data.mount.type === 'nfs')) ? '' : 'hide' + }) + ]), + m('.disabler.hide'), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-advanced"]', 'Advanced options'), + m('.col-sm-10', [ + mithril.createYesNo('source-advanced', source.vm.internal, 'advanced'), + m('span.help-block', 'Show/hide advanced mount options') + ]) + ]) + ]), + m('fieldset[id="mount-advanced-config"]', { + className: (source.vm.internal.advanced) ? '' : 'hide' + }, [ + m('legend', 'Advanced options'), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-charset"]', 'Charset'), + m('.col-sm-10', [ + m('select.selectpicker[data-style="btn-default btn-lg"][id="source-charset"]', mithril.createInput(source.vm.data, 'charset', helpers.selectpicker), [ + m('option[value="utf8"]', 'UTF8 (default)'), + m('option[value="iso8859-1"]', 'ISO 8859-1') + ]), + m('span.help-block', 'Change this settings if you experience problems with character encoding') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-rsize"]', 'Rsize'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="8192"][type="text"][id="source-rsize"]', mithril.createInput(source.vm.data.mount, 'rsize')), + m('span.help-block', 'Change this settings if you experience problems with music playback (es: pops or clips)') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-wsize"]', 'Wsize'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="16384"][type="text"][id="source-wsize"]', mithril.createInput(source.vm.data.mount, 'wsize')), + m('span.help-block', 'Change this settings if you experience problems with music playback (es: pops or clips)') + ]) + ]), + m('.form-group', [ + m('label.col-sm-2.control-label[for="source-options"]', 'Mount flags'), + m('.col-sm-10', [ + m('input.form-control.input-lg[placeholder="cache=none,ro"][type="text"][id="source-options"]', mithril.createInput(source.vm.data.mount, 'options')), + m('span.help-block', 'Advanced mount flags. Don"t use this field if you don"t know what you are doing.') + ]) + ]) + ]), + m('.form-group.form-actions', [ + m('.col-sm-offset-2.col-sm-10', [ + m('a.btn.btn-default.btn-lg[href="/sources"]', { + config: m.route + }, 'Cancel'), + m('button.btn.btn-primary.btn-lg[type="button"]', { + onclick: function (e) { + source.vm.saveAndClose(); + } + }, 'Save and apply') + ]) + ]) + ]; +}; \ No newline at end of file diff --git a/assets/js/runeui.sources.js b/assets/js/runeui.sources.js new file mode 100644 index 00000000..a1a22cca --- /dev/null +++ b/assets/js/runeui.sources.js @@ -0,0 +1,64 @@ +window.helpers = window.helpers || {}; +window.mithril = window.mithril || {}; +window.data = window.data || {}; + +window.sources = new mithril.RuneModule('/sources'); + +sources.Source = function (data) { + this.id = m.prop(data.id); + this.title = m.prop(data.title); + this.path = m.prop(data.path); + this.status = m.prop(data.status); +}; + +sources.vm.updateMDP = function () { + data.postData(sources.vm.url, { updatempd: true }); +}; +sources.vm.mountAll = function () { + data.postData(sources.vm.url, { mountall: true }); +}; +sources.vm.add = function () { + m.route('/sources/0'); +}; +sources.vm.edit = function (id) { + m.route('/sources/' + id); +}; +sources.vm.unmountUSB = function (id) { + modal.unmountUSB.vm.current(id); + m.module(document.getElementById('dialog'), modal.unmountUSB); +}; + +// 'Sources' view +sources.view = function (ctrl) { + return [m('.container', [ + m('h1', 'Local sources'), + m('.boxed', [ + m('p', ['Your ', m('a[href="/#panel-sx"]', 'music library'), ' is composed by two main content types: ', m('strong', 'local sources'), ' and streaming sources.', m('br'), '\n This section lets you configure your local sources, telling ', m('a[href="http://www.musicpd.org/"][rel="nofollow"][target="_blank"][title="Music Player Daemon"]', 'MPD'), ' to scan the contents of ', m('strong', 'network mounts'), ' and ', m('strong', 'USB mounts'), '.']), + m('button.btn.btn-lg.btn-primary[id="updatempddb"][type="button"]', { onclick: sources.vm.updateMDP }, [m('i.fa.fa-refresh.sx'), 'Rebuild MPD Library']) + ]), + m('h2', 'Network mounts'), + m('p', 'List of configured network mounts. Click an existing entry to edit it, or add a new one.'), + m('p', [m('button.btn.btn-lg.btn-primary.btn-block[id="mountall"][type="button"]', { onclick: sources.vm.mountAll }, [m('i.fa.fa-refresh.sx'), ' Remount all sources'])]), + sources.vm.data.mounts.map(function (item, index) { + return m("p", [m("a.btn.btn-lg.btn-default.btn-block[href='javascript:;']", { + onclick: function (e) { + sources.vm.edit(item.id); + } + }, + [" ", m("i.fa.sx", { className: (item.status) ? 'fa-check green' : 'fa-times red' }), item.name, m("span", " \\\\" + item.address + "\\" + item.remotedir)]) + ]); + }), + m('p', [m('a.btn.btn-lg.btn-primary.btn-block', { onclick: sources.vm.add }, [m('i.fa.fa-plus.sx'), ' Add new mount'])]), + m('h2', 'USB mounts'), + m('p', ['List of mounted USB drives. To safely unmount a drive, click on it then confirm at the dialog prompt.', m('br'), '\n If a drive is connected but not shown in the list, please check if ', m('a[href="/settings"]', { config: m.route }, 'USB automount'), ' is enabled.']), + m('.button-list[id="usb-mount-list"]', [ + (sources.vm.data.usbmounts) ? sources.vm.data.usbmounts.map(function (item, index) { + return m("p", [m("a.btn.btn-lg.btn-default.btn-block[href='javascript:;']", { + onclick: function (e) { + sources.vm.unmountUSB(item.device); + } + }, [m("i.fa.fa-check.green.sx"), item.device, m("span", "(size: " + item.size + ", " + item.use + " in use)")])]); + }) : m('p', [m('button.btn.btn-lg.btn-disabled.btn-block[disabled="disabled"]', 'no USB mounts present')]), + ]) + ])]; +}; diff --git a/assets/js/vendor/bootstrap-select.min.js b/assets/js/vendor/bootstrap-select.min.js index cd4f7fe2..074e3837 100644 --- a/assets/js/vendor/bootstrap-select.min.js +++ b/assets/js/vendor/bootstrap-select.min.js @@ -1,8 +1,8 @@ /*! - * bootstrap-select v1.4.2 - * http://silviomoreto.github.io/bootstrap-select/ + * Bootstrap-select v1.6.3 (http://silviomoreto.github.io/bootstrap-select/) * - * Copyright 2013 bootstrap-select - * Licensed under the MIT license + * Copyright 2013-2014 bootstrap-select + * Licensed under MIT (https://github.com/silviomoreto/bootstrap-select/blob/master/LICENSE) */ -;!function(b){b.expr[":"].icontains=function(e,c,d){return b(e).text().toUpperCase().indexOf(d[3].toUpperCase())>=0};var a=function(d,c,f){if(f){f.stopPropagation();f.preventDefault()}this.$element=b(d);this.$newElement=null;this.$button=null;this.$menu=null;this.options=b.extend({},b.fn.selectpicker.defaults,this.$element.data(),typeof c=="object"&&c);if(this.options.title===null){this.options.title=this.$element.attr("title")}this.val=a.prototype.val;this.render=a.prototype.render;this.refresh=a.prototype.refresh;this.setStyle=a.prototype.setStyle;this.selectAll=a.prototype.selectAll;this.deselectAll=a.prototype.deselectAll;this.init()};a.prototype={constructor:a,init:function(){this.$element.hide();this.multiple=this.$element.prop("multiple");var d=this.$element.attr("id");this.$newElement=this.createView();this.$element.after(this.$newElement);this.$menu=this.$newElement.find("> .dropdown-menu");this.$button=this.$newElement.find("> button");this.$searchbox=this.$newElement.find("input");if(d!==undefined){var c=this;this.$button.attr("data-id",d);b('label[for="'+d+'"]').click(function(f){f.preventDefault();c.$button.focus()})}this.checkDisabled();this.clickListener();if(this.options.liveSearch){this.liveSearchListener()}this.render();this.liHeight();this.setStyle();this.setWidth();if(this.options.container){this.selectPosition()}this.$menu.data("this",this);this.$newElement.data("this",this)},createDropdown:function(){var c=this.multiple?" show-tick":"";var f=this.options.header?'
    '+this.options.header+"
    ":"";var e=this.options.liveSearch?'':"";var d='
    ';return b(d)},createView:function(){var c=this.createDropdown();var d=this.createLi();c.find("ul").append(d);return c},reloadLi:function(){this.destroyLi();var c=this.createLi();this.$menu.find("ul").append(c)},destroyLi:function(){this.$menu.find("li").remove()},createLi:function(){var d=this,e=[],c="";this.$element.find("option").each(function(){var i=b(this);var g=i.attr("class")||"";var h=i.attr("style")||"";var m=i.data("content")?i.data("content"):i.html();var k=i.data("subtext")!==undefined?''+i.data("subtext")+"":"";var j=i.data("icon")!==undefined?' ':"";if(j!==""&&(i.is(":disabled")||i.parent().is(":disabled"))){j=""+j+""}if(!i.data("content")){m=j+''+m+k+""}if(d.options.hideDisabled&&(i.is(":disabled")||i.parent().is(":disabled"))){e.push('')}else{if(i.parent().is("optgroup")&&i.data("divider")!==true){if(i.index()===0){var l=i.parent().attr("label");var n=i.parent().data("subtext")!==undefined?''+i.parent().data("subtext")+"":"";var f=i.parent().data("icon")?' ':"";l=f+''+l+n+"";if(i[0].index!==0){e.push('
    '+l+"
    "+d.createA(m,"opt "+g,h))}else{e.push("
    "+l+"
    "+d.createA(m,"opt "+g,h))}}else{e.push(d.createA(m,"opt "+g,h))}}else{if(i.data("divider")===true){e.push('
    ')}else{if(b(this).data("hidden")===true){e.push("")}else{e.push(d.createA(m,g,h))}}}}});b.each(e,function(f,g){c+="
  • "+g+"
  • "});if(!this.multiple&&this.$element.find("option:selected").length===0&&!this.options.title){this.$element.find("option").eq(0).prop("selected",true).attr("selected","selected")}return b(c)},createA:function(e,c,d){return''+e+''},render:function(){var d=this;this.$element.find("option").each(function(h){d.setDisabled(h,b(this).is(":disabled")||b(this).parent().is(":disabled"));d.setSelected(h,b(this).is(":selected"))});this.tabIndex();var g=this.$element.find("option:selected").map(function(){var j=b(this);var i=j.data("icon")&&d.options.showIcon?' ':"";var h;if(d.options.showSubtext&&j.attr("data-subtext")&&!d.multiple){h=' '+j.data("subtext")+""}else{h=""}if(j.data("content")&&d.options.showContent){return j.data("content")}else{if(j.attr("title")!==undefined){return j.attr("title")}else{return i+j.html()+h}}}).toArray();var f=!this.multiple?g[0]:g.join(this.options.multipleSeparator);if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var c=this.options.selectedTextFormat.split(">");var e=this.options.hideDisabled?":not([disabled])":"";if((c.length>1&&g.length>c[1])||(c.length==1&&g.length>=2)){f=this.options.countSelectedText.replace("{0}",g.length).replace("{1}",this.$element.find('option:not([data-divider="true"]):not([data-hidden="true"])'+e).length)}}if(!f){f=this.options.title!==undefined?this.options.title:this.options.noneSelectedText}this.$button.attr("title",b.trim(f));this.$newElement.find(".filter-option").html(f)},setStyle:function(e,d){if(this.$element.attr("class")){this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device/gi,""))}var c=e?e:this.options.style;if(d=="add"){this.$button.addClass(c)}else{if(d=="remove"){this.$button.removeClass(c)}else{this.$button.removeClass(this.options.style);this.$button.addClass(c)}}},liHeight:function(){var e=this.$menu.parent().clone().appendTo("body"),f=e.addClass("open").find("> .dropdown-menu"),d=f.find("li > a").outerHeight(),c=this.options.header?f.find(".popover-title").outerHeight():0,g=this.options.liveSearch?f.find(".bootstrap-select-searchbox").outerHeight():0;e.remove();this.$newElement.data("liHeight",d).data("headerHeight",c).data("searchHeight",g)},setSize:function(){var h=this,d=this.$menu,i=d.find(".inner"),t=this.$newElement.outerHeight(),f=this.$newElement.data("liHeight"),r=this.$newElement.data("headerHeight"),l=this.$newElement.data("searchHeight"),k=d.find("li .divider").outerHeight(true),q=parseInt(d.css("padding-top"))+parseInt(d.css("padding-bottom"))+parseInt(d.css("border-top-width"))+parseInt(d.css("border-bottom-width")),o=this.options.hideDisabled?":not(.disabled)":"",n=b(window),g=q+parseInt(d.css("margin-top"))+parseInt(d.css("margin-bottom"))+2,p,u,s,j=function(){u=h.$newElement.offset().top-n.scrollTop();s=n.height()-u-t};j();if(this.options.header){d.css("padding-top",0)}if(this.options.size=="auto"){var e=function(){var v;j();p=s-g;if(h.options.dropupAuto){h.$newElement.toggleClass("dropup",(u>s)&&((p-g)3){v=f*3+g-2}else{v=0}d.css({"max-height":p+"px",overflow:"hidden","min-height":v+"px"});i.css({"max-height":p-r-l-q+"px","overflow-y":"auto","min-height":v-q+"px"})};e();b(window).resize(e);b(window).scroll(e)}else{if(this.options.size&&this.options.size!="auto"&&d.find("li"+o).length>this.options.size){var m=d.find("li"+o+" > *").filter(":not(.div-contain)").slice(0,this.options.size).last().parent().index();var c=d.find("li").slice(0,m+1).find(".div-contain").length;p=f*this.options.size+c*k+q;if(h.options.dropupAuto){this.$newElement.toggleClass("dropup",(u>s)&&(p .dropdown-menu").css("width");d.remove();this.$newElement.css("width",c)}else{if(this.options.width=="fit"){this.$menu.css("min-width","");this.$newElement.css("width","").addClass("fit-width")}else{if(this.options.width){this.$menu.css("min-width","");this.$newElement.css("width",this.options.width)}else{this.$menu.css("min-width","");this.$newElement.css("width","")}}}if(this.$newElement.hasClass("fit-width")&&this.options.width!=="fit"){this.$newElement.removeClass("fit-width")}},selectPosition:function(){var e=this,d="
    ",f=b(d),h,g,c=function(i){f.addClass(i.attr("class")).toggleClass("dropup",i.hasClass("dropup"));h=i.offset();g=i.hasClass("dropup")?0:i[0].offsetHeight;f.css({top:h.top+g,left:h.left,width:i[0].offsetWidth,position:"absolute"})};this.$newElement.on("click",function(){c(b(this));f.appendTo(e.options.container);f.toggleClass("open",!b(this).hasClass("open"));f.append(e.$menu)});b(window).resize(function(){c(e.$newElement)});b(window).on("scroll",function(){c(e.$newElement)});b("html").on("click",function(i){if(b(i.target).closest(e.$newElement).length<1){f.removeClass("open")}})},mobile:function(){this.$element.addClass("mobile-device").appendTo(this.$newElement);if(this.options.container){this.$menu.hide()}},refresh:function(){this.reloadLi();this.render();this.setWidth();this.setStyle();this.checkDisabled();this.liHeight()},update:function(){this.reloadLi();this.setWidth();this.setStyle();this.checkDisabled();this.liHeight()},setSelected:function(c,d){this.$menu.find("li").eq(c).toggleClass("selected",d)},setDisabled:function(c,d){if(d){this.$menu.find("li").eq(c).addClass("disabled").find("a").attr("href","#").attr("tabindex",-1)}else{this.$menu.find("li").eq(c).removeClass("disabled").find("a").removeAttr("href").attr("tabindex",0)}},isDisabled:function(){return this.$element.is(":disabled")},checkDisabled:function(){var c=this;if(this.isDisabled()){this.$button.addClass("disabled").attr("tabindex",-1)}else{if(this.$button.hasClass("disabled")){this.$button.removeClass("disabled")}if(this.$button.attr("tabindex")==-1){if(!this.$element.data("tabindex")){this.$button.removeAttr("tabindex")}}}this.$button.click(function(){return !c.isDisabled()})},tabIndex:function(){if(this.$element.is("[tabindex]")){this.$element.data("tabindex",this.$element.attr("tabindex"));this.$button.attr("tabindex",this.$element.data("tabindex"))}},clickListener:function(){var c=this;b("body").on("touchstart.dropdown",".dropdown-menu",function(d){d.stopPropagation()});this.$newElement.on("click",function(){c.setSize();if(!c.options.liveSearch&&!c.multiple){setTimeout(function(){c.$menu.find(".selected a").focus()},10)}});this.$menu.on("click","li a",function(k){var g=b(this).parent().index(),j=c.$element.val(),f=c.$element.prop("selectedIndex");if(c.multiple){k.stopPropagation()}k.preventDefault();if(!c.isDisabled()&&!b(this).parent().hasClass("disabled")){var d=c.$element.find("option");var i=d.eq(g);if(!c.multiple){d.prop("selected",false);i.prop("selected",true)}else{var h=i.prop("selected");i.prop("selected",!h)}if(!c.multiple){c.$button.focus()}else{if(c.options.liveSearch){c.$searchbox.focus()}}if((j!=c.$element.val()&&c.multiple)||(f!=c.$element.prop("selectedIndex")&&!c.multiple)){c.$element.change()}}});this.$menu.on("click","li.disabled a, li dt, li .div-contain, .popover-title, .popover-title :not(.close)",function(d){if(d.target==this){d.preventDefault();d.stopPropagation();if(!c.options.liveSearch){c.$button.focus()}else{c.$searchbox.focus()}}});this.$menu.on("click",".popover-title .close",function(){c.$button.focus()});this.$searchbox.on("click",function(d){d.stopPropagation()});this.$element.change(function(){c.render()})},liveSearchListener:function(){var d=this,c=b('
  • ');this.$newElement.on("click.dropdown.data-api",function(){d.$menu.find(".active").removeClass("active");if(!!d.$searchbox.val()){d.$searchbox.val("");d.$menu.find("li").show();if(!!c.parent().length){c.remove()}}if(!d.multiple){d.$menu.find(".selected").addClass("active")}setTimeout(function(){d.$searchbox.focus()},10)});this.$searchbox.on("input propertychange",function(){if(d.$searchbox.val()){d.$menu.find("li").show().not(":icontains("+d.$searchbox.val()+")").hide();if(!d.$menu.find("li").filter(":visible:not(.no-results)").length){if(!!c.parent().length){c.remove()}c.html('No results match "'+d.$searchbox.val()+'"').show();d.$menu.find("li").last().after(c)}else{if(!!c.parent().length){c.remove()}}}else{d.$menu.find("li").show();if(!!c.parent().length){c.remove()}}d.$menu.find("li.active").removeClass("active");d.$menu.find("li").filter(":visible:not(.divider)").eq(0).addClass("active").find("a").focus();b(this).focus()});this.$menu.on("mouseenter","a",function(f){d.$menu.find(".active").removeClass("active");b(f.currentTarget).parent().not(".disabled").addClass("active")});this.$menu.on("mouseleave","a",function(){d.$menu.find(".active").removeClass("active")})},val:function(c){if(c!==undefined){this.$element.val(c);this.$element.change();return this.$element}else{return this.$element.val()}},selectAll:function(){this.$element.find("option").prop("selected",true).attr("selected","selected");this.render()},deselectAll:function(){this.$element.find("option").prop("selected",false).removeAttr("selected");this.render()},keydown:function(p){var q,o,i,n,k,j,r,f,h,m,d,s,g={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"};q=b(this);i=q.parent();if(q.is("input")){i=q.parent().parent()}m=i.data("this");if(m.options.liveSearch){i=q.parent().parent()}if(m.options.container){i=m.$menu}o=b("[role=menu] li:not(.divider) a",i);s=m.$menu.parent().hasClass("open");if(m.options.liveSearch){if(/(^9$|27)/.test(p.keyCode)&&s&&m.$menu.find(".active").length===0){p.preventDefault();m.$menu.parent().removeClass("open");m.$button.focus()}o=b("[role=menu] li:not(.divider):visible",i);if(!q.val()&&!/(38|40)/.test(p.keyCode)){if(o.filter(".active").length===0){o=m.$newElement.find("li").filter(":icontains("+g[p.keyCode]+")")}}}if(!o.length){return}if(/(38|40)/.test(p.keyCode)){if(!s){m.$menu.parent().addClass("open")}n=o.index(o.filter(":focus"));j=o.parent(":not(.disabled):visible").first().index();r=o.parent(":not(.disabled):visible").last().index();k=o.eq(n).parent().nextAll(":not(.disabled):visible").eq(0).index();f=o.eq(n).parent().prevAll(":not(.disabled):visible").eq(0).index();h=o.eq(k).parent().prevAll(":not(.disabled):visible").eq(0).index();if(m.options.liveSearch){o.each(function(e){if(b(this).is(":not(.disabled)")){b(this).data("index",e)}});n=o.index(o.filter(".active"));j=o.filter(":not(.disabled):visible").first().data("index");r=o.filter(":not(.disabled):visible").last().data("index");k=o.eq(n).nextAll(":not(.disabled):visible").eq(0).data("index");f=o.eq(n).prevAll(":not(.disabled):visible").eq(0).data("index");h=o.eq(k).prevAll(":not(.disabled):visible").eq(0).data("index")}d=q.data("prevIndex");if(p.keyCode==38){if(m.options.liveSearch){n-=1}if(n!=h&&n>f){n=f}if(nr){n=r}if(n==d){n=j}}q.data("prevIndex",n);if(!m.options.liveSearch){o.eq(n).focus()}else{p.preventDefault();if(!q.is(".dropdown-toggle")){o.removeClass("active");o.eq(n).addClass("active").find("a").focus();q.focus()}}}else{if(!q.is("input")){var c=[],l,t;o.each(function(){if(b(this).parent().is(":not(.disabled)")){if(b.trim(b(this).text().toLowerCase()).substring(0,1)==g[p.keyCode]){c.push(b(this).parent().index())}}});l=b(document).data("keycount");l++;b(document).data("keycount",l);t=b.trim(b(":focus").text().toLowerCase()).substring(0,1);if(t!=g[p.keyCode]){l=1;b(document).data("keycount",l)}else{if(l>=c.length){b(document).data("keycount",0);if(l>c.length){l=1}}}o.eq(c[l-1]).focus()}}if(/(13|32|^9$)/.test(p.keyCode)&&s){if(!/(32)/.test(p.keyCode)){p.preventDefault()}if(!m.options.liveSearch){b(":focus").click()}else{if(!/(32)/.test(p.keyCode)){m.$menu.find(".active a").click();q.focus()}}b(document).data("keycount",0)}if((/(^9$|27)/.test(p.keyCode)&&s&&(m.multiple||m.options.liveSearch))||(/(27)/.test(p.keyCode)&&!s)){m.$menu.parent().removeClass("open");m.$button.focus()}},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},destroy:function(){this.$newElement.remove();this.$element.remove()}};b.fn.selectpicker=function(e,f){var c=arguments;var g;var d=this.each(function(){if(b(this).is("select")){var m=b(this),l=m.data("selectpicker"),h=typeof e=="object"&&e;if(!l){m.data("selectpicker",(l=new a(this,h,f)))}else{if(h){for(var j in h){l.options[j]=h[j]}}}if(typeof e=="string"){var k=e;if(l[k] instanceof Function){[].shift.apply(c);g=l[k].apply(l,c)}else{g=l.options[k]}}}});if(g!==undefined){return g}else{return d}};b.fn.selectpicker.defaults={style:"btn-default",size:"auto",title:null,selectedTextFormat:"values",noneSelectedText:"Nothing selected",countSelectedText:"{0} of {1} selected",width:false,container:false,hideDisabled:false,showSubtext:false,showIcon:true,showContent:true,dropupAuto:true,header:false,liveSearch:false,multipleSeparator:", ",iconBase:"glyphicon",tickIcon:"glyphicon-ok"};b(document).data("keycount",0).on("keydown",".bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bootstrap-select-searchbox input",a.prototype.keydown).on("focusin.modal",".bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bootstrap-select-searchbox input",function(c){c.stopPropagation()})}(window.jQuery); \ No newline at end of file +!function(a){"use strict";function b(a,b){return a.toUpperCase().indexOf(b.toUpperCase())>-1}function c(b){var c=[{re:/[\xC0-\xC6]/g,ch:"A"},{re:/[\xE0-\xE6]/g,ch:"a"},{re:/[\xC8-\xCB]/g,ch:"E"},{re:/[\xE8-\xEB]/g,ch:"e"},{re:/[\xCC-\xCF]/g,ch:"I"},{re:/[\xEC-\xEF]/g,ch:"i"},{re:/[\xD2-\xD6]/g,ch:"O"},{re:/[\xF2-\xF6]/g,ch:"o"},{re:/[\xD9-\xDC]/g,ch:"U"},{re:/[\xF9-\xFC]/g,ch:"u"},{re:/[\xC7-\xE7]/g,ch:"c"},{re:/[\xD1]/g,ch:"N"},{re:/[\xF1]/g,ch:"n"}];return a.each(c,function(){b=b.replace(this.re,this.ch)}),b}function d(a){var b={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},c="(?:"+Object.keys(b).join("|")+")",d=new RegExp(c),e=new RegExp(c,"g"),f=null==a?"":""+a;return d.test(f)?f.replace(e,function(a){return b[a]}):f}function e(b,c){var d=arguments,e=b,b=d[0],c=d[1];[].shift.apply(d),"undefined"==typeof b&&(b=e);var g,h=this.each(function(){var e=a(this);if(e.is("select")){var h=e.data("selectpicker"),i="object"==typeof b&&b;if(h){if(i)for(var j in i)i.hasOwnProperty(j)&&(h.options[j]=i[j])}else{var k=a.extend({},f.DEFAULTS,a.fn.selectpicker.defaults||{},e.data(),i);e.data("selectpicker",h=new f(this,k,c))}"string"==typeof b&&(g=h[b]instanceof Function?h[b].apply(h,d):h.options[b])}});return"undefined"!=typeof g?g:h}a.expr[":"].icontains=function(c,d,e){return b(a(c).text(),e[3])},a.expr[":"].aicontains=function(c,d,e){return b(a(c).data("normalizedText")||a(c).text(),e[3])};var f=function(b,c,d){d&&(d.stopPropagation(),d.preventDefault()),this.$element=a(b),this.$newElement=null,this.$button=null,this.$menu=null,this.$lis=null,this.options=c,null===this.options.title&&(this.options.title=this.$element.attr("title")),this.val=f.prototype.val,this.render=f.prototype.render,this.refresh=f.prototype.refresh,this.setStyle=f.prototype.setStyle,this.selectAll=f.prototype.selectAll,this.deselectAll=f.prototype.deselectAll,this.destroy=f.prototype.remove,this.remove=f.prototype.remove,this.show=f.prototype.show,this.hide=f.prototype.hide,this.init()};f.VERSION="1.6.3",f.DEFAULTS={noneSelectedText:"Nothing selected",noneResultsText:"No results match",countSelectedText:function(a){return 1==a?"{0} item selected":"{0} items selected"},maxOptionsText:function(a,b){var c=[];return c[0]=1==a?"Limit reached ({n} item max)":"Limit reached ({n} items max)",c[1]=1==b?"Group limit reached ({n} item max)":"Group limit reached ({n} items max)",c},selectAllText:"Select All",deselectAllText:"Deselect All",multipleSeparator:", ",style:"btn-default",size:"auto",title:null,selectedTextFormat:"values",width:!1,container:!1,hideDisabled:!1,showSubtext:!1,showIcon:!0,showContent:!0,dropupAuto:!0,header:!1,liveSearch:!1,actionsBox:!1,iconBase:"glyphicon",tickIcon:"glyphicon-ok",maxOptions:!1,mobile:!1,selectOnTab:!1,dropdownAlignRight:!1,searchAccentInsensitive:!1},f.prototype={constructor:f,init:function(){var b=this,c=this.$element.attr("id");this.$element.hide(),this.multiple=this.$element.prop("multiple"),this.autofocus=this.$element.prop("autofocus"),this.$newElement=this.createView(),this.$element.after(this.$newElement),this.$menu=this.$newElement.find("> .dropdown-menu"),this.$button=this.$newElement.find("> button"),this.$searchbox=this.$newElement.find("input"),this.options.dropdownAlignRight&&this.$menu.addClass("dropdown-menu-right"),"undefined"!=typeof c&&(this.$button.attr("data-id",c),a('label[for="'+c+'"]').click(function(a){a.preventDefault(),b.$button.focus()})),this.checkDisabled(),this.clickListener(),this.options.liveSearch&&this.liveSearchListener(),this.render(),this.liHeight(),this.setStyle(),this.setWidth(),this.options.container&&this.selectPosition(),this.$menu.data("this",this),this.$newElement.data("this",this),this.options.mobile&&this.mobile()},createDropdown:function(){var b=this.multiple?" show-tick":"",c=this.$element.parent().hasClass("input-group")?" input-group-btn":"",d=this.autofocus?" autofocus":"",e=this.$element.parents().hasClass("form-group-lg")?" btn-lg":this.$element.parents().hasClass("form-group-sm")?" btn-sm":"",f=this.options.header?'
    '+this.options.header+"
    ":"",g=this.options.liveSearch?'':"",h=this.options.actionsBox?'
    ":"",i='
    ';return a(i)},createView:function(){var a=this.createDropdown(),b=this.createLi();return a.find("ul").append(b),a},reloadLi:function(){this.destroyLi();var a=this.createLi();this.$menu.find("ul").append(a)},destroyLi:function(){this.$menu.find("li").remove()},createLi:function(){var b=this,e=[],f=0,g=function(a,b,c){return""+a+""},h=function(a,e,f,g){var h=c(d(a));return''+a+''};return this.$element.find("option").each(function(){var c=a(this),d=c.attr("class")||"",i=c.attr("style"),j=c.data("content")?c.data("content"):c.html(),k="undefined"!=typeof c.data("subtext")?''+c.data("subtext")+"":"",l="undefined"!=typeof c.data("icon")?' ':"",m=c.is(":disabled")||c.parent().is(":disabled"),n=c[0].index;if(""!==l&&m&&(l=""+l+""),c.data("content")||(j=l+''+j+k+""),!b.options.hideDisabled||!m)if(c.parent().is("optgroup")&&c.data("divider")!==!0){if(0===c.index()){f+=1;var o=c.parent().attr("label"),p="undefined"!=typeof c.parent().data("subtext")?''+c.parent().data("subtext")+"":"",q=c.parent().data("icon")?' ':"";o=q+''+o+p+"",0!==n&&e.length>0&&e.push(g("",null,"divider")),e.push(g(o,null,"dropdown-header"))}e.push(g(h(j,"opt "+d,i,f),n))}else e.push(c.data("divider")===!0?g("",n,"divider"):c.data("hidden")===!0?g(h(j,d,i),n,"hide is-hidden"):g(h(j,d,i),n))}),this.multiple||0!==this.$element.find("option:selected").length||this.options.title||this.$element.find("option").eq(0).prop("selected",!0).attr("selected","selected"),a(e.join(""))},findLis:function(){return null==this.$lis&&(this.$lis=this.$menu.find("li")),this.$lis},render:function(b){var c=this;b!==!1&&this.$element.find("option").each(function(b){c.setDisabled(b,a(this).is(":disabled")||a(this).parent().is(":disabled")),c.setSelected(b,a(this).is(":selected"))}),this.tabIndex();var e=this.options.hideDisabled?":not([disabled])":"",f=this.$element.find("option:selected"+e).map(function(){var b,d=a(this),e=d.data("icon")&&c.options.showIcon?' ':"";return b=c.options.showSubtext&&d.attr("data-subtext")&&!c.multiple?' '+d.data("subtext")+"":"",d.data("content")&&c.options.showContent?d.data("content"):"undefined"!=typeof d.attr("title")?d.attr("title"):e+d.html()+b}).toArray(),g=this.multiple?f.join(this.options.multipleSeparator):f[0];if(this.multiple&&this.options.selectedTextFormat.indexOf("count")>-1){var h=this.options.selectedTextFormat.split(">");if(h.length>1&&f.length>h[1]||1==h.length&&f.length>=2){e=this.options.hideDisabled?", [disabled]":"";var i=this.$element.find("option").not('[data-divider="true"], [data-hidden="true"]'+e).length,j="function"==typeof this.options.countSelectedText?this.options.countSelectedText(f.length,i):this.options.countSelectedText;g=j.replace("{0}",f.length.toString()).replace("{1}",i.toString())}}this.options.title=this.$element.attr("title"),"static"==this.options.selectedTextFormat&&(g=this.options.title),g||(g="undefined"!=typeof this.options.title?this.options.title:this.options.noneSelectedText),this.$button.attr("title",d(g)),this.$newElement.find(".filter-option").html(g)},setStyle:function(a,b){this.$element.attr("class")&&this.$newElement.addClass(this.$element.attr("class").replace(/selectpicker|mobile-device|validate\[.*\]/gi,""));var c=a?a:this.options.style;"add"==b?this.$button.addClass(c):"remove"==b?this.$button.removeClass(c):(this.$button.removeClass(this.options.style),this.$button.addClass(c))},liHeight:function(){if(this.options.size!==!1){var a=this.$menu.parent().clone().find("> .dropdown-toggle").prop("autofocus",!1).end().appendTo("body"),b=a.addClass("open").find("> .dropdown-menu"),c=b.find("li").not(".divider").not(".dropdown-header").filter(":visible").children("a").outerHeight(),d=this.options.header?b.find(".popover-title").outerHeight():0,e=this.options.liveSearch?b.find(".bs-searchbox").outerHeight():0,f=this.options.actionsBox?b.find(".bs-actionsbox").outerHeight():0;a.remove(),this.$newElement.data("liHeight",c).data("headerHeight",d).data("searchHeight",e).data("actionsHeight",f)}},setSize:function(){this.findLis();var b,c,d,e=this,f=this.$menu,g=f.find(".inner"),h=this.$newElement.outerHeight(),i=this.$newElement.data("liHeight"),j=this.$newElement.data("headerHeight"),k=this.$newElement.data("searchHeight"),l=this.$newElement.data("actionsHeight"),m=this.$lis.filter(".divider").outerHeight(!0),n=parseInt(f.css("padding-top"))+parseInt(f.css("padding-bottom"))+parseInt(f.css("border-top-width"))+parseInt(f.css("border-bottom-width")),o=this.options.hideDisabled?", .disabled":"",p=a(window),q=n+parseInt(f.css("margin-top"))+parseInt(f.css("margin-bottom"))+2,r=function(){c=e.$newElement.offset().top-p.scrollTop(),d=p.height()-c-h};if(r(),this.options.header&&f.css("padding-top",0),"auto"==this.options.size){var s=function(){var a,h=e.$lis.not(".hide");r(),b=d-q,e.options.dropupAuto&&e.$newElement.toggleClass("dropup",c>d&&b-q3?3*i+q-2:0,f.css({"max-height":b+"px",overflow:"hidden","min-height":a+j+k+l+"px"}),g.css({"max-height":b-j-k-l-n+"px","overflow-y":"auto","min-height":Math.max(a-n,0)+"px"})};s(),this.$searchbox.off("input.getSize propertychange.getSize").on("input.getSize propertychange.getSize",s),a(window).off("resize.getSize").on("resize.getSize",s),a(window).off("scroll.getSize").on("scroll.getSize",s)}else if(this.options.size&&"auto"!=this.options.size&&f.find("li"+o).length>this.options.size){var t=this.$lis.not(".divider"+o).find(" > *").slice(0,this.options.size).last().parent().index(),u=this.$lis.slice(0,t+1).filter(".divider").length;b=i*this.options.size+u*m+n,e.options.dropupAuto&&this.$newElement.toggleClass("dropup",c>d&&b .dropdown-menu").css("width"),c=a.css("width","auto").find("> button").css("width");a.remove(),this.$newElement.css("width",Math.max(parseInt(b),parseInt(c))+"px")}else"fit"==this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width","").addClass("fit-width")):this.options.width?(this.$menu.css("min-width",""),this.$newElement.css("width",this.options.width)):(this.$menu.css("min-width",""),this.$newElement.css("width",""));this.$newElement.hasClass("fit-width")&&"fit"!==this.options.width&&this.$newElement.removeClass("fit-width")},selectPosition:function(){var b,c,d=this,e="
    ",f=a(e),g=function(a){f.addClass(a.attr("class").replace(/form-control/gi,"")).toggleClass("dropup",a.hasClass("dropup")),b=a.offset(),c=a.hasClass("dropup")?0:a[0].offsetHeight,f.css({top:b.top+c,left:b.left,width:a[0].offsetWidth,position:"absolute"})};this.$newElement.on("click",function(){d.isDisabled()||(g(a(this)),f.appendTo(d.options.container),f.toggleClass("open",!a(this).hasClass("open")),f.append(d.$menu))}),a(window).resize(function(){g(d.$newElement)}),a(window).on("scroll",function(){g(d.$newElement)}),a("html").on("click",function(b){a(b.target).closest(d.$newElement).length<1&&f.removeClass("open")})},setSelected:function(a,b){this.findLis(),this.$lis.filter('[data-original-index="'+a+'"]').toggleClass("selected",b)},setDisabled:function(a,b){this.findLis(),b?this.$lis.filter('[data-original-index="'+a+'"]').addClass("disabled").find("a").attr("href","#").attr("tabindex",-1):this.$lis.filter('[data-original-index="'+a+'"]').removeClass("disabled").find("a").removeAttr("href").attr("tabindex",0)},isDisabled:function(){return this.$element.is(":disabled")},checkDisabled:function(){var a=this;this.isDisabled()?this.$button.addClass("disabled").attr("tabindex",-1):(this.$button.hasClass("disabled")&&this.$button.removeClass("disabled"),-1==this.$button.attr("tabindex")&&(this.$element.data("tabindex")||this.$button.removeAttr("tabindex"))),this.$button.click(function(){return!a.isDisabled()})},tabIndex:function(){this.$element.is("[tabindex]")&&(this.$element.data("tabindex",this.$element.attr("tabindex")),this.$button.attr("tabindex",this.$element.data("tabindex")))},clickListener:function(){var b=this;this.$newElement.on("touchstart.dropdown",".dropdown-menu",function(a){a.stopPropagation()}),this.$newElement.on("click",function(){b.setSize(),b.options.liveSearch||b.multiple||setTimeout(function(){b.$menu.find(".selected a").focus()},10)}),this.$menu.on("click","li a",function(c){var d=a(this),e=d.parent().data("originalIndex"),f=b.$element.val(),g=b.$element.prop("selectedIndex");if(b.multiple&&c.stopPropagation(),c.preventDefault(),!b.isDisabled()&&!d.parent().hasClass("disabled")){var h=b.$element.find("option"),i=h.eq(e),j=i.prop("selected"),k=i.parent("optgroup"),l=b.options.maxOptions,m=k.data("maxOptions")||!1;if(b.multiple){if(i.prop("selected",!j),b.setSelected(e,!j),d.blur(),l!==!1||m!==!1){var n=l
    ');q[2]&&(r=r.replace("{var}",q[2][l>1?0:1]),s=s.replace("{var}",q[2][m>1?0:1])),i.prop("selected",!1),b.$menu.append(t),l&&n&&(t.append(a("
    "+r+"
    ")),b.$element.trigger("maxReached.bs.select")),m&&o&&(t.append(a("
    "+s+"
    ")),b.$element.trigger("maxReachedGrp.bs.select")),setTimeout(function(){b.setSelected(e,!1)},10),t.delay(750).fadeOut(300,function(){a(this).remove()})}}}else h.prop("selected",!1),i.prop("selected",!0),b.$menu.find(".selected").removeClass("selected"),b.setSelected(e,!0);b.multiple?b.options.liveSearch&&b.$searchbox.focus():b.$button.focus(),(f!=b.$element.val()&&b.multiple||g!=b.$element.prop("selectedIndex")&&!b.multiple)&&b.$element.change()}}),this.$menu.on("click","li.disabled a, .popover-title, .popover-title :not(.close)",function(a){a.target==this&&(a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus())}),this.$menu.on("click","li.divider, li.dropdown-header",function(a){a.preventDefault(),a.stopPropagation(),b.options.liveSearch?b.$searchbox.focus():b.$button.focus()}),this.$menu.on("click",".popover-title .close",function(){b.$button.focus()}),this.$searchbox.on("click",function(a){a.stopPropagation()}),this.$menu.on("click",".actions-btn",function(c){b.options.liveSearch?b.$searchbox.focus():b.$button.focus(),c.preventDefault(),c.stopPropagation(),a(this).is(".bs-select-all")?b.selectAll():b.deselectAll(),b.$element.change()}),this.$element.change(function(){b.render(!1)})},liveSearchListener:function(){var b=this,e=a('
  • ');this.$newElement.on("click.dropdown.data-api touchstart.dropdown.data-api",function(){b.$menu.find(".active").removeClass("active"),b.$searchbox.val()&&(b.$searchbox.val(""),b.$lis.not(".is-hidden").removeClass("hide"),e.parent().length&&e.remove()),b.multiple||b.$menu.find(".selected").addClass("active"),setTimeout(function(){b.$searchbox.focus()},10)}),this.$searchbox.on("click.dropdown.data-api focus.dropdown.data-api touchend.dropdown.data-api",function(a){a.stopPropagation()}),this.$searchbox.on("input propertychange",function(){b.$searchbox.val()?(b.options.searchAccentInsensitive?b.$lis.not(".is-hidden").removeClass("hide").find("a").not(":aicontains("+c(b.$searchbox.val())+")").parent().addClass("hide"):b.$lis.not(".is-hidden").removeClass("hide").find("a").not(":icontains("+b.$searchbox.val()+")").parent().addClass("hide"),b.$menu.find("li").filter(":visible:not(.no-results)").length?e.parent().length&&e.remove():(e.parent().length&&e.remove(),e.html(b.options.noneResultsText+' "'+d(b.$searchbox.val())+'"').show(),b.$menu.find("li").last().after(e))):(b.$lis.not(".is-hidden").removeClass("hide"),e.parent().length&&e.remove()),b.$menu.find("li.active").removeClass("active"),b.$menu.find("li").filter(":visible:not(.divider)").eq(0).addClass("active").find("a").focus(),a(this).focus()})},val:function(a){return"undefined"!=typeof a?(this.$element.val(a),this.render(),this.$element):this.$element.val()},selectAll:function(){this.findLis(),this.$lis.not(".divider").not(".disabled").not(".selected").filter(":visible").find("a").click()},deselectAll:function(){this.findLis(),this.$lis.not(".divider").not(".disabled").filter(".selected").filter(":visible").find("a").click()},keydown:function(b){var d,e,f,g,h,i,j,k,l,m=a(this),n=m.is("input")?m.parent().parent():m.parent(),o=n.data("this"),p={32:" ",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",59:";",65:"a",66:"b",67:"c",68:"d",69:"e",70:"f",71:"g",72:"h",73:"i",74:"j",75:"k",76:"l",77:"m",78:"n",79:"o",80:"p",81:"q",82:"r",83:"s",84:"t",85:"u",86:"v",87:"w",88:"x",89:"y",90:"z",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"};if(o.options.liveSearch&&(n=m.parent().parent()),o.options.container&&(n=o.$menu),d=a("[role=menu] li a",n),l=o.$menu.parent().hasClass("open"),!l&&/([0-9]|[A-z])/.test(String.fromCharCode(b.keyCode))&&(o.options.container?o.$newElement.trigger("click"):(o.setSize(),o.$menu.parent().addClass("open"),l=!0),o.$searchbox.focus()),o.options.liveSearch&&(/(^9$|27)/.test(b.keyCode.toString(10))&&l&&0===o.$menu.find(".active").length&&(b.preventDefault(),o.$menu.parent().removeClass("open"),o.$button.focus()),d=a("[role=menu] li:not(.divider):not(.dropdown-header):visible",n),m.val()||/(38|40)/.test(b.keyCode.toString(10))||0===d.filter(".active").length&&(d=o.$newElement.find("li").filter(o.options.searchAccentInsensitive?":aicontains("+c(p[b.keyCode])+")":":icontains("+p[b.keyCode]+")"))),d.length){if(/(38|40)/.test(b.keyCode.toString(10)))e=d.index(d.filter(":focus")),g=d.parent(":not(.disabled):visible").first().index(),h=d.parent(":not(.disabled):visible").last().index(),f=d.eq(e).parent().nextAll(":not(.disabled):visible").eq(0).index(),i=d.eq(e).parent().prevAll(":not(.disabled):visible").eq(0).index(),j=d.eq(f).parent().prevAll(":not(.disabled):visible").eq(0).index(),o.options.liveSearch&&(d.each(function(b){a(this).is(":not(.disabled)")&&a(this).data("index",b)}),e=d.index(d.filter(".active")),g=d.filter(":not(.disabled):visible").first().data("index"),h=d.filter(":not(.disabled):visible").last().data("index"),f=d.eq(e).nextAll(":not(.disabled):visible").eq(0).data("index"),i=d.eq(e).prevAll(":not(.disabled):visible").eq(0).data("index"),j=d.eq(f).prevAll(":not(.disabled):visible").eq(0).data("index")),k=m.data("prevIndex"),38==b.keyCode&&(o.options.liveSearch&&(e-=1),e!=j&&e>i&&(e=i),g>e&&(e=g),e==k&&(e=h)),40==b.keyCode&&(o.options.liveSearch&&(e+=1),-1==e&&(e=0),e!=j&&f>e&&(e=f),e>h&&(e=h),e==k&&(e=g)),m.data("prevIndex",e),o.options.liveSearch?(b.preventDefault(),m.is(".dropdown-toggle")||(d.removeClass("active"),d.eq(e).addClass("active").find("a").focus(),m.focus())):d.eq(e).focus();else if(!m.is("input")){var q,r,s=[];d.each(function(){a(this).parent().is(":not(.disabled)")&&a.trim(a(this).text().toLowerCase()).substring(0,1)==p[b.keyCode]&&s.push(a(this).parent().index())}),q=a(document).data("keycount"),q++,a(document).data("keycount",q),r=a.trim(a(":focus").text().toLowerCase()).substring(0,1),r!=p[b.keyCode]?(q=1,a(document).data("keycount",q)):q>=s.length&&(a(document).data("keycount",0),q>s.length&&(q=1)),d.eq(s[q-1]).focus()}(/(13|32)/.test(b.keyCode.toString(10))||/(^9$)/.test(b.keyCode.toString(10))&&o.options.selectOnTab)&&l&&(/(32)/.test(b.keyCode.toString(10))||b.preventDefault(),o.options.liveSearch?/(32)/.test(b.keyCode.toString(10))||(o.$menu.find(".active a").click(),m.focus()):a(":focus").click(),a(document).data("keycount",0)),(/(^9$|27)/.test(b.keyCode.toString(10))&&l&&(o.multiple||o.options.liveSearch)||/(27)/.test(b.keyCode.toString(10))&&!l)&&(o.$menu.parent().removeClass("open"),o.$button.focus())}},mobile:function(){this.$element.addClass("mobile-device").appendTo(this.$newElement),this.options.container&&this.$menu.hide()},refresh:function(){this.$lis=null,this.reloadLi(),this.render(),this.setWidth(),this.setStyle(),this.checkDisabled(),this.liHeight()},update:function(){this.reloadLi(),this.setWidth(),this.setStyle(),this.checkDisabled(),this.liHeight()},hide:function(){this.$newElement.hide()},show:function(){this.$newElement.show()},remove:function(){this.$newElement.remove(),this.$element.remove()}};var g=a.fn.selectpicker;a.fn.selectpicker=e,a.fn.selectpicker.Constructor=f,a.fn.selectpicker.noConflict=function(){return a.fn.selectpicker=g,this},a(document).data("keycount",0).on("keydown",".bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input",f.prototype.keydown).on("focusin.modal",".bootstrap-select [data-toggle=dropdown], .bootstrap-select [role=menu], .bs-searchbox input",function(a){a.stopPropagation()}),a(window).on("load.bs.select.data-api",function(){a(".selectpicker").each(function(){var b=a(this);e.call(b,b.data())})})}(jQuery); +//# sourceMappingURL=bootstrap-select.js.map \ No newline at end of file diff --git a/assets/js/vendor/jquery.scrollTo.min.js b/assets/js/vendor/jquery.scrollTo.min.js index 296ba0ef..d9f9b159 100644 --- a/assets/js/vendor/jquery.scrollTo.min.js +++ b/assets/js/vendor/jquery.scrollTo.min.js @@ -1,7 +1,7 @@ /** - * Copyright (c) 2007-2012 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com - * Dual licensed under MIT and GPL. + * Copyright (c) 2007-2014 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com + * Licensed under MIT * @author Ariel Flesler - * @version 1.4.4 + * @version 1.4.14 */ -;(function($){var h=$.scrollTo=function(a,b,c){$(window).scrollTo(a,b,c)};h.defaults={axis:'xy',duration:parseFloat($.fn.jquery)>=1.3?0:1,limit:true};h.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(e,f,g){if(typeof f=='object'){g=f;f=0}if(typeof g=='function')g={onAfter:g};if(e=='max')e=9e9;g=$.extend({},h.defaults,g);f=f||g.duration;g.queue=g.queue&&g.axis.length>1;if(g.queue)f/=2;g.offset=both(g.offset);g.over=both(g.over);return this._scrollable().each(function(){if(e==null)return;var d=this,$elem=$(d),targ=e,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}$.each(g.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=h.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(g.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=g.offset[pos]||0;if(g.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*g.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(g.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&g.queue){if(old!=attr[key])animate(g.onAfterFirst);delete attr[key]}});animate(g.onAfter);function animate(a){$elem.animate(attr,f,g.easing,a&&function(){a.call(this,e,g)})}}).end()};h.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery); \ No newline at end of file +;(function(k){'use strict';k(['jquery'],function($){var j=$.scrollTo=function(a,b,c){return $(window).scrollTo(a,b,c)};j.defaults={axis:'xy',duration:0,limit:!0};j.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(f,g,h){if(typeof g=='object'){h=g;g=0}if(typeof h=='function')h={onAfter:h};if(f=='max')f=9e9;h=$.extend({},j.defaults,h);g=g||h.duration;h.queue=h.queue&&h.axis.length>1;if(h.queue)g/=2;h.offset=both(h.offset);h.over=both(h.over);return this._scrollable().each(function(){if(f==null)return;var d=this,$elem=$(d),targ=f,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=win?$(targ):$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}var e=$.isFunction(h.offset)&&h.offset(d,targ)||h.offset;$.each(h.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=j.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(h.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=e[pos]||0;if(h.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*h.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(h.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&h.queue){if(old!=attr[key])animate(h.onAfterFirst);delete attr[key]}});animate(h.onAfter);function animate(a){$elem.animate(attr,g,h.easing,a&&function(){a.call(this,targ,h)})}}).end()};j.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}return j})}(typeof define==='function'&&define.amd?define:function(a,b){if(typeof module!=='undefined'&&module.exports){module.exports=b(require('jquery'))}else{b(jQuery)}})); \ No newline at end of file diff --git a/assets/js/vendor/mithril.js b/assets/js/vendor/mithril.js new file mode 100644 index 00000000..4c547994 --- /dev/null +++ b/assets/js/vendor/mithril.js @@ -0,0 +1,1012 @@ +var m = (function app(window, undefined) { + var OBJECT = "[object Object]", ARRAY = "[object Array]", STRING = "[object String]", FUNCTION = "function"; + var type = {}.toString; + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/; + var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/; + + // caching commonly used variables + var $document, $location, $requestAnimationFrame, $cancelAnimationFrame; + + // self invoking function needed because of the way mocks work + function initialize(window){ + $document = window.document; + $location = window.location; + $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout; + $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout; + } + + initialize(window); + + + /* + * @typedef {String} Tag + * A string that looks like -> div.classname#id[param=one][param2=two] + * Which describes a DOM node + */ + + /* + * + * @param {Tag} The DOM node tag + * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs + * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional) + * + */ + function m() { + var args = [].slice.call(arguments); + var hasAttrs = args[1] != null && type.call(args[1]) === OBJECT && !("tag" in args[1]) && !("subtree" in args[1]); + var attrs = hasAttrs ? args[1] : {}; + var classAttrName = "class" in attrs ? "class" : "className"; + var cell = {tag: "div", attrs: {}}; + var match, classes = []; + if (type.call(args[0]) != STRING) throw new Error("selector in m(selector, attrs, children) should be a string") + while (match = parser.exec(args[0])) { + if (match[1] === "" && match[2]) cell.tag = match[2]; + else if (match[1] === "#") cell.attrs.id = match[2]; + else if (match[1] === ".") classes.push(match[2]); + else if (match[3][0] === "[") { + var pair = attrParser.exec(match[3]); + cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true) + } + } + if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" "); + + + var children = hasAttrs ? args[2] : args[1]; + if (type.call(children) === ARRAY) { + cell.children = children + } + else { + cell.children = hasAttrs ? args.slice(2) : args.slice(1) + } + + for (var attrName in attrs) { + if (attrName === classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName]; + else cell.attrs[attrName] = attrs[attrName] + } + return cell + } + function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) { + //`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached` + //the diff algorithm can be summarized as this: + //1 - compare `data` and `cached` + //2 - if they are different, copy `data` to `cached` and update the DOM based on what the difference is + //3 - recursively apply this algorithm for every array and for the children of every virtual element + + //the `cached` data structure is essentially the same as the previous redraw's `data` data structure, with a few additions: + //- `cached` always has a property called `nodes`, which is a list of DOM elements that correspond to the data represented by the respective virtual element + //- in order to support attaching `nodes` as a property of `cached`, `cached` is *always* a non-primitive object, i.e. if the data was a string, then cached is a String instance. If data was `null` or `undefined`, cached is `new String("")` + //- `cached also has a `configContext` property, which is the state storage object exposed by config(element, isInitialized, context) + //- when `cached` is an Object, it represents a virtual element; when it's an Array, it represents a list of elements; when it's a String, Number or Boolean, it represents a text node + + //`parentElement` is a DOM element used for W3C DOM API calls + //`parentTag` is only used for handling a corner case for textarea values + //`parentCache` is used to remove nodes in some multi-node cases + //`parentIndex` and `index` are used to figure out the offset of nodes. They're artifacts from before arrays started being flattened and are likely refactorable + //`data` and `cached` are, respectively, the new and old nodes being diffed + //`shouldReattach` is a flag indicating whether a parent node was recreated (if so, and if this node is reused, then this node must reattach itself to the new parent) + //`editable` is a flag that indicates whether an ancestor is contenteditable + //`namespace` indicates the closest HTML namespace as it cascades down from an ancestor + //`configs` is a list of config functions to run after the topmost `build` call finishes running + + //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings + //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")} + //- it simplifies diffing code + //data.toString() is null if data is the return value of Console.log in Firefox + if (data == null || data.toString() == null) data = ""; + if (data.subtree === "retain") return cached; + var cachedType = type.call(cached), dataType = type.call(data); + if (cached == null || cachedType !== dataType) { + if (cached != null) { + if (parentCache && parentCache.nodes) { + var offset = index - parentIndex; + var end = offset + (dataType === ARRAY ? data : cached.nodes).length; + clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end)) + } + else if (cached.nodes) clear(cached.nodes, cached) + } + cached = new data.constructor; + if (cached.tag) cached = {}; //if constructor creates a virtual dom element, use a blank object as the base cached node instead of copying the virtual el (#277) + cached.nodes = [] + } + + if (dataType === ARRAY) { + //recursively flatten array + for (var i = 0, len = data.length; i < len; i++) { + if (type.call(data[i]) === ARRAY) { + data = data.concat.apply([], data); + i-- //check current index again and flatten until there are no more nested arrays at that index + } + } + + var nodes = [], intact = cached.length === data.length, subArrayCount = 0; + + //keys algorithm: sort elements without recreating them if keys are present + //1) create a map of all existing keys, and mark all for deletion + //2) add new keys to map and mark them for addition + //3) if key exists in new list, change action from deletion to a move + //4) for each key, handle its corresponding action as marked in previous steps + //5) copy unkeyed items into their respective gaps + var DELETION = 1, INSERTION = 2 , MOVE = 3; + var existing = {}, unkeyed = [], shouldMaintainIdentities = false; + for (var i = 0; i < cached.length; i++) { + if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) { + shouldMaintainIdentities = true; + existing[cached[i].attrs.key] = {action: DELETION, index: i} + } + } + if (shouldMaintainIdentities) { + if (data.indexOf(null) > -1) data = data.filter(function(x) {return x != null}) + + var keysDiffer = false + if (data.length != cached.length) keysDiffer = true + else for (var i = 0, cachedCell, dataCell; cachedCell = cached[i], dataCell = data[i]; i++) { + if (cachedCell.attrs && dataCell.attrs && cachedCell.attrs.key != dataCell.attrs.key) { + keysDiffer = true + break + } + } + + if (keysDiffer) { + for (var i = 0, len = data.length; i < len; i++) { + if (data[i] && data[i].attrs) { + if (data[i].attrs.key != null) { + var key = data[i].attrs.key; + if (!existing[key]) existing[key] = {action: INSERTION, index: i}; + else existing[key] = { + action: MOVE, + index: i, + from: existing[key].index, + element: parentElement.childNodes[existing[key].index] || $document.createElement("div") + } + } + else unkeyed.push({index: i, element: parentElement.childNodes[i] || $document.createElement("div")}) + } + } + var actions = [] + for (var prop in existing) actions.push(existing[prop]) + var changes = actions.sort(sortChanges); + var newCached = new Array(cached.length) + + for (var i = 0, change; change = changes[i]; i++) { + if (change.action === DELETION) { + clear(cached[change.index].nodes, cached[change.index]); + newCached.splice(change.index, 1) + } + if (change.action === INSERTION) { + var dummy = $document.createElement("div"); + dummy.key = data[change.index].attrs.key; + parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null); + newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) + } + + if (change.action === MOVE) { + if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { + parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null) + } + newCached[change.index] = cached[change.from] + } + } + for (var i = 0, len = unkeyed.length; i < len; i++) { + var change = unkeyed[i]; + parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null); + newCached[change.index] = cached[change.index] + } + cached = newCached; + cached.nodes = new Array(parentElement.childNodes.length); + for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes[i] = child + } + } + //end key algorithm + + for (var i = 0, cacheCount = 0, len = data.length; i < len; i++) { + //diff each item in the array + var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs); + if (item === undefined) continue; + if (!item.nodes.intact) intact = false; + if (item.$trusted) { + //fix offset of next element if item was a trusted string w/ more than one html element + //the first clause in the regexp matches elements + //the second clause (after the pipe) matches text nodes + subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || []).length + } + else subArrayCount += type.call(item) === ARRAY ? item.length : 1; + cached[cacheCount++] = item + } + if (!intact) { + //diff the array itself + + //update the list of DOM nodes by collecting the nodes from each item + for (var i = 0, len = data.length; i < len; i++) { + if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes) + } + //remove items from the end of the array if the new array is shorter than the old one + //if errors ever happen here, the issue is most likely a bug in the construction of the `cached` data structure somewhere earlier in the program + for (var i = 0, node; node = cached.nodes[i]; i++) { + if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]) + } + if (data.length < cached.length) cached.length = data.length; + cached.nodes = nodes + } + } + else if (data != null && dataType === OBJECT) { + if (!data.attrs) data.attrs = {}; + if (!cached.attrs) cached.attrs = {}; + + var dataAttrKeys = Object.keys(data.attrs) + var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0) + //if an element is different enough from the one in cache, recreate it + if (data.tag != cached.tag || dataAttrKeys.join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) { + if (cached.nodes.length) clear(cached.nodes); + if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) cached.configContext.onunload() + } + if (type.call(data.tag) != STRING) return; + + var node, isNew = cached.nodes.length === 0; + if (data.attrs.xmlns) namespace = data.attrs.xmlns; + else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg"; + else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML"; + if (isNew) { + if (data.attrs.is) node = namespace === undefined ? $document.createElement(data.tag, data.attrs.is) : $document.createElementNS(namespace, data.tag, data.attrs.is); + else node = namespace === undefined ? $document.createElement(data.tag) : $document.createElementNS(namespace, data.tag); + cached = { + tag: data.tag, + //set attributes first, then create children + attrs: hasKeys ? setAttributes(node, data.tag, data.attrs, {}, namespace) : data.attrs, + children: data.children != null && data.children.length > 0 ? + build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) : + data.children, + nodes: [node] + }; + if (cached.children && !cached.children.nodes) cached.children.nodes = []; + //edge case: setting value on ` -// element gets special love because it's special, and that's a fact! -.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { - height: @input-height; - padding: @padding-vertical @padding-horizontal; - font-size: @font-size; - line-height: @line-height; - border-radius: @border-radius; - - select& { - height: @input-height; - line-height: @input-height; - } - - textarea&, - select[multiple]& { - height: auto; - } -} +// Form validation states +// +// Used in forms.less to generate the form validation CSS for warnings, errors, +// and successes. + +.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) { + // Color the label and help text + .help-block, + .control-label, + .radio, + .checkbox, + .radio-inline, + .checkbox-inline { + color: @text-color; + } + // Set the border and box shadow on specific inputs to match + .form-control { + border-color: @border-color; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken(@border-color, 10%); + @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%); + .box-shadow(@shadow); + } + } + // Set validation states also for addons + .input-group-addon { + color: @text-color; + border-color: @border-color; + background-color: @background-color; + } + // Optional feedback icon + .form-control-feedback { + color: @text-color; + } +} + + +// Form control focus state +// +// Generate a customized focus state and for any input with the specified color, +// which defaults to the `@input-border-focus` variable. +// +// We highly encourage you to not customize the default value, but instead use +// this to tweak colors on an as-needed basis. This aesthetic change is based on +// WebKit's default styles, but applicable to a wider range of browsers. Its +// usability and accessibility should be taken into account with any change. +// +// Example usage: change the default blue border and shadow to white for better +// contrast against a dark gray background. +.form-control-focus(@color: @input-border-focus) { + @color-rgba: rgba(red(@color), green(@color), blue(@color), .6); + &:focus { + border-color: @color; + outline: 0; + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}"); + } +} + +// Form control sizing +// +// Relative text size, padding, and border-radii changes for form controls. For +// horizontal sizing, wrap controls in the predefined grid classes. `` background color -@input-bg: #fff; -//** `` background color -@input-bg-disabled: @gray-lighter; - -//** Text color for ``s -@input-color: @gray; -//** `` border color -@input-border: #ccc; -//** `` border radius -@input-border-radius: @border-radius-base; -//** Border color for inputs on focus -@input-border-focus: #66afe9; - -//** Placeholder text color -@input-color-placeholder: @gray-light; - -//** Default `.form-control` height -@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2); -//** Large `.form-control` height -@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2); -//** Small `.form-control` height -@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2); - -@legend-color: @gray-dark; -@legend-border-color: #e5e5e5; - -//** Background color for textual input addons -@input-group-addon-bg: @gray-lighter; -//** Border color for textual input addons -@input-group-addon-border-color: @input-border; - - -//== Dropdowns -// -//## Dropdown menu container and contents. - -//** Background for the dropdown menu. -@dropdown-bg: #fff; -//** Dropdown menu `border-color`. -@dropdown-border: rgba(0,0,0,.15); -//** Dropdown menu `border-color` **for IE8**. -@dropdown-fallback-border: #ccc; -//** Divider color for between dropdown items. -@dropdown-divider-bg: #e5e5e5; - -//** Dropdown link text color. -@dropdown-link-color: @gray-dark; -//** Hover color for dropdown links. -@dropdown-link-hover-color: darken(@gray-dark, 5%); -//** Hover background for dropdown links. -@dropdown-link-hover-bg: #f5f5f5; - -//** Active dropdown menu item text color. -@dropdown-link-active-color: @component-active-color; -//** Active dropdown menu item background color. -@dropdown-link-active-bg: @component-active-bg; - -//** Disabled dropdown menu item background color. -@dropdown-link-disabled-color: @gray-light; - -//** Text color for headers within dropdown menus. -@dropdown-header-color: @gray-light; - -//** Deprecated `@dropdown-caret-color` as of v3.1.0 -@dropdown-caret-color: #000; - - -//-- Z-index master list -// -// Warning: Avoid customizing these values. They're used for a bird's eye view -// of components dependent on the z-axis and are designed to all work together. -// -// Note: These variables are not generated into the Customizer. - -@zindex-navbar: 1000; -@zindex-dropdown: 1000; -@zindex-popover: 1060; -@zindex-tooltip: 1070; -@zindex-navbar-fixed: 1030; -@zindex-modal-background: 1040; -@zindex-modal: 1050; - - -//== Media queries breakpoints -// -//## Define the breakpoints at which your layout will change, adapting to different screen sizes. - -// Extra small screen / phone -//** Deprecated `@screen-xs` as of v3.0.1 -@screen-xs: 480px; -//** Deprecated `@screen-xs-min` as of v3.2.0 -@screen-xs-min: @screen-xs; -//** Deprecated `@screen-phone` as of v3.0.1 -@screen-phone: @screen-xs-min; - -// Small screen / tablet -//** Deprecated `@screen-sm` as of v3.0.1 -@screen-sm: 768px; -@screen-sm-min: @screen-sm; -//** Deprecated `@screen-tablet` as of v3.0.1 -@screen-tablet: @screen-sm-min; - -// Medium screen / desktop -//** Deprecated `@screen-md` as of v3.0.1 -@screen-md: 992px; -@screen-md-min: @screen-md; -//** Deprecated `@screen-desktop` as of v3.0.1 -@screen-desktop: @screen-md-min; - -// Large screen / wide desktop -//** Deprecated `@screen-lg` as of v3.0.1 -@screen-lg: 1200px; -@screen-lg-min: @screen-lg; -//** Deprecated `@screen-lg-desktop` as of v3.0.1 -@screen-lg-desktop: @screen-lg-min; - -// So media queries don't overlap when required, provide a maximum -@screen-xs-max: (@screen-sm-min - 1); -@screen-sm-max: (@screen-md-min - 1); -@screen-md-max: (@screen-lg-min - 1); - - -//== Grid system -// -//## Define your custom responsive grid. - -//** Number of columns in the grid. -@grid-columns: 12; -//** Padding between columns. Gets divided in half for the left and right. -@grid-gutter-width: 30px; -// Navbar collapse -//** Point at which the navbar becomes uncollapsed. -@grid-float-breakpoint: @screen-sm-min; -//** Point at which the navbar begins collapsing. -@grid-float-breakpoint-max: (@grid-float-breakpoint - 1); - - -//== Container sizes -// -//## Define the maximum width of `.container` for different screen sizes. - -// Small screen / tablet -@container-tablet: ((720px + @grid-gutter-width)); -//** For `@screen-sm-min` and up. -@container-sm: @container-tablet; - -// Medium screen / desktop -@container-desktop: ((940px + @grid-gutter-width)); -//** For `@screen-md-min` and up. -@container-md: @container-desktop; - -// Large screen / wide desktop -@container-large-desktop: ((1140px + @grid-gutter-width)); -//** For `@screen-lg-min` and up. -@container-lg: @container-large-desktop; - - -//== Navbar -// -//## - -// Basics of a navbar -@navbar-height: 50px; -@navbar-margin-bottom: @line-height-computed; -@navbar-border-radius: @border-radius-base; -@navbar-padding-horizontal: floor((@grid-gutter-width / 2)); -@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2); -@navbar-collapse-max-height: 340px; - -@navbar-default-color: #777; -@navbar-default-bg: #f8f8f8; -@navbar-default-border: darken(@navbar-default-bg, 6.5%); - -// Navbar links -@navbar-default-link-color: #777; -@navbar-default-link-hover-color: #333; -@navbar-default-link-hover-bg: transparent; -@navbar-default-link-active-color: #555; -@navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%); -@navbar-default-link-disabled-color: #ccc; -@navbar-default-link-disabled-bg: transparent; - -// Navbar brand label -@navbar-default-brand-color: @navbar-default-link-color; -@navbar-default-brand-hover-color: darken(@navbar-default-brand-color, 10%); -@navbar-default-brand-hover-bg: transparent; - -// Navbar toggle -@navbar-default-toggle-hover-bg: #ddd; -@navbar-default-toggle-icon-bar-bg: #888; -@navbar-default-toggle-border-color: #ddd; - - -// Inverted navbar -// Reset inverted navbar basics -@navbar-inverse-color: @gray-light; -@navbar-inverse-bg: #222; -@navbar-inverse-border: darken(@navbar-inverse-bg, 10%); - -// Inverted navbar links -@navbar-inverse-link-color: @gray-light; -@navbar-inverse-link-hover-color: #fff; -@navbar-inverse-link-hover-bg: transparent; -@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color; -@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%); -@navbar-inverse-link-disabled-color: #444; -@navbar-inverse-link-disabled-bg: transparent; - -// Inverted navbar brand label -@navbar-inverse-brand-color: @navbar-inverse-link-color; -@navbar-inverse-brand-hover-color: #fff; -@navbar-inverse-brand-hover-bg: transparent; - -// Inverted navbar toggle -@navbar-inverse-toggle-hover-bg: #333; -@navbar-inverse-toggle-icon-bar-bg: #fff; -@navbar-inverse-toggle-border-color: #333; - - -//== Navs -// -//## - -//=== Shared nav styles -@nav-link-padding: 10px 15px; -@nav-link-hover-bg: @gray-lighter; - -@nav-disabled-link-color: @gray-light; -@nav-disabled-link-hover-color: @gray-light; - -@nav-open-link-hover-color: #fff; - -//== Tabs -@nav-tabs-border-color: #ddd; - -@nav-tabs-link-hover-border-color: @gray-lighter; - -@nav-tabs-active-link-hover-bg: @body-bg; -@nav-tabs-active-link-hover-color: @gray; -@nav-tabs-active-link-hover-border-color: #ddd; - -@nav-tabs-justified-link-border-color: #ddd; -@nav-tabs-justified-active-link-border-color: @body-bg; - -//== Pills -@nav-pills-border-radius: @border-radius-base; -@nav-pills-active-link-hover-bg: @component-active-bg; -@nav-pills-active-link-hover-color: @component-active-color; - - -//== Pagination -// -//## - -@pagination-color: @link-color; -@pagination-bg: #fff; -@pagination-border: #ddd; - -@pagination-hover-color: @link-hover-color; -@pagination-hover-bg: @gray-lighter; -@pagination-hover-border: #ddd; - -@pagination-active-color: #fff; -@pagination-active-bg: @brand-primary; -@pagination-active-border: @brand-primary; - -@pagination-disabled-color: @gray-light; -@pagination-disabled-bg: #fff; -@pagination-disabled-border: #ddd; - - -//== Pager -// -//## - -@pager-bg: @pagination-bg; -@pager-border: @pagination-border; -@pager-border-radius: 15px; - -@pager-hover-bg: @pagination-hover-bg; - -@pager-active-bg: @pagination-active-bg; -@pager-active-color: @pagination-active-color; - -@pager-disabled-color: @pagination-disabled-color; - - -//== Jumbotron -// -//## - -@jumbotron-padding: 30px; -@jumbotron-color: inherit; -@jumbotron-bg: @gray-lighter; -@jumbotron-heading-color: inherit; -@jumbotron-font-size: ceil((@font-size-base * 1.5)); - - -//== Form states and alerts -// -//## Define colors for form feedback states and, by default, alerts. - -@state-success-text: #3c763d; -@state-success-bg: #dff0d8; -@state-success-border: darken(spin(@state-success-bg, -10), 5%); - -@state-info-text: #31708f; -@state-info-bg: #d9edf7; -@state-info-border: darken(spin(@state-info-bg, -10), 7%); - -@state-warning-text: #8a6d3b; -@state-warning-bg: #fcf8e3; -@state-warning-border: darken(spin(@state-warning-bg, -10), 5%); - -@state-danger-text: #a94442; -@state-danger-bg: #f2dede; -@state-danger-border: darken(spin(@state-danger-bg, -10), 5%); - - -//== Tooltips -// -//## - -//** Tooltip max width -@tooltip-max-width: 200px; -//** Tooltip text color -@tooltip-color: #fff; -//** Tooltip background color -@tooltip-bg: #000; -@tooltip-opacity: .9; - -//** Tooltip arrow width -@tooltip-arrow-width: 5px; -//** Tooltip arrow color -@tooltip-arrow-color: @tooltip-bg; - - -//== Popovers -// -//## - -//** Popover body background color -@popover-bg: #fff; -//** Popover maximum width -@popover-max-width: 276px; -//** Popover border color -@popover-border-color: rgba(0,0,0,.2); -//** Popover fallback border color -@popover-fallback-border-color: #ccc; - -//** Popover title background color -@popover-title-bg: darken(@popover-bg, 3%); - -//** Popover arrow width -@popover-arrow-width: 10px; -//** Popover arrow color -@popover-arrow-color: #fff; - -//** Popover outer arrow width -@popover-arrow-outer-width: (@popover-arrow-width + 1); -//** Popover outer arrow color -@popover-arrow-outer-color: fadein(@popover-border-color, 5%); -//** Popover outer arrow fallback color -@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%); - - -//== Labels -// -//## - -//** Default label background color -@label-default-bg: @gray-light; -//** Primary label background color -@label-primary-bg: @brand-primary; -//** Success label background color -@label-success-bg: @brand-success; -//** Info label background color -@label-info-bg: @brand-info; -//** Warning label background color -@label-warning-bg: @brand-warning; -//** Danger label background color -@label-danger-bg: @brand-danger; - -//** Default label text color -@label-color: #fff; -//** Default text color of a linked label -@label-link-hover-color: #fff; - - -//== Modals -// -//## - -//** Padding applied to the modal body -@modal-inner-padding: 15px; - -//** Padding applied to the modal title -@modal-title-padding: 15px; -//** Modal title line-height -@modal-title-line-height: @line-height-base; - -//** Background color of modal content area -@modal-content-bg: #fff; -//** Modal content border color -@modal-content-border-color: rgba(0,0,0,.2); -//** Modal content border color **for IE8** -@modal-content-fallback-border-color: #999; - -//** Modal backdrop background color -@modal-backdrop-bg: #000; -//** Modal backdrop opacity -@modal-backdrop-opacity: .5; -//** Modal header border color -@modal-header-border-color: #e5e5e5; -//** Modal footer border color -@modal-footer-border-color: @modal-header-border-color; - -@modal-lg: 900px; -@modal-md: 600px; -@modal-sm: 300px; - - -//== Alerts -// -//## Define alert colors, border radius, and padding. - -@alert-padding: 15px; -@alert-border-radius: @border-radius-base; -@alert-link-font-weight: bold; - -@alert-success-bg: @state-success-bg; -@alert-success-text: @state-success-text; -@alert-success-border: @state-success-border; - -@alert-info-bg: @state-info-bg; -@alert-info-text: @state-info-text; -@alert-info-border: @state-info-border; - -@alert-warning-bg: @state-warning-bg; -@alert-warning-text: @state-warning-text; -@alert-warning-border: @state-warning-border; - -@alert-danger-bg: @state-danger-bg; -@alert-danger-text: @state-danger-text; -@alert-danger-border: @state-danger-border; - - -//== Progress bars -// -//## - -//** Background color of the whole progress component -@progress-bg: #f5f5f5; -//** Progress bar text color -@progress-bar-color: #fff; - -//** Default progress bar color -@progress-bar-bg: @brand-primary; -//** Success progress bar color -@progress-bar-success-bg: @brand-success; -//** Warning progress bar color -@progress-bar-warning-bg: @brand-warning; -//** Danger progress bar color -@progress-bar-danger-bg: @brand-danger; -//** Info progress bar color -@progress-bar-info-bg: @brand-info; - - -//== List group -// -//## - -//** Background color on `.list-group-item` -@list-group-bg: #fff; -//** `.list-group-item` border color -@list-group-border: #ddd; -//** List group border radius -@list-group-border-radius: @border-radius-base; - -//** Background color of single list items on hover -@list-group-hover-bg: #f5f5f5; -//** Text color of active list items -@list-group-active-color: @component-active-color; -//** Background color of active list items -@list-group-active-bg: @component-active-bg; -//** Border color of active list elements -@list-group-active-border: @list-group-active-bg; -//** Text color for content within active list items -@list-group-active-text-color: lighten(@list-group-active-bg, 40%); - -//** Text color of disabled list items -@list-group-disabled-color: @gray-light; -//** Background color of disabled list items -@list-group-disabled-bg: @gray-lighter; -//** Text color for content within disabled list items -@list-group-disabled-text-color: @list-group-disabled-color; - -@list-group-link-color: #555; -@list-group-link-hover-color: @list-group-link-color; -@list-group-link-heading-color: #333; - - -//== Panels -// -//## - -@panel-bg: #fff; -@panel-body-padding: 15px; -@panel-heading-padding: 10px 15px; -@panel-footer-padding: @panel-heading-padding; -@panel-border-radius: @border-radius-base; - -//** Border color for elements within panels -@panel-inner-border: #ddd; -@panel-footer-bg: #f5f5f5; - -@panel-default-text: @gray-dark; -@panel-default-border: #ddd; -@panel-default-heading-bg: #f5f5f5; - -@panel-primary-text: #fff; -@panel-primary-border: @brand-primary; -@panel-primary-heading-bg: @brand-primary; - -@panel-success-text: @state-success-text; -@panel-success-border: @state-success-border; -@panel-success-heading-bg: @state-success-bg; - -@panel-info-text: @state-info-text; -@panel-info-border: @state-info-border; -@panel-info-heading-bg: @state-info-bg; - -@panel-warning-text: @state-warning-text; -@panel-warning-border: @state-warning-border; -@panel-warning-heading-bg: @state-warning-bg; - -@panel-danger-text: @state-danger-text; -@panel-danger-border: @state-danger-border; -@panel-danger-heading-bg: @state-danger-bg; - - -//== Thumbnails -// -//## - -//** Padding around the thumbnail image -@thumbnail-padding: 4px; -//** Thumbnail background color -@thumbnail-bg: @body-bg; -//** Thumbnail border color -@thumbnail-border: #ddd; -//** Thumbnail border radius -@thumbnail-border-radius: @border-radius-base; - -//** Custom text color for thumbnail captions -@thumbnail-caption-color: @text-color; -//** Padding around the thumbnail caption -@thumbnail-caption-padding: 9px; - - -//== Wells -// -//## - -@well-bg: #f5f5f5; -@well-border: darken(@well-bg, 7%); - - -//== Badges -// -//## - -@badge-color: #fff; -//** Linked badge text color on hover -@badge-link-hover-color: #fff; -@badge-bg: @gray-light; - -//** Badge text color in active nav link -@badge-active-color: @link-color; -//** Badge background color in active nav link -@badge-active-bg: #fff; - -@badge-font-weight: bold; -@badge-line-height: 1; -@badge-border-radius: 10px; - - -//== Breadcrumbs -// -//## - -@breadcrumb-padding-vertical: 8px; -@breadcrumb-padding-horizontal: 15px; -//** Breadcrumb background color -@breadcrumb-bg: #f5f5f5; -//** Breadcrumb text color -@breadcrumb-color: #ccc; -//** Text color of current page in the breadcrumb -@breadcrumb-active-color: @gray-light; -//** Textual separator for between breadcrumb elements -@breadcrumb-separator: "/"; - - -//== Carousel -// -//## - -@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6); - -@carousel-control-color: #fff; -@carousel-control-width: 15%; -@carousel-control-opacity: .5; -@carousel-control-font-size: 20px; - -@carousel-indicator-active-bg: #fff; -@carousel-indicator-border-color: #fff; - -@carousel-caption-color: #fff; - - -//== Close -// -//## - -@close-font-weight: bold; -@close-color: #000; -@close-text-shadow: 0 1px 0 #fff; - - -//== Code -// -//## - -@code-color: #c7254e; -@code-bg: #f9f2f4; - -@kbd-color: #fff; -@kbd-bg: #333; - -@pre-bg: #f5f5f5; -@pre-color: @gray-dark; -@pre-border-color: #ccc; -@pre-scrollable-max-height: 340px; - - -//== Type -// -//## - -//** Horizontal offset for forms and lists. -@component-offset-horizontal: 180px; -//** Text muted color -@text-muted: @gray-light; -//** Abbreviations and acronyms border color -@abbr-border-color: @gray-light; -//** Headings small color -@headings-small-color: @gray-light; -//** Blockquote small color -@blockquote-small-color: @gray-light; -//** Blockquote font size -@blockquote-font-size: (@font-size-base * 1.25); -//** Blockquote border color -@blockquote-border-color: @gray-lighter; -//** Page header border color -@page-header-border-color: @gray-lighter; -//** Width of horizontal description list titles -@dl-horizontal-offset: @component-offset-horizontal; -//** Horizontal line color. -@hr-border: @gray-lighter; - - +// +// Variables +// -------------------------------------------------- + + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +@gray-darker: lighten(#000, 13.5%); // #222 +@gray-dark: lighten(#000, 20%); // #333 +@gray: lighten(#000, 33.5%); // #555 +@gray-light: lighten(#000, 46.7%); // #777 +@gray-lighter: lighten(#000, 93.5%); // #eee + +@brand-primary: #428bca; +@brand-success: #5cb85c; +@brand-info: #5bc0de; +@brand-warning: #f0ad4e; +@brand-danger: #d9534f; + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for ``. +@body-bg: #fff; +//** Global text color on ``. +@text-color: @gray-dark; + +//** Global textual link color. +@link-color: @brand-primary; +//** Link hover color set via `darken()` function. +@link-hover-color: darken(@link-color, 15%); + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +@font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif; +@font-family-serif: Georgia, "Times New Roman", Times, serif; +//** Default monospace fonts for ``, ``, and `
    `.
    +@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
    +@font-family-base:        @font-family-sans-serif;
    +
    +@font-size-base:          14px;
    +@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
    +@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
    +
    +@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
    +@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
    +@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
    +@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
    +@font-size-h5:            @font-size-base;
    +@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
    +
    +//** Unit-less `line-height` for use in components like buttons.
    +@line-height-base:        1.428571429; // 20/14
    +//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
    +@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
    +
    +//** By default, this inherits from the ``.
    +@headings-font-family:    inherit;
    +@headings-font-weight:    500;
    +@headings-line-height:    1.1;
    +@headings-color:          inherit;
    +
    +
    +//== Iconography
    +//
    +//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
    +
    +//** Load fonts from this directory.
    +@icon-font-path:          "../fonts/";
    +//** File name for all font files.
    +@icon-font-name:          "glyphicons-halflings-regular";
    +//** Element ID within SVG icon file.
    +@icon-font-svg-id:        "glyphicons_halflingsregular";
    +
    +
    +//== Components
    +//
    +//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
    +
    +@padding-base-vertical:     6px;
    +@padding-base-horizontal:   12px;
    +
    +@padding-large-vertical:    10px;
    +@padding-large-horizontal:  16px;
    +
    +@padding-small-vertical:    5px;
    +@padding-small-horizontal:  10px;
    +
    +@padding-xs-vertical:       1px;
    +@padding-xs-horizontal:     5px;
    +
    +@line-height-large:         1.33;
    +@line-height-small:         1.5;
    +
    +@border-radius-base:        4px;
    +@border-radius-large:       6px;
    +@border-radius-small:       3px;
    +
    +//** Global color for active items (e.g., navs or dropdowns).
    +@component-active-color:    #fff;
    +//** Global background color for active items (e.g., navs or dropdowns).
    +@component-active-bg:       @brand-primary;
    +
    +//** Width of the `border` for generating carets that indicator dropdowns.
    +@caret-width-base:          4px;
    +//** Carets increase slightly in size for larger components.
    +@caret-width-large:         5px;
    +
    +
    +//== Tables
    +//
    +//## Customizes the `.table` component with basic values, each used across all table variations.
    +
    +//** Padding for ``s and ``s.
    +@table-cell-padding:            8px;
    +//** Padding for cells in `.table-condensed`.
    +@table-condensed-cell-padding:  5px;
    +
    +//** Default background color used for all tables.
    +@table-bg:                      transparent;
    +//** Background color used for `.table-striped`.
    +@table-bg-accent:               #f9f9f9;
    +//** Background color used for `.table-hover`.
    +@table-bg-hover:                #f5f5f5;
    +@table-bg-active:               @table-bg-hover;
    +
    +//** Border color for table and cell borders.
    +@table-border-color:            #ddd;
    +
    +
    +//== Buttons
    +//
    +//## For each of Bootstrap's buttons, define text, background and border color.
    +
    +@btn-font-weight:                normal;
    +
    +@btn-default-color:              #333;
    +@btn-default-bg:                 #fff;
    +@btn-default-border:             #ccc;
    +
    +@btn-primary-color:              #fff;
    +@btn-primary-bg:                 @brand-primary;
    +@btn-primary-border:             darken(@btn-primary-bg, 5%);
    +
    +@btn-success-color:              #fff;
    +@btn-success-bg:                 @brand-success;
    +@btn-success-border:             darken(@btn-success-bg, 5%);
    +
    +@btn-info-color:                 #fff;
    +@btn-info-bg:                    @brand-info;
    +@btn-info-border:                darken(@btn-info-bg, 5%);
    +
    +@btn-warning-color:              #fff;
    +@btn-warning-bg:                 @brand-warning;
    +@btn-warning-border:             darken(@btn-warning-bg, 5%);
    +
    +@btn-danger-color:               #fff;
    +@btn-danger-bg:                  @brand-danger;
    +@btn-danger-border:              darken(@btn-danger-bg, 5%);
    +
    +@btn-link-disabled-color:        @gray-light;
    +
    +
    +//== Forms
    +//
    +//##
    +
    +//** `` background color
    +@input-bg:                       #fff;
    +//** `` background color
    +@input-bg-disabled:              @gray-lighter;
    +
    +//** Text color for ``s
    +@input-color:                    @gray;
    +//** `` border color
    +@input-border:                   #ccc;
    +//** `` border radius
    +@input-border-radius:            @border-radius-base;
    +//** Border color for inputs on focus
    +@input-border-focus:             #66afe9;
    +
    +//** Placeholder text color
    +@input-color-placeholder:        @gray-light;
    +
    +//** Default `.form-control` height
    +@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
    +//** Large `.form-control` height
    +@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
    +//** Small `.form-control` height
    +@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
    +
    +@legend-color:                   @gray-dark;
    +@legend-border-color:            #e5e5e5;
    +
    +//** Background color for textual input addons
    +@input-group-addon-bg:           @gray-lighter;
    +//** Border color for textual input addons
    +@input-group-addon-border-color: @input-border;
    +
    +
    +//== Dropdowns
    +//
    +//## Dropdown menu container and contents.
    +
    +//** Background for the dropdown menu.
    +@dropdown-bg:                    #fff;
    +//** Dropdown menu `border-color`.
    +@dropdown-border:                rgba(0,0,0,.15);
    +//** Dropdown menu `border-color` **for IE8**.
    +@dropdown-fallback-border:       #ccc;
    +//** Divider color for between dropdown items.
    +@dropdown-divider-bg:            #e5e5e5;
    +
    +//** Dropdown link text color.
    +@dropdown-link-color:            @gray-dark;
    +//** Hover color for dropdown links.
    +@dropdown-link-hover-color:      darken(@gray-dark, 5%);
    +//** Hover background for dropdown links.
    +@dropdown-link-hover-bg:         #f5f5f5;
    +
    +//** Active dropdown menu item text color.
    +@dropdown-link-active-color:     @component-active-color;
    +//** Active dropdown menu item background color.
    +@dropdown-link-active-bg:        @component-active-bg;
    +
    +//** Disabled dropdown menu item background color.
    +@dropdown-link-disabled-color:   @gray-light;
    +
    +//** Text color for headers within dropdown menus.
    +@dropdown-header-color:          @gray-light;
    +
    +//** Deprecated `@dropdown-caret-color` as of v3.1.0
    +@dropdown-caret-color:           #000;
    +
    +
    +//-- Z-index master list
    +//
    +// Warning: Avoid customizing these values. They're used for a bird's eye view
    +// of components dependent on the z-axis and are designed to all work together.
    +//
    +// Note: These variables are not generated into the Customizer.
    +
    +@zindex-navbar:            1000;
    +@zindex-dropdown:          1000;
    +@zindex-popover:           1060;
    +@zindex-tooltip:           1070;
    +@zindex-navbar-fixed:      1030;
    +@zindex-modal-background:  1040;
    +@zindex-modal:             1050;
    +
    +
    +//== Media queries breakpoints
    +//
    +//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
    +
    +// Extra small screen / phone
    +//** Deprecated `@screen-xs` as of v3.0.1
    +@screen-xs:                  480px;
    +//** Deprecated `@screen-xs-min` as of v3.2.0
    +@screen-xs-min:              @screen-xs;
    +//** Deprecated `@screen-phone` as of v3.0.1
    +@screen-phone:               @screen-xs-min;
    +
    +// Small screen / tablet
    +//** Deprecated `@screen-sm` as of v3.0.1
    +@screen-sm:                  768px;
    +@screen-sm-min:              @screen-sm;
    +//** Deprecated `@screen-tablet` as of v3.0.1
    +@screen-tablet:              @screen-sm-min;
    +
    +// Medium screen / desktop
    +//** Deprecated `@screen-md` as of v3.0.1
    +@screen-md:                  992px;
    +@screen-md-min:              @screen-md;
    +//** Deprecated `@screen-desktop` as of v3.0.1
    +@screen-desktop:             @screen-md-min;
    +
    +// Large screen / wide desktop
    +//** Deprecated `@screen-lg` as of v3.0.1
    +@screen-lg:                  1200px;
    +@screen-lg-min:              @screen-lg;
    +//** Deprecated `@screen-lg-desktop` as of v3.0.1
    +@screen-lg-desktop:          @screen-lg-min;
    +
    +// So media queries don't overlap when required, provide a maximum
    +@screen-xs-max:              (@screen-sm-min - 1);
    +@screen-sm-max:              (@screen-md-min - 1);
    +@screen-md-max:              (@screen-lg-min - 1);
    +
    +
    +//== Grid system
    +//
    +//## Define your custom responsive grid.
    +
    +//** Number of columns in the grid.
    +@grid-columns:              12;
    +//** Padding between columns. Gets divided in half for the left and right.
    +@grid-gutter-width:         30px;
    +// Navbar collapse
    +//** Point at which the navbar becomes uncollapsed.
    +@grid-float-breakpoint:     @screen-sm-min;
    +//** Point at which the navbar begins collapsing.
    +@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
    +
    +
    +//== Container sizes
    +//
    +//## Define the maximum width of `.container` for different screen sizes.
    +
    +// Small screen / tablet
    +@container-tablet:             ((720px + @grid-gutter-width));
    +//** For `@screen-sm-min` and up.
    +@container-sm:                 @container-tablet;
    +
    +// Medium screen / desktop
    +@container-desktop:            ((940px + @grid-gutter-width));
    +//** For `@screen-md-min` and up.
    +@container-md:                 @container-desktop;
    +
    +// Large screen / wide desktop
    +@container-large-desktop:      ((1140px + @grid-gutter-width));
    +//** For `@screen-lg-min` and up.
    +@container-lg:                 @container-large-desktop;
    +
    +
    +//== Navbar
    +//
    +//##
    +
    +// Basics of a navbar
    +@navbar-height:                    50px;
    +@navbar-margin-bottom:             @line-height-computed;
    +@navbar-border-radius:             @border-radius-base;
    +@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
    +@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
    +@navbar-collapse-max-height:       340px;
    +
    +@navbar-default-color:             #777;
    +@navbar-default-bg:                #f8f8f8;
    +@navbar-default-border:            darken(@navbar-default-bg, 6.5%);
    +
    +// Navbar links
    +@navbar-default-link-color:                #777;
    +@navbar-default-link-hover-color:          #333;
    +@navbar-default-link-hover-bg:             transparent;
    +@navbar-default-link-active-color:         #555;
    +@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
    +@navbar-default-link-disabled-color:       #ccc;
    +@navbar-default-link-disabled-bg:          transparent;
    +
    +// Navbar brand label
    +@navbar-default-brand-color:               @navbar-default-link-color;
    +@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
    +@navbar-default-brand-hover-bg:            transparent;
    +
    +// Navbar toggle
    +@navbar-default-toggle-hover-bg:           #ddd;
    +@navbar-default-toggle-icon-bar-bg:        #888;
    +@navbar-default-toggle-border-color:       #ddd;
    +
    +
    +// Inverted navbar
    +// Reset inverted navbar basics
    +@navbar-inverse-color:                      @gray-light;
    +@navbar-inverse-bg:                         #222;
    +@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
    +
    +// Inverted navbar links
    +@navbar-inverse-link-color:                 @gray-light;
    +@navbar-inverse-link-hover-color:           #fff;
    +@navbar-inverse-link-hover-bg:              transparent;
    +@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
    +@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
    +@navbar-inverse-link-disabled-color:        #444;
    +@navbar-inverse-link-disabled-bg:           transparent;
    +
    +// Inverted navbar brand label
    +@navbar-inverse-brand-color:                @navbar-inverse-link-color;
    +@navbar-inverse-brand-hover-color:          #fff;
    +@navbar-inverse-brand-hover-bg:             transparent;
    +
    +// Inverted navbar toggle
    +@navbar-inverse-toggle-hover-bg:            #333;
    +@navbar-inverse-toggle-icon-bar-bg:         #fff;
    +@navbar-inverse-toggle-border-color:        #333;
    +
    +
    +//== Navs
    +//
    +//##
    +
    +//=== Shared nav styles
    +@nav-link-padding:                          10px 15px;
    +@nav-link-hover-bg:                         @gray-lighter;
    +
    +@nav-disabled-link-color:                   @gray-light;
    +@nav-disabled-link-hover-color:             @gray-light;
    +
    +@nav-open-link-hover-color:                 #fff;
    +
    +//== Tabs
    +@nav-tabs-border-color:                     #ddd;
    +
    +@nav-tabs-link-hover-border-color:          @gray-lighter;
    +
    +@nav-tabs-active-link-hover-bg:             @body-bg;
    +@nav-tabs-active-link-hover-color:          @gray;
    +@nav-tabs-active-link-hover-border-color:   #ddd;
    +
    +@nav-tabs-justified-link-border-color:            #ddd;
    +@nav-tabs-justified-active-link-border-color:     @body-bg;
    +
    +//== Pills
    +@nav-pills-border-radius:                   @border-radius-base;
    +@nav-pills-active-link-hover-bg:            @component-active-bg;
    +@nav-pills-active-link-hover-color:         @component-active-color;
    +
    +
    +//== Pagination
    +//
    +//##
    +
    +@pagination-color:                     @link-color;
    +@pagination-bg:                        #fff;
    +@pagination-border:                    #ddd;
    +
    +@pagination-hover-color:               @link-hover-color;
    +@pagination-hover-bg:                  @gray-lighter;
    +@pagination-hover-border:              #ddd;
    +
    +@pagination-active-color:              #fff;
    +@pagination-active-bg:                 @brand-primary;
    +@pagination-active-border:             @brand-primary;
    +
    +@pagination-disabled-color:            @gray-light;
    +@pagination-disabled-bg:               #fff;
    +@pagination-disabled-border:           #ddd;
    +
    +
    +//== Pager
    +//
    +//##
    +
    +@pager-bg:                             @pagination-bg;
    +@pager-border:                         @pagination-border;
    +@pager-border-radius:                  15px;
    +
    +@pager-hover-bg:                       @pagination-hover-bg;
    +
    +@pager-active-bg:                      @pagination-active-bg;
    +@pager-active-color:                   @pagination-active-color;
    +
    +@pager-disabled-color:                 @pagination-disabled-color;
    +
    +
    +//== Jumbotron
    +//
    +//##
    +
    +@jumbotron-padding:              30px;
    +@jumbotron-color:                inherit;
    +@jumbotron-bg:                   @gray-lighter;
    +@jumbotron-heading-color:        inherit;
    +@jumbotron-font-size:            ceil((@font-size-base * 1.5));
    +
    +
    +//== Form states and alerts
    +//
    +//## Define colors for form feedback states and, by default, alerts.
    +
    +@state-success-text:             #3c763d;
    +@state-success-bg:               #dff0d8;
    +@state-success-border:           darken(spin(@state-success-bg, -10), 5%);
    +
    +@state-info-text:                #31708f;
    +@state-info-bg:                  #d9edf7;
    +@state-info-border:              darken(spin(@state-info-bg, -10), 7%);
    +
    +@state-warning-text:             #8a6d3b;
    +@state-warning-bg:               #fcf8e3;
    +@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
    +
    +@state-danger-text:              #a94442;
    +@state-danger-bg:                #f2dede;
    +@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
    +
    +
    +//== Tooltips
    +//
    +//##
    +
    +//** Tooltip max width
    +@tooltip-max-width:           200px;
    +//** Tooltip text color
    +@tooltip-color:               #fff;
    +//** Tooltip background color
    +@tooltip-bg:                  #000;
    +@tooltip-opacity:             .9;
    +
    +//** Tooltip arrow width
    +@tooltip-arrow-width:         5px;
    +//** Tooltip arrow color
    +@tooltip-arrow-color:         @tooltip-bg;
    +
    +
    +//== Popovers
    +//
    +//##
    +
    +//** Popover body background color
    +@popover-bg:                          #fff;
    +//** Popover maximum width
    +@popover-max-width:                   276px;
    +//** Popover border color
    +@popover-border-color:                rgba(0,0,0,.2);
    +//** Popover fallback border color
    +@popover-fallback-border-color:       #ccc;
    +
    +//** Popover title background color
    +@popover-title-bg:                    darken(@popover-bg, 3%);
    +
    +//** Popover arrow width
    +@popover-arrow-width:                 10px;
    +//** Popover arrow color
    +@popover-arrow-color:                 #fff;
    +
    +//** Popover outer arrow width
    +@popover-arrow-outer-width:           (@popover-arrow-width + 1);
    +//** Popover outer arrow color
    +@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
    +//** Popover outer arrow fallback color
    +@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
    +
    +
    +//== Labels
    +//
    +//##
    +
    +//** Default label background color
    +@label-default-bg:            @gray-light;
    +//** Primary label background color
    +@label-primary-bg:            @brand-primary;
    +//** Success label background color
    +@label-success-bg:            @brand-success;
    +//** Info label background color
    +@label-info-bg:               @brand-info;
    +//** Warning label background color
    +@label-warning-bg:            @brand-warning;
    +//** Danger label background color
    +@label-danger-bg:             @brand-danger;
    +
    +//** Default label text color
    +@label-color:                 #fff;
    +//** Default text color of a linked label
    +@label-link-hover-color:      #fff;
    +
    +
    +//== Modals
    +//
    +//##
    +
    +//** Padding applied to the modal body
    +@modal-inner-padding:         15px;
    +
    +//** Padding applied to the modal title
    +@modal-title-padding:         15px;
    +//** Modal title line-height
    +@modal-title-line-height:     @line-height-base;
    +
    +//** Background color of modal content area
    +@modal-content-bg:                             #fff;
    +//** Modal content border color
    +@modal-content-border-color:                   rgba(0,0,0,.2);
    +//** Modal content border color **for IE8**
    +@modal-content-fallback-border-color:          #999;
    +
    +//** Modal backdrop background color
    +@modal-backdrop-bg:           #000;
    +//** Modal backdrop opacity
    +@modal-backdrop-opacity:      .5;
    +//** Modal header border color
    +@modal-header-border-color:   #e5e5e5;
    +//** Modal footer border color
    +@modal-footer-border-color:   @modal-header-border-color;
    +
    +@modal-lg:                    900px;
    +@modal-md:                    600px;
    +@modal-sm:                    300px;
    +
    +
    +//== Alerts
    +//
    +//## Define alert colors, border radius, and padding.
    +
    +@alert-padding:               15px;
    +@alert-border-radius:         @border-radius-base;
    +@alert-link-font-weight:      bold;
    +
    +@alert-success-bg:            @state-success-bg;
    +@alert-success-text:          @state-success-text;
    +@alert-success-border:        @state-success-border;
    +
    +@alert-info-bg:               @state-info-bg;
    +@alert-info-text:             @state-info-text;
    +@alert-info-border:           @state-info-border;
    +
    +@alert-warning-bg:            @state-warning-bg;
    +@alert-warning-text:          @state-warning-text;
    +@alert-warning-border:        @state-warning-border;
    +
    +@alert-danger-bg:             @state-danger-bg;
    +@alert-danger-text:           @state-danger-text;
    +@alert-danger-border:         @state-danger-border;
    +
    +
    +//== Progress bars
    +//
    +//##
    +
    +//** Background color of the whole progress component
    +@progress-bg:                 #f5f5f5;
    +//** Progress bar text color
    +@progress-bar-color:          #fff;
    +
    +//** Default progress bar color
    +@progress-bar-bg:             @brand-primary;
    +//** Success progress bar color
    +@progress-bar-success-bg:     @brand-success;
    +//** Warning progress bar color
    +@progress-bar-warning-bg:     @brand-warning;
    +//** Danger progress bar color
    +@progress-bar-danger-bg:      @brand-danger;
    +//** Info progress bar color
    +@progress-bar-info-bg:        @brand-info;
    +
    +
    +//== List group
    +//
    +//##
    +
    +//** Background color on `.list-group-item`
    +@list-group-bg:                 #fff;
    +//** `.list-group-item` border color
    +@list-group-border:             #ddd;
    +//** List group border radius
    +@list-group-border-radius:      @border-radius-base;
    +
    +//** Background color of single list items on hover
    +@list-group-hover-bg:           #f5f5f5;
    +//** Text color of active list items
    +@list-group-active-color:       @component-active-color;
    +//** Background color of active list items
    +@list-group-active-bg:          @component-active-bg;
    +//** Border color of active list elements
    +@list-group-active-border:      @list-group-active-bg;
    +//** Text color for content within active list items
    +@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
    +
    +//** Text color of disabled list items
    +@list-group-disabled-color:      @gray-light;
    +//** Background color of disabled list items
    +@list-group-disabled-bg:         @gray-lighter;
    +//** Text color for content within disabled list items
    +@list-group-disabled-text-color: @list-group-disabled-color;
    +
    +@list-group-link-color:         #555;
    +@list-group-link-hover-color:   @list-group-link-color;
    +@list-group-link-heading-color: #333;
    +
    +
    +//== Panels
    +//
    +//##
    +
    +@panel-bg:                    #fff;
    +@panel-body-padding:          15px;
    +@panel-heading-padding:       10px 15px;
    +@panel-footer-padding:        @panel-heading-padding;
    +@panel-border-radius:         @border-radius-base;
    +
    +//** Border color for elements within panels
    +@panel-inner-border:          #ddd;
    +@panel-footer-bg:             #f5f5f5;
    +
    +@panel-default-text:          @gray-dark;
    +@panel-default-border:        #ddd;
    +@panel-default-heading-bg:    #f5f5f5;
    +
    +@panel-primary-text:          #fff;
    +@panel-primary-border:        @brand-primary;
    +@panel-primary-heading-bg:    @brand-primary;
    +
    +@panel-success-text:          @state-success-text;
    +@panel-success-border:        @state-success-border;
    +@panel-success-heading-bg:    @state-success-bg;
    +
    +@panel-info-text:             @state-info-text;
    +@panel-info-border:           @state-info-border;
    +@panel-info-heading-bg:       @state-info-bg;
    +
    +@panel-warning-text:          @state-warning-text;
    +@panel-warning-border:        @state-warning-border;
    +@panel-warning-heading-bg:    @state-warning-bg;
    +
    +@panel-danger-text:           @state-danger-text;
    +@panel-danger-border:         @state-danger-border;
    +@panel-danger-heading-bg:     @state-danger-bg;
    +
    +
    +//== Thumbnails
    +//
    +//##
    +
    +//** Padding around the thumbnail image
    +@thumbnail-padding:           4px;
    +//** Thumbnail background color
    +@thumbnail-bg:                @body-bg;
    +//** Thumbnail border color
    +@thumbnail-border:            #ddd;
    +//** Thumbnail border radius
    +@thumbnail-border-radius:     @border-radius-base;
    +
    +//** Custom text color for thumbnail captions
    +@thumbnail-caption-color:     @text-color;
    +//** Padding around the thumbnail caption
    +@thumbnail-caption-padding:   9px;
    +
    +
    +//== Wells
    +//
    +//##
    +
    +@well-bg:                     #f5f5f5;
    +@well-border:                 darken(@well-bg, 7%);
    +
    +
    +//== Badges
    +//
    +//##
    +
    +@badge-color:                 #fff;
    +//** Linked badge text color on hover
    +@badge-link-hover-color:      #fff;
    +@badge-bg:                    @gray-light;
    +
    +//** Badge text color in active nav link
    +@badge-active-color:          @link-color;
    +//** Badge background color in active nav link
    +@badge-active-bg:             #fff;
    +
    +@badge-font-weight:           bold;
    +@badge-line-height:           1;
    +@badge-border-radius:         10px;
    +
    +
    +//== Breadcrumbs
    +//
    +//##
    +
    +@breadcrumb-padding-vertical:   8px;
    +@breadcrumb-padding-horizontal: 15px;
    +//** Breadcrumb background color
    +@breadcrumb-bg:                 #f5f5f5;
    +//** Breadcrumb text color
    +@breadcrumb-color:              #ccc;
    +//** Text color of current page in the breadcrumb
    +@breadcrumb-active-color:       @gray-light;
    +//** Textual separator for between breadcrumb elements
    +@breadcrumb-separator:          "/";
    +
    +
    +//== Carousel
    +//
    +//##
    +
    +@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
    +
    +@carousel-control-color:                      #fff;
    +@carousel-control-width:                      15%;
    +@carousel-control-opacity:                    .5;
    +@carousel-control-font-size:                  20px;
    +
    +@carousel-indicator-active-bg:                #fff;
    +@carousel-indicator-border-color:             #fff;
    +
    +@carousel-caption-color:                      #fff;
    +
    +
    +//== Close
    +//
    +//##
    +
    +@close-font-weight:           bold;
    +@close-color:                 #000;
    +@close-text-shadow:           0 1px 0 #fff;
    +
    +
    +//== Code
    +//
    +//##
    +
    +@code-color:                  #c7254e;
    +@code-bg:                     #f9f2f4;
    +
    +@kbd-color:                   #fff;
    +@kbd-bg:                      #333;
    +
    +@pre-bg:                      #f5f5f5;
    +@pre-color:                   @gray-dark;
    +@pre-border-color:            #ccc;
    +@pre-scrollable-max-height:   340px;
    +
    +
    +//== Type
    +//
    +//##
    +
    +//** Horizontal offset for forms and lists.
    +@component-offset-horizontal: 180px;
    +//** Text muted color
    +@text-muted:                  @gray-light;
    +//** Abbreviations and acronyms border color
    +@abbr-border-color:           @gray-light;
    +//** Headings small color
    +@headings-small-color:        @gray-light;
    +//** Blockquote small color
    +@blockquote-small-color:      @gray-light;
    +//** Blockquote font size
    +@blockquote-font-size:        (@font-size-base * 1.25);
    +//** Blockquote border color
    +@blockquote-border-color:     @gray-lighter;
    +//** Page header border color
    +@page-header-border-color:    @gray-lighter;
    +//** Width of horizontal description list titles
    +@dl-horizontal-offset:        @component-offset-horizontal;
    +//** Horizontal line color.
    +@hr-border:                   @gray-lighter;
    +
    +
    diff --git a/assets/less/bootstrap/wells.less b/assets/less/bootstrap/wells.less
    index 15d072b0..add4c890 100644
    --- a/assets/less/bootstrap/wells.less
    +++ b/assets/less/bootstrap/wells.less
    @@ -1,29 +1,29 @@
    -//
    -// Wells
    -// --------------------------------------------------
    -
    -
    -// Base class
    -.well {
    -  min-height: 20px;
    -  padding: 19px;
    -  margin-bottom: 20px;
    -  background-color: @well-bg;
    -  border: 1px solid @well-border;
    -  border-radius: @border-radius-base;
    -  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
    -  blockquote {
    -    border-color: #ddd;
    -    border-color: rgba(0,0,0,.15);
    -  }
    -}
    -
    -// Sizes
    -.well-lg {
    -  padding: 24px;
    -  border-radius: @border-radius-large;
    -}
    -.well-sm {
    -  padding: 9px;
    -  border-radius: @border-radius-small;
    -}
    +//
    +// Wells
    +// --------------------------------------------------
    +
    +
    +// Base class
    +.well {
    +  min-height: 20px;
    +  padding: 19px;
    +  margin-bottom: 20px;
    +  background-color: @well-bg;
    +  border: 1px solid @well-border;
    +  border-radius: @border-radius-base;
    +  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
    +  blockquote {
    +    border-color: #ddd;
    +    border-color: rgba(0,0,0,.15);
    +  }
    +}
    +
    +// Sizes
    +.well-lg {
    +  padding: 24px;
    +  border-radius: @border-radius-large;
    +}
    +.well-sm {
    +  padding: 9px;
    +  border-radius: @border-radius-small;
    +}
    diff --git a/assets/less/font-awesome/bordered-pulled.less b/assets/less/font-awesome/bordered-pulled.less
    index 0c90eb56..48a7004d 100644
    --- a/assets/less/font-awesome/bordered-pulled.less
    +++ b/assets/less/font-awesome/bordered-pulled.less
    @@ -1,16 +1,16 @@
    -// Bordered & Pulled
    -// -------------------------
    -
    -.@{fa-css-prefix}-border {
    -  padding: .2em .25em .15em;
    -  border: solid .08em @fa-border-color;
    -  border-radius: .1em;
    -}
    -
    -.pull-right { float: right; }
    -.pull-left { float: left; }
    -
    -.@{fa-css-prefix} {
    -  &.pull-left { margin-right: .3em; }
    -  &.pull-right { margin-left: .3em; }
    -}
    +// Bordered & Pulled
    +// -------------------------
    +
    +.@{fa-css-prefix}-border {
    +  padding: .2em .25em .15em;
    +  border: solid .08em @fa-border-color;
    +  border-radius: .1em;
    +}
    +
    +.pull-right { float: right; }
    +.pull-left { float: left; }
    +
    +.@{fa-css-prefix} {
    +  &.pull-left { margin-right: .3em; }
    +  &.pull-right { margin-left: .3em; }
    +}
    diff --git a/assets/less/font-awesome/core.less b/assets/less/font-awesome/core.less
    index 6d223bc2..500ca3ee 100644
    --- a/assets/less/font-awesome/core.less
    +++ b/assets/less/font-awesome/core.less
    @@ -1,12 +1,12 @@
    -// Base Class Definition
    -// -------------------------
    -
    -.@{fa-css-prefix} {
    -  display: inline-block;
    -  font-family: FontAwesome;
    -  font-style: normal;
    -  font-weight: normal;
    -  line-height: 1;
    -  -webkit-font-smoothing: antialiased;
    -  -moz-osx-font-smoothing: grayscale;
    -}
    +// Base Class Definition
    +// -------------------------
    +
    +.@{fa-css-prefix} {
    +  display: inline-block;
    +  font-family: FontAwesome;
    +  font-style: normal;
    +  font-weight: normal;
    +  line-height: 1;
    +  -webkit-font-smoothing: antialiased;
    +  -moz-osx-font-smoothing: grayscale;
    +}
    diff --git a/assets/less/font-awesome/fixed-width.less b/assets/less/font-awesome/fixed-width.less
    index 110289f2..4fd1ed36 100644
    --- a/assets/less/font-awesome/fixed-width.less
    +++ b/assets/less/font-awesome/fixed-width.less
    @@ -1,6 +1,6 @@
    -// Fixed Width Icons
    -// -------------------------
    -.@{fa-css-prefix}-fw {
    -  width: (18em / 14);
    -  text-align: center;
    -}
    +// Fixed Width Icons
    +// -------------------------
    +.@{fa-css-prefix}-fw {
    +  width: (18em / 14);
    +  text-align: center;
    +}
    diff --git a/assets/less/font-awesome/font-awesome.less b/assets/less/font-awesome/font-awesome.less
    index 50cbcac4..2d77b992 100644
    --- a/assets/less/font-awesome/font-awesome.less
    +++ b/assets/less/font-awesome/font-awesome.less
    @@ -1,17 +1,17 @@
    -/*!
    - *  Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome
    - *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
    - */
    -
    -@import "variables.less";
    -@import "mixins.less";
    -@import "path.less";
    -@import "core.less";
    -@import "larger.less";
    -@import "fixed-width.less";
    -@import "list.less";
    -@import "bordered-pulled.less";
    -@import "spinning.less";
    -@import "rotated-flipped.less";
    -@import "stacked.less";
    -@import "icons.less";
    +/*!
    + *  Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome
    + *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
    + */
    +
    +@import "variables.less";
    +@import "mixins.less";
    +@import "path.less";
    +@import "core.less";
    +@import "larger.less";
    +@import "fixed-width.less";
    +@import "list.less";
    +@import "bordered-pulled.less";
    +@import "spinning.less";
    +@import "rotated-flipped.less";
    +@import "stacked.less";
    +@import "icons.less";
    diff --git a/assets/less/font-awesome/icons.less b/assets/less/font-awesome/icons.less
    index 13d8c685..c9a2ba47 100644
    --- a/assets/less/font-awesome/icons.less
    +++ b/assets/less/font-awesome/icons.less
    @@ -1,506 +1,506 @@
    -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
    -   readers do not read off random characters that represent icons */
    -
    -.@{fa-css-prefix}-glass:before { content: @fa-var-glass; }
    -.@{fa-css-prefix}-music:before { content: @fa-var-music; }
    -.@{fa-css-prefix}-search:before { content: @fa-var-search; }
    -.@{fa-css-prefix}-envelope-o:before { content: @fa-var-envelope-o; }
    -.@{fa-css-prefix}-heart:before { content: @fa-var-heart; }
    -.@{fa-css-prefix}-star:before { content: @fa-var-star; }
    -.@{fa-css-prefix}-star-o:before { content: @fa-var-star-o; }
    -.@{fa-css-prefix}-user:before { content: @fa-var-user; }
    -.@{fa-css-prefix}-film:before { content: @fa-var-film; }
    -.@{fa-css-prefix}-th-large:before { content: @fa-var-th-large; }
    -.@{fa-css-prefix}-th:before { content: @fa-var-th; }
    -.@{fa-css-prefix}-th-list:before { content: @fa-var-th-list; }
    -.@{fa-css-prefix}-check:before { content: @fa-var-check; }
    -.@{fa-css-prefix}-times:before { content: @fa-var-times; }
    -.@{fa-css-prefix}-search-plus:before { content: @fa-var-search-plus; }
    -.@{fa-css-prefix}-search-minus:before { content: @fa-var-search-minus; }
    -.@{fa-css-prefix}-power-off:before { content: @fa-var-power-off; }
    -.@{fa-css-prefix}-signal:before { content: @fa-var-signal; }
    -.@{fa-css-prefix}-gear:before,
    -.@{fa-css-prefix}-cog:before { content: @fa-var-cog; }
    -.@{fa-css-prefix}-trash-o:before { content: @fa-var-trash-o; }
    -.@{fa-css-prefix}-home:before { content: @fa-var-home; }
    -.@{fa-css-prefix}-file-o:before { content: @fa-var-file-o; }
    -.@{fa-css-prefix}-clock-o:before { content: @fa-var-clock-o; }
    -.@{fa-css-prefix}-road:before { content: @fa-var-road; }
    -.@{fa-css-prefix}-download:before { content: @fa-var-download; }
    -.@{fa-css-prefix}-arrow-circle-o-down:before { content: @fa-var-arrow-circle-o-down; }
    -.@{fa-css-prefix}-arrow-circle-o-up:before { content: @fa-var-arrow-circle-o-up; }
    -.@{fa-css-prefix}-inbox:before { content: @fa-var-inbox; }
    -.@{fa-css-prefix}-play-circle-o:before { content: @fa-var-play-circle-o; }
    -.@{fa-css-prefix}-rotate-right:before,
    -.@{fa-css-prefix}-repeat:before { content: @fa-var-repeat; }
    -.@{fa-css-prefix}-refresh:before { content: @fa-var-refresh; }
    -.@{fa-css-prefix}-list-alt:before { content: @fa-var-list-alt; }
    -.@{fa-css-prefix}-lock:before { content: @fa-var-lock; }
    -.@{fa-css-prefix}-flag:before { content: @fa-var-flag; }
    -.@{fa-css-prefix}-headphones:before { content: @fa-var-headphones; }
    -.@{fa-css-prefix}-volume-off:before { content: @fa-var-volume-off; }
    -.@{fa-css-prefix}-volume-down:before { content: @fa-var-volume-down; }
    -.@{fa-css-prefix}-volume-up:before { content: @fa-var-volume-up; }
    -.@{fa-css-prefix}-qrcode:before { content: @fa-var-qrcode; }
    -.@{fa-css-prefix}-barcode:before { content: @fa-var-barcode; }
    -.@{fa-css-prefix}-tag:before { content: @fa-var-tag; }
    -.@{fa-css-prefix}-tags:before { content: @fa-var-tags; }
    -.@{fa-css-prefix}-book:before { content: @fa-var-book; }
    -.@{fa-css-prefix}-bookmark:before { content: @fa-var-bookmark; }
    -.@{fa-css-prefix}-print:before { content: @fa-var-print; }
    -.@{fa-css-prefix}-camera:before { content: @fa-var-camera; }
    -.@{fa-css-prefix}-font:before { content: @fa-var-font; }
    -.@{fa-css-prefix}-bold:before { content: @fa-var-bold; }
    -.@{fa-css-prefix}-italic:before { content: @fa-var-italic; }
    -.@{fa-css-prefix}-text-height:before { content: @fa-var-text-height; }
    -.@{fa-css-prefix}-text-width:before { content: @fa-var-text-width; }
    -.@{fa-css-prefix}-align-left:before { content: @fa-var-align-left; }
    -.@{fa-css-prefix}-align-center:before { content: @fa-var-align-center; }
    -.@{fa-css-prefix}-align-right:before { content: @fa-var-align-right; }
    -.@{fa-css-prefix}-align-justify:before { content: @fa-var-align-justify; }
    -.@{fa-css-prefix}-list:before { content: @fa-var-list; }
    -.@{fa-css-prefix}-dedent:before,
    -.@{fa-css-prefix}-outdent:before { content: @fa-var-outdent; }
    -.@{fa-css-prefix}-indent:before { content: @fa-var-indent; }
    -.@{fa-css-prefix}-video-camera:before { content: @fa-var-video-camera; }
    -.@{fa-css-prefix}-photo:before,
    -.@{fa-css-prefix}-image:before,
    -.@{fa-css-prefix}-picture-o:before { content: @fa-var-picture-o; }
    -.@{fa-css-prefix}-pencil:before { content: @fa-var-pencil; }
    -.@{fa-css-prefix}-map-marker:before { content: @fa-var-map-marker; }
    -.@{fa-css-prefix}-adjust:before { content: @fa-var-adjust; }
    -.@{fa-css-prefix}-tint:before { content: @fa-var-tint; }
    -.@{fa-css-prefix}-edit:before,
    -.@{fa-css-prefix}-pencil-square-o:before { content: @fa-var-pencil-square-o; }
    -.@{fa-css-prefix}-share-square-o:before { content: @fa-var-share-square-o; }
    -.@{fa-css-prefix}-check-square-o:before { content: @fa-var-check-square-o; }
    -.@{fa-css-prefix}-arrows:before { content: @fa-var-arrows; }
    -.@{fa-css-prefix}-step-backward:before { content: @fa-var-step-backward; }
    -.@{fa-css-prefix}-fast-backward:before { content: @fa-var-fast-backward; }
    -.@{fa-css-prefix}-backward:before { content: @fa-var-backward; }
    -.@{fa-css-prefix}-play:before { content: @fa-var-play; }
    -.@{fa-css-prefix}-pause:before { content: @fa-var-pause; }
    -.@{fa-css-prefix}-stop:before { content: @fa-var-stop; }
    -.@{fa-css-prefix}-forward:before { content: @fa-var-forward; }
    -.@{fa-css-prefix}-fast-forward:before { content: @fa-var-fast-forward; }
    -.@{fa-css-prefix}-step-forward:before { content: @fa-var-step-forward; }
    -.@{fa-css-prefix}-eject:before { content: @fa-var-eject; }
    -.@{fa-css-prefix}-chevron-left:before { content: @fa-var-chevron-left; }
    -.@{fa-css-prefix}-chevron-right:before { content: @fa-var-chevron-right; }
    -.@{fa-css-prefix}-plus-circle:before { content: @fa-var-plus-circle; }
    -.@{fa-css-prefix}-minus-circle:before { content: @fa-var-minus-circle; }
    -.@{fa-css-prefix}-times-circle:before { content: @fa-var-times-circle; }
    -.@{fa-css-prefix}-check-circle:before { content: @fa-var-check-circle; }
    -.@{fa-css-prefix}-question-circle:before { content: @fa-var-question-circle; }
    -.@{fa-css-prefix}-info-circle:before { content: @fa-var-info-circle; }
    -.@{fa-css-prefix}-crosshairs:before { content: @fa-var-crosshairs; }
    -.@{fa-css-prefix}-times-circle-o:before { content: @fa-var-times-circle-o; }
    -.@{fa-css-prefix}-check-circle-o:before { content: @fa-var-check-circle-o; }
    -.@{fa-css-prefix}-ban:before { content: @fa-var-ban; }
    -.@{fa-css-prefix}-arrow-left:before { content: @fa-var-arrow-left; }
    -.@{fa-css-prefix}-arrow-right:before { content: @fa-var-arrow-right; }
    -.@{fa-css-prefix}-arrow-up:before { content: @fa-var-arrow-up; }
    -.@{fa-css-prefix}-arrow-down:before { content: @fa-var-arrow-down; }
    -.@{fa-css-prefix}-mail-forward:before,
    -.@{fa-css-prefix}-share:before { content: @fa-var-share; }
    -.@{fa-css-prefix}-expand:before { content: @fa-var-expand; }
    -.@{fa-css-prefix}-compress:before { content: @fa-var-compress; }
    -.@{fa-css-prefix}-plus:before { content: @fa-var-plus; }
    -.@{fa-css-prefix}-minus:before { content: @fa-var-minus; }
    -.@{fa-css-prefix}-asterisk:before { content: @fa-var-asterisk; }
    -.@{fa-css-prefix}-exclamation-circle:before { content: @fa-var-exclamation-circle; }
    -.@{fa-css-prefix}-gift:before { content: @fa-var-gift; }
    -.@{fa-css-prefix}-leaf:before { content: @fa-var-leaf; }
    -.@{fa-css-prefix}-fire:before { content: @fa-var-fire; }
    -.@{fa-css-prefix}-eye:before { content: @fa-var-eye; }
    -.@{fa-css-prefix}-eye-slash:before { content: @fa-var-eye-slash; }
    -.@{fa-css-prefix}-warning:before,
    -.@{fa-css-prefix}-exclamation-triangle:before { content: @fa-var-exclamation-triangle; }
    -.@{fa-css-prefix}-plane:before { content: @fa-var-plane; }
    -.@{fa-css-prefix}-calendar:before { content: @fa-var-calendar; }
    -.@{fa-css-prefix}-random:before { content: @fa-var-random; }
    -.@{fa-css-prefix}-comment:before { content: @fa-var-comment; }
    -.@{fa-css-prefix}-magnet:before { content: @fa-var-magnet; }
    -.@{fa-css-prefix}-chevron-up:before { content: @fa-var-chevron-up; }
    -.@{fa-css-prefix}-chevron-down:before { content: @fa-var-chevron-down; }
    -.@{fa-css-prefix}-retweet:before { content: @fa-var-retweet; }
    -.@{fa-css-prefix}-shopping-cart:before { content: @fa-var-shopping-cart; }
    -.@{fa-css-prefix}-folder:before { content: @fa-var-folder; }
    -.@{fa-css-prefix}-folder-open:before { content: @fa-var-folder-open; }
    -.@{fa-css-prefix}-arrows-v:before { content: @fa-var-arrows-v; }
    -.@{fa-css-prefix}-arrows-h:before { content: @fa-var-arrows-h; }
    -.@{fa-css-prefix}-bar-chart-o:before { content: @fa-var-bar-chart-o; }
    -.@{fa-css-prefix}-twitter-square:before { content: @fa-var-twitter-square; }
    -.@{fa-css-prefix}-facebook-square:before { content: @fa-var-facebook-square; }
    -.@{fa-css-prefix}-camera-retro:before { content: @fa-var-camera-retro; }
    -.@{fa-css-prefix}-key:before { content: @fa-var-key; }
    -.@{fa-css-prefix}-gears:before,
    -.@{fa-css-prefix}-cogs:before { content: @fa-var-cogs; }
    -.@{fa-css-prefix}-comments:before { content: @fa-var-comments; }
    -.@{fa-css-prefix}-thumbs-o-up:before { content: @fa-var-thumbs-o-up; }
    -.@{fa-css-prefix}-thumbs-o-down:before { content: @fa-var-thumbs-o-down; }
    -.@{fa-css-prefix}-star-half:before { content: @fa-var-star-half; }
    -.@{fa-css-prefix}-heart-o:before { content: @fa-var-heart-o; }
    -.@{fa-css-prefix}-sign-out:before { content: @fa-var-sign-out; }
    -.@{fa-css-prefix}-linkedin-square:before { content: @fa-var-linkedin-square; }
    -.@{fa-css-prefix}-thumb-tack:before { content: @fa-var-thumb-tack; }
    -.@{fa-css-prefix}-external-link:before { content: @fa-var-external-link; }
    -.@{fa-css-prefix}-sign-in:before { content: @fa-var-sign-in; }
    -.@{fa-css-prefix}-trophy:before { content: @fa-var-trophy; }
    -.@{fa-css-prefix}-github-square:before { content: @fa-var-github-square; }
    -.@{fa-css-prefix}-upload:before { content: @fa-var-upload; }
    -.@{fa-css-prefix}-lemon-o:before { content: @fa-var-lemon-o; }
    -.@{fa-css-prefix}-phone:before { content: @fa-var-phone; }
    -.@{fa-css-prefix}-square-o:before { content: @fa-var-square-o; }
    -.@{fa-css-prefix}-bookmark-o:before { content: @fa-var-bookmark-o; }
    -.@{fa-css-prefix}-phone-square:before { content: @fa-var-phone-square; }
    -.@{fa-css-prefix}-twitter:before { content: @fa-var-twitter; }
    -.@{fa-css-prefix}-facebook:before { content: @fa-var-facebook; }
    -.@{fa-css-prefix}-github:before { content: @fa-var-github; }
    -.@{fa-css-prefix}-unlock:before { content: @fa-var-unlock; }
    -.@{fa-css-prefix}-credit-card:before { content: @fa-var-credit-card; }
    -.@{fa-css-prefix}-rss:before { content: @fa-var-rss; }
    -.@{fa-css-prefix}-hdd-o:before { content: @fa-var-hdd-o; }
    -.@{fa-css-prefix}-bullhorn:before { content: @fa-var-bullhorn; }
    -.@{fa-css-prefix}-bell:before { content: @fa-var-bell; }
    -.@{fa-css-prefix}-certificate:before { content: @fa-var-certificate; }
    -.@{fa-css-prefix}-hand-o-right:before { content: @fa-var-hand-o-right; }
    -.@{fa-css-prefix}-hand-o-left:before { content: @fa-var-hand-o-left; }
    -.@{fa-css-prefix}-hand-o-up:before { content: @fa-var-hand-o-up; }
    -.@{fa-css-prefix}-hand-o-down:before { content: @fa-var-hand-o-down; }
    -.@{fa-css-prefix}-arrow-circle-left:before { content: @fa-var-arrow-circle-left; }
    -.@{fa-css-prefix}-arrow-circle-right:before { content: @fa-var-arrow-circle-right; }
    -.@{fa-css-prefix}-arrow-circle-up:before { content: @fa-var-arrow-circle-up; }
    -.@{fa-css-prefix}-arrow-circle-down:before { content: @fa-var-arrow-circle-down; }
    -.@{fa-css-prefix}-globe:before { content: @fa-var-globe; }
    -.@{fa-css-prefix}-wrench:before { content: @fa-var-wrench; }
    -.@{fa-css-prefix}-tasks:before { content: @fa-var-tasks; }
    -.@{fa-css-prefix}-filter:before { content: @fa-var-filter; }
    -.@{fa-css-prefix}-briefcase:before { content: @fa-var-briefcase; }
    -.@{fa-css-prefix}-arrows-alt:before { content: @fa-var-arrows-alt; }
    -.@{fa-css-prefix}-group:before,
    -.@{fa-css-prefix}-users:before { content: @fa-var-users; }
    -.@{fa-css-prefix}-chain:before,
    -.@{fa-css-prefix}-link:before { content: @fa-var-link; }
    -.@{fa-css-prefix}-cloud:before { content: @fa-var-cloud; }
    -.@{fa-css-prefix}-flask:before { content: @fa-var-flask; }
    -.@{fa-css-prefix}-cut:before,
    -.@{fa-css-prefix}-scissors:before { content: @fa-var-scissors; }
    -.@{fa-css-prefix}-copy:before,
    -.@{fa-css-prefix}-files-o:before { content: @fa-var-files-o; }
    -.@{fa-css-prefix}-paperclip:before { content: @fa-var-paperclip; }
    -.@{fa-css-prefix}-save:before,
    -.@{fa-css-prefix}-floppy-o:before { content: @fa-var-floppy-o; }
    -.@{fa-css-prefix}-square:before { content: @fa-var-square; }
    -.@{fa-css-prefix}-navicon:before,
    -.@{fa-css-prefix}-reorder:before,
    -.@{fa-css-prefix}-bars:before { content: @fa-var-bars; }
    -.@{fa-css-prefix}-list-ul:before { content: @fa-var-list-ul; }
    -.@{fa-css-prefix}-list-ol:before { content: @fa-var-list-ol; }
    -.@{fa-css-prefix}-strikethrough:before { content: @fa-var-strikethrough; }
    -.@{fa-css-prefix}-underline:before { content: @fa-var-underline; }
    -.@{fa-css-prefix}-table:before { content: @fa-var-table; }
    -.@{fa-css-prefix}-magic:before { content: @fa-var-magic; }
    -.@{fa-css-prefix}-truck:before { content: @fa-var-truck; }
    -.@{fa-css-prefix}-pinterest:before { content: @fa-var-pinterest; }
    -.@{fa-css-prefix}-pinterest-square:before { content: @fa-var-pinterest-square; }
    -.@{fa-css-prefix}-google-plus-square:before { content: @fa-var-google-plus-square; }
    -.@{fa-css-prefix}-google-plus:before { content: @fa-var-google-plus; }
    -.@{fa-css-prefix}-money:before { content: @fa-var-money; }
    -.@{fa-css-prefix}-caret-down:before { content: @fa-var-caret-down; }
    -.@{fa-css-prefix}-caret-up:before { content: @fa-var-caret-up; }
    -.@{fa-css-prefix}-caret-left:before { content: @fa-var-caret-left; }
    -.@{fa-css-prefix}-caret-right:before { content: @fa-var-caret-right; }
    -.@{fa-css-prefix}-columns:before { content: @fa-var-columns; }
    -.@{fa-css-prefix}-unsorted:before,
    -.@{fa-css-prefix}-sort:before { content: @fa-var-sort; }
    -.@{fa-css-prefix}-sort-down:before,
    -.@{fa-css-prefix}-sort-desc:before { content: @fa-var-sort-desc; }
    -.@{fa-css-prefix}-sort-up:before,
    -.@{fa-css-prefix}-sort-asc:before { content: @fa-var-sort-asc; }
    -.@{fa-css-prefix}-envelope:before { content: @fa-var-envelope; }
    -.@{fa-css-prefix}-linkedin:before { content: @fa-var-linkedin; }
    -.@{fa-css-prefix}-rotate-left:before,
    -.@{fa-css-prefix}-undo:before { content: @fa-var-undo; }
    -.@{fa-css-prefix}-legal:before,
    -.@{fa-css-prefix}-gavel:before { content: @fa-var-gavel; }
    -.@{fa-css-prefix}-dashboard:before,
    -.@{fa-css-prefix}-tachometer:before { content: @fa-var-tachometer; }
    -.@{fa-css-prefix}-comment-o:before { content: @fa-var-comment-o; }
    -.@{fa-css-prefix}-comments-o:before { content: @fa-var-comments-o; }
    -.@{fa-css-prefix}-flash:before,
    -.@{fa-css-prefix}-bolt:before { content: @fa-var-bolt; }
    -.@{fa-css-prefix}-sitemap:before { content: @fa-var-sitemap; }
    -.@{fa-css-prefix}-umbrella:before { content: @fa-var-umbrella; }
    -.@{fa-css-prefix}-paste:before,
    -.@{fa-css-prefix}-clipboard:before { content: @fa-var-clipboard; }
    -.@{fa-css-prefix}-lightbulb-o:before { content: @fa-var-lightbulb-o; }
    -.@{fa-css-prefix}-exchange:before { content: @fa-var-exchange; }
    -.@{fa-css-prefix}-cloud-download:before { content: @fa-var-cloud-download; }
    -.@{fa-css-prefix}-cloud-upload:before { content: @fa-var-cloud-upload; }
    -.@{fa-css-prefix}-user-md:before { content: @fa-var-user-md; }
    -.@{fa-css-prefix}-stethoscope:before { content: @fa-var-stethoscope; }
    -.@{fa-css-prefix}-suitcase:before { content: @fa-var-suitcase; }
    -.@{fa-css-prefix}-bell-o:before { content: @fa-var-bell-o; }
    -.@{fa-css-prefix}-coffee:before { content: @fa-var-coffee; }
    -.@{fa-css-prefix}-cutlery:before { content: @fa-var-cutlery; }
    -.@{fa-css-prefix}-file-text-o:before { content: @fa-var-file-text-o; }
    -.@{fa-css-prefix}-building-o:before { content: @fa-var-building-o; }
    -.@{fa-css-prefix}-hospital-o:before { content: @fa-var-hospital-o; }
    -.@{fa-css-prefix}-ambulance:before { content: @fa-var-ambulance; }
    -.@{fa-css-prefix}-medkit:before { content: @fa-var-medkit; }
    -.@{fa-css-prefix}-fighter-jet:before { content: @fa-var-fighter-jet; }
    -.@{fa-css-prefix}-beer:before { content: @fa-var-beer; }
    -.@{fa-css-prefix}-h-square:before { content: @fa-var-h-square; }
    -.@{fa-css-prefix}-plus-square:before { content: @fa-var-plus-square; }
    -.@{fa-css-prefix}-angle-double-left:before { content: @fa-var-angle-double-left; }
    -.@{fa-css-prefix}-angle-double-right:before { content: @fa-var-angle-double-right; }
    -.@{fa-css-prefix}-angle-double-up:before { content: @fa-var-angle-double-up; }
    -.@{fa-css-prefix}-angle-double-down:before { content: @fa-var-angle-double-down; }
    -.@{fa-css-prefix}-angle-left:before { content: @fa-var-angle-left; }
    -.@{fa-css-prefix}-angle-right:before { content: @fa-var-angle-right; }
    -.@{fa-css-prefix}-angle-up:before { content: @fa-var-angle-up; }
    -.@{fa-css-prefix}-angle-down:before { content: @fa-var-angle-down; }
    -.@{fa-css-prefix}-desktop:before { content: @fa-var-desktop; }
    -.@{fa-css-prefix}-laptop:before { content: @fa-var-laptop; }
    -.@{fa-css-prefix}-tablet:before { content: @fa-var-tablet; }
    -.@{fa-css-prefix}-mobile-phone:before,
    -.@{fa-css-prefix}-mobile:before { content: @fa-var-mobile; }
    -.@{fa-css-prefix}-circle-o:before { content: @fa-var-circle-o; }
    -.@{fa-css-prefix}-quote-left:before { content: @fa-var-quote-left; }
    -.@{fa-css-prefix}-quote-right:before { content: @fa-var-quote-right; }
    -.@{fa-css-prefix}-spinner:before { content: @fa-var-spinner; }
    -.@{fa-css-prefix}-circle:before { content: @fa-var-circle; }
    -.@{fa-css-prefix}-mail-reply:before,
    -.@{fa-css-prefix}-reply:before { content: @fa-var-reply; }
    -.@{fa-css-prefix}-github-alt:before { content: @fa-var-github-alt; }
    -.@{fa-css-prefix}-folder-o:before { content: @fa-var-folder-o; }
    -.@{fa-css-prefix}-folder-open-o:before { content: @fa-var-folder-open-o; }
    -.@{fa-css-prefix}-smile-o:before { content: @fa-var-smile-o; }
    -.@{fa-css-prefix}-frown-o:before { content: @fa-var-frown-o; }
    -.@{fa-css-prefix}-meh-o:before { content: @fa-var-meh-o; }
    -.@{fa-css-prefix}-gamepad:before { content: @fa-var-gamepad; }
    -.@{fa-css-prefix}-keyboard-o:before { content: @fa-var-keyboard-o; }
    -.@{fa-css-prefix}-flag-o:before { content: @fa-var-flag-o; }
    -.@{fa-css-prefix}-flag-checkered:before { content: @fa-var-flag-checkered; }
    -.@{fa-css-prefix}-terminal:before { content: @fa-var-terminal; }
    -.@{fa-css-prefix}-code:before { content: @fa-var-code; }
    -.@{fa-css-prefix}-mail-reply-all:before,
    -.@{fa-css-prefix}-reply-all:before { content: @fa-var-reply-all; }
    -.@{fa-css-prefix}-star-half-empty:before,
    -.@{fa-css-prefix}-star-half-full:before,
    -.@{fa-css-prefix}-star-half-o:before { content: @fa-var-star-half-o; }
    -.@{fa-css-prefix}-location-arrow:before { content: @fa-var-location-arrow; }
    -.@{fa-css-prefix}-crop:before { content: @fa-var-crop; }
    -.@{fa-css-prefix}-code-fork:before { content: @fa-var-code-fork; }
    -.@{fa-css-prefix}-unlink:before,
    -.@{fa-css-prefix}-chain-broken:before { content: @fa-var-chain-broken; }
    -.@{fa-css-prefix}-question:before { content: @fa-var-question; }
    -.@{fa-css-prefix}-info:before { content: @fa-var-info; }
    -.@{fa-css-prefix}-exclamation:before { content: @fa-var-exclamation; }
    -.@{fa-css-prefix}-superscript:before { content: @fa-var-superscript; }
    -.@{fa-css-prefix}-subscript:before { content: @fa-var-subscript; }
    -.@{fa-css-prefix}-eraser:before { content: @fa-var-eraser; }
    -.@{fa-css-prefix}-puzzle-piece:before { content: @fa-var-puzzle-piece; }
    -.@{fa-css-prefix}-microphone:before { content: @fa-var-microphone; }
    -.@{fa-css-prefix}-microphone-slash:before { content: @fa-var-microphone-slash; }
    -.@{fa-css-prefix}-shield:before { content: @fa-var-shield; }
    -.@{fa-css-prefix}-calendar-o:before { content: @fa-var-calendar-o; }
    -.@{fa-css-prefix}-fire-extinguisher:before { content: @fa-var-fire-extinguisher; }
    -.@{fa-css-prefix}-rocket:before { content: @fa-var-rocket; }
    -.@{fa-css-prefix}-maxcdn:before { content: @fa-var-maxcdn; }
    -.@{fa-css-prefix}-chevron-circle-left:before { content: @fa-var-chevron-circle-left; }
    -.@{fa-css-prefix}-chevron-circle-right:before { content: @fa-var-chevron-circle-right; }
    -.@{fa-css-prefix}-chevron-circle-up:before { content: @fa-var-chevron-circle-up; }
    -.@{fa-css-prefix}-chevron-circle-down:before { content: @fa-var-chevron-circle-down; }
    -.@{fa-css-prefix}-html5:before { content: @fa-var-html5; }
    -.@{fa-css-prefix}-css3:before { content: @fa-var-css3; }
    -.@{fa-css-prefix}-anchor:before { content: @fa-var-anchor; }
    -.@{fa-css-prefix}-unlock-alt:before { content: @fa-var-unlock-alt; }
    -.@{fa-css-prefix}-bullseye:before { content: @fa-var-bullseye; }
    -.@{fa-css-prefix}-ellipsis-h:before { content: @fa-var-ellipsis-h; }
    -.@{fa-css-prefix}-ellipsis-v:before { content: @fa-var-ellipsis-v; }
    -.@{fa-css-prefix}-rss-square:before { content: @fa-var-rss-square; }
    -.@{fa-css-prefix}-play-circle:before { content: @fa-var-play-circle; }
    -.@{fa-css-prefix}-ticket:before { content: @fa-var-ticket; }
    -.@{fa-css-prefix}-minus-square:before { content: @fa-var-minus-square; }
    -.@{fa-css-prefix}-minus-square-o:before { content: @fa-var-minus-square-o; }
    -.@{fa-css-prefix}-level-up:before { content: @fa-var-level-up; }
    -.@{fa-css-prefix}-level-down:before { content: @fa-var-level-down; }
    -.@{fa-css-prefix}-check-square:before { content: @fa-var-check-square; }
    -.@{fa-css-prefix}-pencil-square:before { content: @fa-var-pencil-square; }
    -.@{fa-css-prefix}-external-link-square:before { content: @fa-var-external-link-square; }
    -.@{fa-css-prefix}-share-square:before { content: @fa-var-share-square; }
    -.@{fa-css-prefix}-compass:before { content: @fa-var-compass; }
    -.@{fa-css-prefix}-toggle-down:before,
    -.@{fa-css-prefix}-caret-square-o-down:before { content: @fa-var-caret-square-o-down; }
    -.@{fa-css-prefix}-toggle-up:before,
    -.@{fa-css-prefix}-caret-square-o-up:before { content: @fa-var-caret-square-o-up; }
    -.@{fa-css-prefix}-toggle-right:before,
    -.@{fa-css-prefix}-caret-square-o-right:before { content: @fa-var-caret-square-o-right; }
    -.@{fa-css-prefix}-euro:before,
    -.@{fa-css-prefix}-eur:before { content: @fa-var-eur; }
    -.@{fa-css-prefix}-gbp:before { content: @fa-var-gbp; }
    -.@{fa-css-prefix}-dollar:before,
    -.@{fa-css-prefix}-usd:before { content: @fa-var-usd; }
    -.@{fa-css-prefix}-rupee:before,
    -.@{fa-css-prefix}-inr:before { content: @fa-var-inr; }
    -.@{fa-css-prefix}-cny:before,
    -.@{fa-css-prefix}-rmb:before,
    -.@{fa-css-prefix}-yen:before,
    -.@{fa-css-prefix}-jpy:before { content: @fa-var-jpy; }
    -.@{fa-css-prefix}-ruble:before,
    -.@{fa-css-prefix}-rouble:before,
    -.@{fa-css-prefix}-rub:before { content: @fa-var-rub; }
    -.@{fa-css-prefix}-won:before,
    -.@{fa-css-prefix}-krw:before { content: @fa-var-krw; }
    -.@{fa-css-prefix}-bitcoin:before,
    -.@{fa-css-prefix}-btc:before { content: @fa-var-btc; }
    -.@{fa-css-prefix}-file:before { content: @fa-var-file; }
    -.@{fa-css-prefix}-file-text:before { content: @fa-var-file-text; }
    -.@{fa-css-prefix}-sort-alpha-asc:before { content: @fa-var-sort-alpha-asc; }
    -.@{fa-css-prefix}-sort-alpha-desc:before { content: @fa-var-sort-alpha-desc; }
    -.@{fa-css-prefix}-sort-amount-asc:before { content: @fa-var-sort-amount-asc; }
    -.@{fa-css-prefix}-sort-amount-desc:before { content: @fa-var-sort-amount-desc; }
    -.@{fa-css-prefix}-sort-numeric-asc:before { content: @fa-var-sort-numeric-asc; }
    -.@{fa-css-prefix}-sort-numeric-desc:before { content: @fa-var-sort-numeric-desc; }
    -.@{fa-css-prefix}-thumbs-up:before { content: @fa-var-thumbs-up; }
    -.@{fa-css-prefix}-thumbs-down:before { content: @fa-var-thumbs-down; }
    -.@{fa-css-prefix}-youtube-square:before { content: @fa-var-youtube-square; }
    -.@{fa-css-prefix}-youtube:before { content: @fa-var-youtube; }
    -.@{fa-css-prefix}-xing:before { content: @fa-var-xing; }
    -.@{fa-css-prefix}-xing-square:before { content: @fa-var-xing-square; }
    -.@{fa-css-prefix}-youtube-play:before { content: @fa-var-youtube-play; }
    -.@{fa-css-prefix}-dropbox:before { content: @fa-var-dropbox; }
    -.@{fa-css-prefix}-stack-overflow:before { content: @fa-var-stack-overflow; }
    -.@{fa-css-prefix}-instagram:before { content: @fa-var-instagram; }
    -.@{fa-css-prefix}-flickr:before { content: @fa-var-flickr; }
    -.@{fa-css-prefix}-adn:before { content: @fa-var-adn; }
    -.@{fa-css-prefix}-bitbucket:before { content: @fa-var-bitbucket; }
    -.@{fa-css-prefix}-bitbucket-square:before { content: @fa-var-bitbucket-square; }
    -.@{fa-css-prefix}-tumblr:before { content: @fa-var-tumblr; }
    -.@{fa-css-prefix}-tumblr-square:before { content: @fa-var-tumblr-square; }
    -.@{fa-css-prefix}-long-arrow-down:before { content: @fa-var-long-arrow-down; }
    -.@{fa-css-prefix}-long-arrow-up:before { content: @fa-var-long-arrow-up; }
    -.@{fa-css-prefix}-long-arrow-left:before { content: @fa-var-long-arrow-left; }
    -.@{fa-css-prefix}-long-arrow-right:before { content: @fa-var-long-arrow-right; }
    -.@{fa-css-prefix}-apple:before { content: @fa-var-apple; }
    -.@{fa-css-prefix}-windows:before { content: @fa-var-windows; }
    -.@{fa-css-prefix}-android:before { content: @fa-var-android; }
    -.@{fa-css-prefix}-linux:before { content: @fa-var-linux; }
    -.@{fa-css-prefix}-dribbble:before { content: @fa-var-dribbble; }
    -.@{fa-css-prefix}-skype:before { content: @fa-var-skype; }
    -.@{fa-css-prefix}-foursquare:before { content: @fa-var-foursquare; }
    -.@{fa-css-prefix}-trello:before { content: @fa-var-trello; }
    -.@{fa-css-prefix}-female:before { content: @fa-var-female; }
    -.@{fa-css-prefix}-male:before { content: @fa-var-male; }
    -.@{fa-css-prefix}-gittip:before { content: @fa-var-gittip; }
    -.@{fa-css-prefix}-sun-o:before { content: @fa-var-sun-o; }
    -.@{fa-css-prefix}-moon-o:before { content: @fa-var-moon-o; }
    -.@{fa-css-prefix}-archive:before { content: @fa-var-archive; }
    -.@{fa-css-prefix}-bug:before { content: @fa-var-bug; }
    -.@{fa-css-prefix}-vk:before { content: @fa-var-vk; }
    -.@{fa-css-prefix}-weibo:before { content: @fa-var-weibo; }
    -.@{fa-css-prefix}-renren:before { content: @fa-var-renren; }
    -.@{fa-css-prefix}-pagelines:before { content: @fa-var-pagelines; }
    -.@{fa-css-prefix}-stack-exchange:before { content: @fa-var-stack-exchange; }
    -.@{fa-css-prefix}-arrow-circle-o-right:before { content: @fa-var-arrow-circle-o-right; }
    -.@{fa-css-prefix}-arrow-circle-o-left:before { content: @fa-var-arrow-circle-o-left; }
    -.@{fa-css-prefix}-toggle-left:before,
    -.@{fa-css-prefix}-caret-square-o-left:before { content: @fa-var-caret-square-o-left; }
    -.@{fa-css-prefix}-dot-circle-o:before { content: @fa-var-dot-circle-o; }
    -.@{fa-css-prefix}-wheelchair:before { content: @fa-var-wheelchair; }
    -.@{fa-css-prefix}-vimeo-square:before { content: @fa-var-vimeo-square; }
    -.@{fa-css-prefix}-turkish-lira:before,
    -.@{fa-css-prefix}-try:before { content: @fa-var-try; }
    -.@{fa-css-prefix}-plus-square-o:before { content: @fa-var-plus-square-o; }
    -.@{fa-css-prefix}-space-shuttle:before { content: @fa-var-space-shuttle; }
    -.@{fa-css-prefix}-slack:before { content: @fa-var-slack; }
    -.@{fa-css-prefix}-envelope-square:before { content: @fa-var-envelope-square; }
    -.@{fa-css-prefix}-wordpress:before { content: @fa-var-wordpress; }
    -.@{fa-css-prefix}-openid:before { content: @fa-var-openid; }
    -.@{fa-css-prefix}-institution:before,
    -.@{fa-css-prefix}-bank:before,
    -.@{fa-css-prefix}-university:before { content: @fa-var-university; }
    -.@{fa-css-prefix}-mortar-board:before,
    -.@{fa-css-prefix}-graduation-cap:before { content: @fa-var-graduation-cap; }
    -.@{fa-css-prefix}-yahoo:before { content: @fa-var-yahoo; }
    -.@{fa-css-prefix}-google:before { content: @fa-var-google; }
    -.@{fa-css-prefix}-reddit:before { content: @fa-var-reddit; }
    -.@{fa-css-prefix}-reddit-square:before { content: @fa-var-reddit-square; }
    -.@{fa-css-prefix}-stumbleupon-circle:before { content: @fa-var-stumbleupon-circle; }
    -.@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; }
    -.@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; }
    -.@{fa-css-prefix}-digg:before { content: @fa-var-digg; }
    -.@{fa-css-prefix}-pied-piper-square:before,
    -.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; }
    -.@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; }
    -.@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; }
    -.@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; }
    -.@{fa-css-prefix}-language:before { content: @fa-var-language; }
    -.@{fa-css-prefix}-fax:before { content: @fa-var-fax; }
    -.@{fa-css-prefix}-building:before { content: @fa-var-building; }
    -.@{fa-css-prefix}-child:before { content: @fa-var-child; }
    -.@{fa-css-prefix}-paw:before { content: @fa-var-paw; }
    -.@{fa-css-prefix}-spoon:before { content: @fa-var-spoon; }
    -.@{fa-css-prefix}-cube:before { content: @fa-var-cube; }
    -.@{fa-css-prefix}-cubes:before { content: @fa-var-cubes; }
    -.@{fa-css-prefix}-behance:before { content: @fa-var-behance; }
    -.@{fa-css-prefix}-behance-square:before { content: @fa-var-behance-square; }
    -.@{fa-css-prefix}-steam:before { content: @fa-var-steam; }
    -.@{fa-css-prefix}-steam-square:before { content: @fa-var-steam-square; }
    -.@{fa-css-prefix}-recycle:before { content: @fa-var-recycle; }
    -.@{fa-css-prefix}-automobile:before,
    -.@{fa-css-prefix}-car:before { content: @fa-var-car; }
    -.@{fa-css-prefix}-cab:before,
    -.@{fa-css-prefix}-taxi:before { content: @fa-var-taxi; }
    -.@{fa-css-prefix}-tree:before { content: @fa-var-tree; }
    -.@{fa-css-prefix}-spotify:before { content: @fa-var-spotify; }
    -.@{fa-css-prefix}-deviantart:before { content: @fa-var-deviantart; }
    -.@{fa-css-prefix}-soundcloud:before { content: @fa-var-soundcloud; }
    -.@{fa-css-prefix}-database:before { content: @fa-var-database; }
    -.@{fa-css-prefix}-file-pdf-o:before { content: @fa-var-file-pdf-o; }
    -.@{fa-css-prefix}-file-word-o:before { content: @fa-var-file-word-o; }
    -.@{fa-css-prefix}-file-excel-o:before { content: @fa-var-file-excel-o; }
    -.@{fa-css-prefix}-file-powerpoint-o:before { content: @fa-var-file-powerpoint-o; }
    -.@{fa-css-prefix}-file-photo-o:before,
    -.@{fa-css-prefix}-file-picture-o:before,
    -.@{fa-css-prefix}-file-image-o:before { content: @fa-var-file-image-o; }
    -.@{fa-css-prefix}-file-zip-o:before,
    -.@{fa-css-prefix}-file-archive-o:before { content: @fa-var-file-archive-o; }
    -.@{fa-css-prefix}-file-sound-o:before,
    -.@{fa-css-prefix}-file-audio-o:before { content: @fa-var-file-audio-o; }
    -.@{fa-css-prefix}-file-movie-o:before,
    -.@{fa-css-prefix}-file-video-o:before { content: @fa-var-file-video-o; }
    -.@{fa-css-prefix}-file-code-o:before { content: @fa-var-file-code-o; }
    -.@{fa-css-prefix}-vine:before { content: @fa-var-vine; }
    -.@{fa-css-prefix}-codepen:before { content: @fa-var-codepen; }
    -.@{fa-css-prefix}-jsfiddle:before { content: @fa-var-jsfiddle; }
    -.@{fa-css-prefix}-life-bouy:before,
    -.@{fa-css-prefix}-life-saver:before,
    -.@{fa-css-prefix}-support:before,
    -.@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; }
    -.@{fa-css-prefix}-circle-o-notch:before { content: @fa-var-circle-o-notch; }
    -.@{fa-css-prefix}-ra:before,
    -.@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; }
    -.@{fa-css-prefix}-ge:before,
    -.@{fa-css-prefix}-empire:before { content: @fa-var-empire; }
    -.@{fa-css-prefix}-git-square:before { content: @fa-var-git-square; }
    -.@{fa-css-prefix}-git:before { content: @fa-var-git; }
    -.@{fa-css-prefix}-hacker-news:before { content: @fa-var-hacker-news; }
    -.@{fa-css-prefix}-tencent-weibo:before { content: @fa-var-tencent-weibo; }
    -.@{fa-css-prefix}-qq:before { content: @fa-var-qq; }
    -.@{fa-css-prefix}-wechat:before,
    -.@{fa-css-prefix}-weixin:before { content: @fa-var-weixin; }
    -.@{fa-css-prefix}-send:before,
    -.@{fa-css-prefix}-paper-plane:before { content: @fa-var-paper-plane; }
    -.@{fa-css-prefix}-send-o:before,
    -.@{fa-css-prefix}-paper-plane-o:before { content: @fa-var-paper-plane-o; }
    -.@{fa-css-prefix}-history:before { content: @fa-var-history; }
    -.@{fa-css-prefix}-circle-thin:before { content: @fa-var-circle-thin; }
    -.@{fa-css-prefix}-header:before { content: @fa-var-header; }
    -.@{fa-css-prefix}-paragraph:before { content: @fa-var-paragraph; }
    -.@{fa-css-prefix}-sliders:before { content: @fa-var-sliders; }
    -.@{fa-css-prefix}-share-alt:before { content: @fa-var-share-alt; }
    -.@{fa-css-prefix}-share-alt-square:before { content: @fa-var-share-alt-square; }
    -.@{fa-css-prefix}-bomb:before { content: @fa-var-bomb; }
    +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
    +   readers do not read off random characters that represent icons */
    +
    +.@{fa-css-prefix}-glass:before { content: @fa-var-glass; }
    +.@{fa-css-prefix}-music:before { content: @fa-var-music; }
    +.@{fa-css-prefix}-search:before { content: @fa-var-search; }
    +.@{fa-css-prefix}-envelope-o:before { content: @fa-var-envelope-o; }
    +.@{fa-css-prefix}-heart:before { content: @fa-var-heart; }
    +.@{fa-css-prefix}-star:before { content: @fa-var-star; }
    +.@{fa-css-prefix}-star-o:before { content: @fa-var-star-o; }
    +.@{fa-css-prefix}-user:before { content: @fa-var-user; }
    +.@{fa-css-prefix}-film:before { content: @fa-var-film; }
    +.@{fa-css-prefix}-th-large:before { content: @fa-var-th-large; }
    +.@{fa-css-prefix}-th:before { content: @fa-var-th; }
    +.@{fa-css-prefix}-th-list:before { content: @fa-var-th-list; }
    +.@{fa-css-prefix}-check:before { content: @fa-var-check; }
    +.@{fa-css-prefix}-times:before { content: @fa-var-times; }
    +.@{fa-css-prefix}-search-plus:before { content: @fa-var-search-plus; }
    +.@{fa-css-prefix}-search-minus:before { content: @fa-var-search-minus; }
    +.@{fa-css-prefix}-power-off:before { content: @fa-var-power-off; }
    +.@{fa-css-prefix}-signal:before { content: @fa-var-signal; }
    +.@{fa-css-prefix}-gear:before,
    +.@{fa-css-prefix}-cog:before { content: @fa-var-cog; }
    +.@{fa-css-prefix}-trash-o:before { content: @fa-var-trash-o; }
    +.@{fa-css-prefix}-home:before { content: @fa-var-home; }
    +.@{fa-css-prefix}-file-o:before { content: @fa-var-file-o; }
    +.@{fa-css-prefix}-clock-o:before { content: @fa-var-clock-o; }
    +.@{fa-css-prefix}-road:before { content: @fa-var-road; }
    +.@{fa-css-prefix}-download:before { content: @fa-var-download; }
    +.@{fa-css-prefix}-arrow-circle-o-down:before { content: @fa-var-arrow-circle-o-down; }
    +.@{fa-css-prefix}-arrow-circle-o-up:before { content: @fa-var-arrow-circle-o-up; }
    +.@{fa-css-prefix}-inbox:before { content: @fa-var-inbox; }
    +.@{fa-css-prefix}-play-circle-o:before { content: @fa-var-play-circle-o; }
    +.@{fa-css-prefix}-rotate-right:before,
    +.@{fa-css-prefix}-repeat:before { content: @fa-var-repeat; }
    +.@{fa-css-prefix}-refresh:before { content: @fa-var-refresh; }
    +.@{fa-css-prefix}-list-alt:before { content: @fa-var-list-alt; }
    +.@{fa-css-prefix}-lock:before { content: @fa-var-lock; }
    +.@{fa-css-prefix}-flag:before { content: @fa-var-flag; }
    +.@{fa-css-prefix}-headphones:before { content: @fa-var-headphones; }
    +.@{fa-css-prefix}-volume-off:before { content: @fa-var-volume-off; }
    +.@{fa-css-prefix}-volume-down:before { content: @fa-var-volume-down; }
    +.@{fa-css-prefix}-volume-up:before { content: @fa-var-volume-up; }
    +.@{fa-css-prefix}-qrcode:before { content: @fa-var-qrcode; }
    +.@{fa-css-prefix}-barcode:before { content: @fa-var-barcode; }
    +.@{fa-css-prefix}-tag:before { content: @fa-var-tag; }
    +.@{fa-css-prefix}-tags:before { content: @fa-var-tags; }
    +.@{fa-css-prefix}-book:before { content: @fa-var-book; }
    +.@{fa-css-prefix}-bookmark:before { content: @fa-var-bookmark; }
    +.@{fa-css-prefix}-print:before { content: @fa-var-print; }
    +.@{fa-css-prefix}-camera:before { content: @fa-var-camera; }
    +.@{fa-css-prefix}-font:before { content: @fa-var-font; }
    +.@{fa-css-prefix}-bold:before { content: @fa-var-bold; }
    +.@{fa-css-prefix}-italic:before { content: @fa-var-italic; }
    +.@{fa-css-prefix}-text-height:before { content: @fa-var-text-height; }
    +.@{fa-css-prefix}-text-width:before { content: @fa-var-text-width; }
    +.@{fa-css-prefix}-align-left:before { content: @fa-var-align-left; }
    +.@{fa-css-prefix}-align-center:before { content: @fa-var-align-center; }
    +.@{fa-css-prefix}-align-right:before { content: @fa-var-align-right; }
    +.@{fa-css-prefix}-align-justify:before { content: @fa-var-align-justify; }
    +.@{fa-css-prefix}-list:before { content: @fa-var-list; }
    +.@{fa-css-prefix}-dedent:before,
    +.@{fa-css-prefix}-outdent:before { content: @fa-var-outdent; }
    +.@{fa-css-prefix}-indent:before { content: @fa-var-indent; }
    +.@{fa-css-prefix}-video-camera:before { content: @fa-var-video-camera; }
    +.@{fa-css-prefix}-photo:before,
    +.@{fa-css-prefix}-image:before,
    +.@{fa-css-prefix}-picture-o:before { content: @fa-var-picture-o; }
    +.@{fa-css-prefix}-pencil:before { content: @fa-var-pencil; }
    +.@{fa-css-prefix}-map-marker:before { content: @fa-var-map-marker; }
    +.@{fa-css-prefix}-adjust:before { content: @fa-var-adjust; }
    +.@{fa-css-prefix}-tint:before { content: @fa-var-tint; }
    +.@{fa-css-prefix}-edit:before,
    +.@{fa-css-prefix}-pencil-square-o:before { content: @fa-var-pencil-square-o; }
    +.@{fa-css-prefix}-share-square-o:before { content: @fa-var-share-square-o; }
    +.@{fa-css-prefix}-check-square-o:before { content: @fa-var-check-square-o; }
    +.@{fa-css-prefix}-arrows:before { content: @fa-var-arrows; }
    +.@{fa-css-prefix}-step-backward:before { content: @fa-var-step-backward; }
    +.@{fa-css-prefix}-fast-backward:before { content: @fa-var-fast-backward; }
    +.@{fa-css-prefix}-backward:before { content: @fa-var-backward; }
    +.@{fa-css-prefix}-play:before { content: @fa-var-play; }
    +.@{fa-css-prefix}-pause:before { content: @fa-var-pause; }
    +.@{fa-css-prefix}-stop:before { content: @fa-var-stop; }
    +.@{fa-css-prefix}-forward:before { content: @fa-var-forward; }
    +.@{fa-css-prefix}-fast-forward:before { content: @fa-var-fast-forward; }
    +.@{fa-css-prefix}-step-forward:before { content: @fa-var-step-forward; }
    +.@{fa-css-prefix}-eject:before { content: @fa-var-eject; }
    +.@{fa-css-prefix}-chevron-left:before { content: @fa-var-chevron-left; }
    +.@{fa-css-prefix}-chevron-right:before { content: @fa-var-chevron-right; }
    +.@{fa-css-prefix}-plus-circle:before { content: @fa-var-plus-circle; }
    +.@{fa-css-prefix}-minus-circle:before { content: @fa-var-minus-circle; }
    +.@{fa-css-prefix}-times-circle:before { content: @fa-var-times-circle; }
    +.@{fa-css-prefix}-check-circle:before { content: @fa-var-check-circle; }
    +.@{fa-css-prefix}-question-circle:before { content: @fa-var-question-circle; }
    +.@{fa-css-prefix}-info-circle:before { content: @fa-var-info-circle; }
    +.@{fa-css-prefix}-crosshairs:before { content: @fa-var-crosshairs; }
    +.@{fa-css-prefix}-times-circle-o:before { content: @fa-var-times-circle-o; }
    +.@{fa-css-prefix}-check-circle-o:before { content: @fa-var-check-circle-o; }
    +.@{fa-css-prefix}-ban:before { content: @fa-var-ban; }
    +.@{fa-css-prefix}-arrow-left:before { content: @fa-var-arrow-left; }
    +.@{fa-css-prefix}-arrow-right:before { content: @fa-var-arrow-right; }
    +.@{fa-css-prefix}-arrow-up:before { content: @fa-var-arrow-up; }
    +.@{fa-css-prefix}-arrow-down:before { content: @fa-var-arrow-down; }
    +.@{fa-css-prefix}-mail-forward:before,
    +.@{fa-css-prefix}-share:before { content: @fa-var-share; }
    +.@{fa-css-prefix}-expand:before { content: @fa-var-expand; }
    +.@{fa-css-prefix}-compress:before { content: @fa-var-compress; }
    +.@{fa-css-prefix}-plus:before { content: @fa-var-plus; }
    +.@{fa-css-prefix}-minus:before { content: @fa-var-minus; }
    +.@{fa-css-prefix}-asterisk:before { content: @fa-var-asterisk; }
    +.@{fa-css-prefix}-exclamation-circle:before { content: @fa-var-exclamation-circle; }
    +.@{fa-css-prefix}-gift:before { content: @fa-var-gift; }
    +.@{fa-css-prefix}-leaf:before { content: @fa-var-leaf; }
    +.@{fa-css-prefix}-fire:before { content: @fa-var-fire; }
    +.@{fa-css-prefix}-eye:before { content: @fa-var-eye; }
    +.@{fa-css-prefix}-eye-slash:before { content: @fa-var-eye-slash; }
    +.@{fa-css-prefix}-warning:before,
    +.@{fa-css-prefix}-exclamation-triangle:before { content: @fa-var-exclamation-triangle; }
    +.@{fa-css-prefix}-plane:before { content: @fa-var-plane; }
    +.@{fa-css-prefix}-calendar:before { content: @fa-var-calendar; }
    +.@{fa-css-prefix}-random:before { content: @fa-var-random; }
    +.@{fa-css-prefix}-comment:before { content: @fa-var-comment; }
    +.@{fa-css-prefix}-magnet:before { content: @fa-var-magnet; }
    +.@{fa-css-prefix}-chevron-up:before { content: @fa-var-chevron-up; }
    +.@{fa-css-prefix}-chevron-down:before { content: @fa-var-chevron-down; }
    +.@{fa-css-prefix}-retweet:before { content: @fa-var-retweet; }
    +.@{fa-css-prefix}-shopping-cart:before { content: @fa-var-shopping-cart; }
    +.@{fa-css-prefix}-folder:before { content: @fa-var-folder; }
    +.@{fa-css-prefix}-folder-open:before { content: @fa-var-folder-open; }
    +.@{fa-css-prefix}-arrows-v:before { content: @fa-var-arrows-v; }
    +.@{fa-css-prefix}-arrows-h:before { content: @fa-var-arrows-h; }
    +.@{fa-css-prefix}-bar-chart-o:before { content: @fa-var-bar-chart-o; }
    +.@{fa-css-prefix}-twitter-square:before { content: @fa-var-twitter-square; }
    +.@{fa-css-prefix}-facebook-square:before { content: @fa-var-facebook-square; }
    +.@{fa-css-prefix}-camera-retro:before { content: @fa-var-camera-retro; }
    +.@{fa-css-prefix}-key:before { content: @fa-var-key; }
    +.@{fa-css-prefix}-gears:before,
    +.@{fa-css-prefix}-cogs:before { content: @fa-var-cogs; }
    +.@{fa-css-prefix}-comments:before { content: @fa-var-comments; }
    +.@{fa-css-prefix}-thumbs-o-up:before { content: @fa-var-thumbs-o-up; }
    +.@{fa-css-prefix}-thumbs-o-down:before { content: @fa-var-thumbs-o-down; }
    +.@{fa-css-prefix}-star-half:before { content: @fa-var-star-half; }
    +.@{fa-css-prefix}-heart-o:before { content: @fa-var-heart-o; }
    +.@{fa-css-prefix}-sign-out:before { content: @fa-var-sign-out; }
    +.@{fa-css-prefix}-linkedin-square:before { content: @fa-var-linkedin-square; }
    +.@{fa-css-prefix}-thumb-tack:before { content: @fa-var-thumb-tack; }
    +.@{fa-css-prefix}-external-link:before { content: @fa-var-external-link; }
    +.@{fa-css-prefix}-sign-in:before { content: @fa-var-sign-in; }
    +.@{fa-css-prefix}-trophy:before { content: @fa-var-trophy; }
    +.@{fa-css-prefix}-github-square:before { content: @fa-var-github-square; }
    +.@{fa-css-prefix}-upload:before { content: @fa-var-upload; }
    +.@{fa-css-prefix}-lemon-o:before { content: @fa-var-lemon-o; }
    +.@{fa-css-prefix}-phone:before { content: @fa-var-phone; }
    +.@{fa-css-prefix}-square-o:before { content: @fa-var-square-o; }
    +.@{fa-css-prefix}-bookmark-o:before { content: @fa-var-bookmark-o; }
    +.@{fa-css-prefix}-phone-square:before { content: @fa-var-phone-square; }
    +.@{fa-css-prefix}-twitter:before { content: @fa-var-twitter; }
    +.@{fa-css-prefix}-facebook:before { content: @fa-var-facebook; }
    +.@{fa-css-prefix}-github:before { content: @fa-var-github; }
    +.@{fa-css-prefix}-unlock:before { content: @fa-var-unlock; }
    +.@{fa-css-prefix}-credit-card:before { content: @fa-var-credit-card; }
    +.@{fa-css-prefix}-rss:before { content: @fa-var-rss; }
    +.@{fa-css-prefix}-hdd-o:before { content: @fa-var-hdd-o; }
    +.@{fa-css-prefix}-bullhorn:before { content: @fa-var-bullhorn; }
    +.@{fa-css-prefix}-bell:before { content: @fa-var-bell; }
    +.@{fa-css-prefix}-certificate:before { content: @fa-var-certificate; }
    +.@{fa-css-prefix}-hand-o-right:before { content: @fa-var-hand-o-right; }
    +.@{fa-css-prefix}-hand-o-left:before { content: @fa-var-hand-o-left; }
    +.@{fa-css-prefix}-hand-o-up:before { content: @fa-var-hand-o-up; }
    +.@{fa-css-prefix}-hand-o-down:before { content: @fa-var-hand-o-down; }
    +.@{fa-css-prefix}-arrow-circle-left:before { content: @fa-var-arrow-circle-left; }
    +.@{fa-css-prefix}-arrow-circle-right:before { content: @fa-var-arrow-circle-right; }
    +.@{fa-css-prefix}-arrow-circle-up:before { content: @fa-var-arrow-circle-up; }
    +.@{fa-css-prefix}-arrow-circle-down:before { content: @fa-var-arrow-circle-down; }
    +.@{fa-css-prefix}-globe:before { content: @fa-var-globe; }
    +.@{fa-css-prefix}-wrench:before { content: @fa-var-wrench; }
    +.@{fa-css-prefix}-tasks:before { content: @fa-var-tasks; }
    +.@{fa-css-prefix}-filter:before { content: @fa-var-filter; }
    +.@{fa-css-prefix}-briefcase:before { content: @fa-var-briefcase; }
    +.@{fa-css-prefix}-arrows-alt:before { content: @fa-var-arrows-alt; }
    +.@{fa-css-prefix}-group:before,
    +.@{fa-css-prefix}-users:before { content: @fa-var-users; }
    +.@{fa-css-prefix}-chain:before,
    +.@{fa-css-prefix}-link:before { content: @fa-var-link; }
    +.@{fa-css-prefix}-cloud:before { content: @fa-var-cloud; }
    +.@{fa-css-prefix}-flask:before { content: @fa-var-flask; }
    +.@{fa-css-prefix}-cut:before,
    +.@{fa-css-prefix}-scissors:before { content: @fa-var-scissors; }
    +.@{fa-css-prefix}-copy:before,
    +.@{fa-css-prefix}-files-o:before { content: @fa-var-files-o; }
    +.@{fa-css-prefix}-paperclip:before { content: @fa-var-paperclip; }
    +.@{fa-css-prefix}-save:before,
    +.@{fa-css-prefix}-floppy-o:before { content: @fa-var-floppy-o; }
    +.@{fa-css-prefix}-square:before { content: @fa-var-square; }
    +.@{fa-css-prefix}-navicon:before,
    +.@{fa-css-prefix}-reorder:before,
    +.@{fa-css-prefix}-bars:before { content: @fa-var-bars; }
    +.@{fa-css-prefix}-list-ul:before { content: @fa-var-list-ul; }
    +.@{fa-css-prefix}-list-ol:before { content: @fa-var-list-ol; }
    +.@{fa-css-prefix}-strikethrough:before { content: @fa-var-strikethrough; }
    +.@{fa-css-prefix}-underline:before { content: @fa-var-underline; }
    +.@{fa-css-prefix}-table:before { content: @fa-var-table; }
    +.@{fa-css-prefix}-magic:before { content: @fa-var-magic; }
    +.@{fa-css-prefix}-truck:before { content: @fa-var-truck; }
    +.@{fa-css-prefix}-pinterest:before { content: @fa-var-pinterest; }
    +.@{fa-css-prefix}-pinterest-square:before { content: @fa-var-pinterest-square; }
    +.@{fa-css-prefix}-google-plus-square:before { content: @fa-var-google-plus-square; }
    +.@{fa-css-prefix}-google-plus:before { content: @fa-var-google-plus; }
    +.@{fa-css-prefix}-money:before { content: @fa-var-money; }
    +.@{fa-css-prefix}-caret-down:before { content: @fa-var-caret-down; }
    +.@{fa-css-prefix}-caret-up:before { content: @fa-var-caret-up; }
    +.@{fa-css-prefix}-caret-left:before { content: @fa-var-caret-left; }
    +.@{fa-css-prefix}-caret-right:before { content: @fa-var-caret-right; }
    +.@{fa-css-prefix}-columns:before { content: @fa-var-columns; }
    +.@{fa-css-prefix}-unsorted:before,
    +.@{fa-css-prefix}-sort:before { content: @fa-var-sort; }
    +.@{fa-css-prefix}-sort-down:before,
    +.@{fa-css-prefix}-sort-desc:before { content: @fa-var-sort-desc; }
    +.@{fa-css-prefix}-sort-up:before,
    +.@{fa-css-prefix}-sort-asc:before { content: @fa-var-sort-asc; }
    +.@{fa-css-prefix}-envelope:before { content: @fa-var-envelope; }
    +.@{fa-css-prefix}-linkedin:before { content: @fa-var-linkedin; }
    +.@{fa-css-prefix}-rotate-left:before,
    +.@{fa-css-prefix}-undo:before { content: @fa-var-undo; }
    +.@{fa-css-prefix}-legal:before,
    +.@{fa-css-prefix}-gavel:before { content: @fa-var-gavel; }
    +.@{fa-css-prefix}-dashboard:before,
    +.@{fa-css-prefix}-tachometer:before { content: @fa-var-tachometer; }
    +.@{fa-css-prefix}-comment-o:before { content: @fa-var-comment-o; }
    +.@{fa-css-prefix}-comments-o:before { content: @fa-var-comments-o; }
    +.@{fa-css-prefix}-flash:before,
    +.@{fa-css-prefix}-bolt:before { content: @fa-var-bolt; }
    +.@{fa-css-prefix}-sitemap:before { content: @fa-var-sitemap; }
    +.@{fa-css-prefix}-umbrella:before { content: @fa-var-umbrella; }
    +.@{fa-css-prefix}-paste:before,
    +.@{fa-css-prefix}-clipboard:before { content: @fa-var-clipboard; }
    +.@{fa-css-prefix}-lightbulb-o:before { content: @fa-var-lightbulb-o; }
    +.@{fa-css-prefix}-exchange:before { content: @fa-var-exchange; }
    +.@{fa-css-prefix}-cloud-download:before { content: @fa-var-cloud-download; }
    +.@{fa-css-prefix}-cloud-upload:before { content: @fa-var-cloud-upload; }
    +.@{fa-css-prefix}-user-md:before { content: @fa-var-user-md; }
    +.@{fa-css-prefix}-stethoscope:before { content: @fa-var-stethoscope; }
    +.@{fa-css-prefix}-suitcase:before { content: @fa-var-suitcase; }
    +.@{fa-css-prefix}-bell-o:before { content: @fa-var-bell-o; }
    +.@{fa-css-prefix}-coffee:before { content: @fa-var-coffee; }
    +.@{fa-css-prefix}-cutlery:before { content: @fa-var-cutlery; }
    +.@{fa-css-prefix}-file-text-o:before { content: @fa-var-file-text-o; }
    +.@{fa-css-prefix}-building-o:before { content: @fa-var-building-o; }
    +.@{fa-css-prefix}-hospital-o:before { content: @fa-var-hospital-o; }
    +.@{fa-css-prefix}-ambulance:before { content: @fa-var-ambulance; }
    +.@{fa-css-prefix}-medkit:before { content: @fa-var-medkit; }
    +.@{fa-css-prefix}-fighter-jet:before { content: @fa-var-fighter-jet; }
    +.@{fa-css-prefix}-beer:before { content: @fa-var-beer; }
    +.@{fa-css-prefix}-h-square:before { content: @fa-var-h-square; }
    +.@{fa-css-prefix}-plus-square:before { content: @fa-var-plus-square; }
    +.@{fa-css-prefix}-angle-double-left:before { content: @fa-var-angle-double-left; }
    +.@{fa-css-prefix}-angle-double-right:before { content: @fa-var-angle-double-right; }
    +.@{fa-css-prefix}-angle-double-up:before { content: @fa-var-angle-double-up; }
    +.@{fa-css-prefix}-angle-double-down:before { content: @fa-var-angle-double-down; }
    +.@{fa-css-prefix}-angle-left:before { content: @fa-var-angle-left; }
    +.@{fa-css-prefix}-angle-right:before { content: @fa-var-angle-right; }
    +.@{fa-css-prefix}-angle-up:before { content: @fa-var-angle-up; }
    +.@{fa-css-prefix}-angle-down:before { content: @fa-var-angle-down; }
    +.@{fa-css-prefix}-desktop:before { content: @fa-var-desktop; }
    +.@{fa-css-prefix}-laptop:before { content: @fa-var-laptop; }
    +.@{fa-css-prefix}-tablet:before { content: @fa-var-tablet; }
    +.@{fa-css-prefix}-mobile-phone:before,
    +.@{fa-css-prefix}-mobile:before { content: @fa-var-mobile; }
    +.@{fa-css-prefix}-circle-o:before { content: @fa-var-circle-o; }
    +.@{fa-css-prefix}-quote-left:before { content: @fa-var-quote-left; }
    +.@{fa-css-prefix}-quote-right:before { content: @fa-var-quote-right; }
    +.@{fa-css-prefix}-spinner:before { content: @fa-var-spinner; }
    +.@{fa-css-prefix}-circle:before { content: @fa-var-circle; }
    +.@{fa-css-prefix}-mail-reply:before,
    +.@{fa-css-prefix}-reply:before { content: @fa-var-reply; }
    +.@{fa-css-prefix}-github-alt:before { content: @fa-var-github-alt; }
    +.@{fa-css-prefix}-folder-o:before { content: @fa-var-folder-o; }
    +.@{fa-css-prefix}-folder-open-o:before { content: @fa-var-folder-open-o; }
    +.@{fa-css-prefix}-smile-o:before { content: @fa-var-smile-o; }
    +.@{fa-css-prefix}-frown-o:before { content: @fa-var-frown-o; }
    +.@{fa-css-prefix}-meh-o:before { content: @fa-var-meh-o; }
    +.@{fa-css-prefix}-gamepad:before { content: @fa-var-gamepad; }
    +.@{fa-css-prefix}-keyboard-o:before { content: @fa-var-keyboard-o; }
    +.@{fa-css-prefix}-flag-o:before { content: @fa-var-flag-o; }
    +.@{fa-css-prefix}-flag-checkered:before { content: @fa-var-flag-checkered; }
    +.@{fa-css-prefix}-terminal:before { content: @fa-var-terminal; }
    +.@{fa-css-prefix}-code:before { content: @fa-var-code; }
    +.@{fa-css-prefix}-mail-reply-all:before,
    +.@{fa-css-prefix}-reply-all:before { content: @fa-var-reply-all; }
    +.@{fa-css-prefix}-star-half-empty:before,
    +.@{fa-css-prefix}-star-half-full:before,
    +.@{fa-css-prefix}-star-half-o:before { content: @fa-var-star-half-o; }
    +.@{fa-css-prefix}-location-arrow:before { content: @fa-var-location-arrow; }
    +.@{fa-css-prefix}-crop:before { content: @fa-var-crop; }
    +.@{fa-css-prefix}-code-fork:before { content: @fa-var-code-fork; }
    +.@{fa-css-prefix}-unlink:before,
    +.@{fa-css-prefix}-chain-broken:before { content: @fa-var-chain-broken; }
    +.@{fa-css-prefix}-question:before { content: @fa-var-question; }
    +.@{fa-css-prefix}-info:before { content: @fa-var-info; }
    +.@{fa-css-prefix}-exclamation:before { content: @fa-var-exclamation; }
    +.@{fa-css-prefix}-superscript:before { content: @fa-var-superscript; }
    +.@{fa-css-prefix}-subscript:before { content: @fa-var-subscript; }
    +.@{fa-css-prefix}-eraser:before { content: @fa-var-eraser; }
    +.@{fa-css-prefix}-puzzle-piece:before { content: @fa-var-puzzle-piece; }
    +.@{fa-css-prefix}-microphone:before { content: @fa-var-microphone; }
    +.@{fa-css-prefix}-microphone-slash:before { content: @fa-var-microphone-slash; }
    +.@{fa-css-prefix}-shield:before { content: @fa-var-shield; }
    +.@{fa-css-prefix}-calendar-o:before { content: @fa-var-calendar-o; }
    +.@{fa-css-prefix}-fire-extinguisher:before { content: @fa-var-fire-extinguisher; }
    +.@{fa-css-prefix}-rocket:before { content: @fa-var-rocket; }
    +.@{fa-css-prefix}-maxcdn:before { content: @fa-var-maxcdn; }
    +.@{fa-css-prefix}-chevron-circle-left:before { content: @fa-var-chevron-circle-left; }
    +.@{fa-css-prefix}-chevron-circle-right:before { content: @fa-var-chevron-circle-right; }
    +.@{fa-css-prefix}-chevron-circle-up:before { content: @fa-var-chevron-circle-up; }
    +.@{fa-css-prefix}-chevron-circle-down:before { content: @fa-var-chevron-circle-down; }
    +.@{fa-css-prefix}-html5:before { content: @fa-var-html5; }
    +.@{fa-css-prefix}-css3:before { content: @fa-var-css3; }
    +.@{fa-css-prefix}-anchor:before { content: @fa-var-anchor; }
    +.@{fa-css-prefix}-unlock-alt:before { content: @fa-var-unlock-alt; }
    +.@{fa-css-prefix}-bullseye:before { content: @fa-var-bullseye; }
    +.@{fa-css-prefix}-ellipsis-h:before { content: @fa-var-ellipsis-h; }
    +.@{fa-css-prefix}-ellipsis-v:before { content: @fa-var-ellipsis-v; }
    +.@{fa-css-prefix}-rss-square:before { content: @fa-var-rss-square; }
    +.@{fa-css-prefix}-play-circle:before { content: @fa-var-play-circle; }
    +.@{fa-css-prefix}-ticket:before { content: @fa-var-ticket; }
    +.@{fa-css-prefix}-minus-square:before { content: @fa-var-minus-square; }
    +.@{fa-css-prefix}-minus-square-o:before { content: @fa-var-minus-square-o; }
    +.@{fa-css-prefix}-level-up:before { content: @fa-var-level-up; }
    +.@{fa-css-prefix}-level-down:before { content: @fa-var-level-down; }
    +.@{fa-css-prefix}-check-square:before { content: @fa-var-check-square; }
    +.@{fa-css-prefix}-pencil-square:before { content: @fa-var-pencil-square; }
    +.@{fa-css-prefix}-external-link-square:before { content: @fa-var-external-link-square; }
    +.@{fa-css-prefix}-share-square:before { content: @fa-var-share-square; }
    +.@{fa-css-prefix}-compass:before { content: @fa-var-compass; }
    +.@{fa-css-prefix}-toggle-down:before,
    +.@{fa-css-prefix}-caret-square-o-down:before { content: @fa-var-caret-square-o-down; }
    +.@{fa-css-prefix}-toggle-up:before,
    +.@{fa-css-prefix}-caret-square-o-up:before { content: @fa-var-caret-square-o-up; }
    +.@{fa-css-prefix}-toggle-right:before,
    +.@{fa-css-prefix}-caret-square-o-right:before { content: @fa-var-caret-square-o-right; }
    +.@{fa-css-prefix}-euro:before,
    +.@{fa-css-prefix}-eur:before { content: @fa-var-eur; }
    +.@{fa-css-prefix}-gbp:before { content: @fa-var-gbp; }
    +.@{fa-css-prefix}-dollar:before,
    +.@{fa-css-prefix}-usd:before { content: @fa-var-usd; }
    +.@{fa-css-prefix}-rupee:before,
    +.@{fa-css-prefix}-inr:before { content: @fa-var-inr; }
    +.@{fa-css-prefix}-cny:before,
    +.@{fa-css-prefix}-rmb:before,
    +.@{fa-css-prefix}-yen:before,
    +.@{fa-css-prefix}-jpy:before { content: @fa-var-jpy; }
    +.@{fa-css-prefix}-ruble:before,
    +.@{fa-css-prefix}-rouble:before,
    +.@{fa-css-prefix}-rub:before { content: @fa-var-rub; }
    +.@{fa-css-prefix}-won:before,
    +.@{fa-css-prefix}-krw:before { content: @fa-var-krw; }
    +.@{fa-css-prefix}-bitcoin:before,
    +.@{fa-css-prefix}-btc:before { content: @fa-var-btc; }
    +.@{fa-css-prefix}-file:before { content: @fa-var-file; }
    +.@{fa-css-prefix}-file-text:before { content: @fa-var-file-text; }
    +.@{fa-css-prefix}-sort-alpha-asc:before { content: @fa-var-sort-alpha-asc; }
    +.@{fa-css-prefix}-sort-alpha-desc:before { content: @fa-var-sort-alpha-desc; }
    +.@{fa-css-prefix}-sort-amount-asc:before { content: @fa-var-sort-amount-asc; }
    +.@{fa-css-prefix}-sort-amount-desc:before { content: @fa-var-sort-amount-desc; }
    +.@{fa-css-prefix}-sort-numeric-asc:before { content: @fa-var-sort-numeric-asc; }
    +.@{fa-css-prefix}-sort-numeric-desc:before { content: @fa-var-sort-numeric-desc; }
    +.@{fa-css-prefix}-thumbs-up:before { content: @fa-var-thumbs-up; }
    +.@{fa-css-prefix}-thumbs-down:before { content: @fa-var-thumbs-down; }
    +.@{fa-css-prefix}-youtube-square:before { content: @fa-var-youtube-square; }
    +.@{fa-css-prefix}-youtube:before { content: @fa-var-youtube; }
    +.@{fa-css-prefix}-xing:before { content: @fa-var-xing; }
    +.@{fa-css-prefix}-xing-square:before { content: @fa-var-xing-square; }
    +.@{fa-css-prefix}-youtube-play:before { content: @fa-var-youtube-play; }
    +.@{fa-css-prefix}-dropbox:before { content: @fa-var-dropbox; }
    +.@{fa-css-prefix}-stack-overflow:before { content: @fa-var-stack-overflow; }
    +.@{fa-css-prefix}-instagram:before { content: @fa-var-instagram; }
    +.@{fa-css-prefix}-flickr:before { content: @fa-var-flickr; }
    +.@{fa-css-prefix}-adn:before { content: @fa-var-adn; }
    +.@{fa-css-prefix}-bitbucket:before { content: @fa-var-bitbucket; }
    +.@{fa-css-prefix}-bitbucket-square:before { content: @fa-var-bitbucket-square; }
    +.@{fa-css-prefix}-tumblr:before { content: @fa-var-tumblr; }
    +.@{fa-css-prefix}-tumblr-square:before { content: @fa-var-tumblr-square; }
    +.@{fa-css-prefix}-long-arrow-down:before { content: @fa-var-long-arrow-down; }
    +.@{fa-css-prefix}-long-arrow-up:before { content: @fa-var-long-arrow-up; }
    +.@{fa-css-prefix}-long-arrow-left:before { content: @fa-var-long-arrow-left; }
    +.@{fa-css-prefix}-long-arrow-right:before { content: @fa-var-long-arrow-right; }
    +.@{fa-css-prefix}-apple:before { content: @fa-var-apple; }
    +.@{fa-css-prefix}-windows:before { content: @fa-var-windows; }
    +.@{fa-css-prefix}-android:before { content: @fa-var-android; }
    +.@{fa-css-prefix}-linux:before { content: @fa-var-linux; }
    +.@{fa-css-prefix}-dribbble:before { content: @fa-var-dribbble; }
    +.@{fa-css-prefix}-skype:before { content: @fa-var-skype; }
    +.@{fa-css-prefix}-foursquare:before { content: @fa-var-foursquare; }
    +.@{fa-css-prefix}-trello:before { content: @fa-var-trello; }
    +.@{fa-css-prefix}-female:before { content: @fa-var-female; }
    +.@{fa-css-prefix}-male:before { content: @fa-var-male; }
    +.@{fa-css-prefix}-gittip:before { content: @fa-var-gittip; }
    +.@{fa-css-prefix}-sun-o:before { content: @fa-var-sun-o; }
    +.@{fa-css-prefix}-moon-o:before { content: @fa-var-moon-o; }
    +.@{fa-css-prefix}-archive:before { content: @fa-var-archive; }
    +.@{fa-css-prefix}-bug:before { content: @fa-var-bug; }
    +.@{fa-css-prefix}-vk:before { content: @fa-var-vk; }
    +.@{fa-css-prefix}-weibo:before { content: @fa-var-weibo; }
    +.@{fa-css-prefix}-renren:before { content: @fa-var-renren; }
    +.@{fa-css-prefix}-pagelines:before { content: @fa-var-pagelines; }
    +.@{fa-css-prefix}-stack-exchange:before { content: @fa-var-stack-exchange; }
    +.@{fa-css-prefix}-arrow-circle-o-right:before { content: @fa-var-arrow-circle-o-right; }
    +.@{fa-css-prefix}-arrow-circle-o-left:before { content: @fa-var-arrow-circle-o-left; }
    +.@{fa-css-prefix}-toggle-left:before,
    +.@{fa-css-prefix}-caret-square-o-left:before { content: @fa-var-caret-square-o-left; }
    +.@{fa-css-prefix}-dot-circle-o:before { content: @fa-var-dot-circle-o; }
    +.@{fa-css-prefix}-wheelchair:before { content: @fa-var-wheelchair; }
    +.@{fa-css-prefix}-vimeo-square:before { content: @fa-var-vimeo-square; }
    +.@{fa-css-prefix}-turkish-lira:before,
    +.@{fa-css-prefix}-try:before { content: @fa-var-try; }
    +.@{fa-css-prefix}-plus-square-o:before { content: @fa-var-plus-square-o; }
    +.@{fa-css-prefix}-space-shuttle:before { content: @fa-var-space-shuttle; }
    +.@{fa-css-prefix}-slack:before { content: @fa-var-slack; }
    +.@{fa-css-prefix}-envelope-square:before { content: @fa-var-envelope-square; }
    +.@{fa-css-prefix}-wordpress:before { content: @fa-var-wordpress; }
    +.@{fa-css-prefix}-openid:before { content: @fa-var-openid; }
    +.@{fa-css-prefix}-institution:before,
    +.@{fa-css-prefix}-bank:before,
    +.@{fa-css-prefix}-university:before { content: @fa-var-university; }
    +.@{fa-css-prefix}-mortar-board:before,
    +.@{fa-css-prefix}-graduation-cap:before { content: @fa-var-graduation-cap; }
    +.@{fa-css-prefix}-yahoo:before { content: @fa-var-yahoo; }
    +.@{fa-css-prefix}-google:before { content: @fa-var-google; }
    +.@{fa-css-prefix}-reddit:before { content: @fa-var-reddit; }
    +.@{fa-css-prefix}-reddit-square:before { content: @fa-var-reddit-square; }
    +.@{fa-css-prefix}-stumbleupon-circle:before { content: @fa-var-stumbleupon-circle; }
    +.@{fa-css-prefix}-stumbleupon:before { content: @fa-var-stumbleupon; }
    +.@{fa-css-prefix}-delicious:before { content: @fa-var-delicious; }
    +.@{fa-css-prefix}-digg:before { content: @fa-var-digg; }
    +.@{fa-css-prefix}-pied-piper-square:before,
    +.@{fa-css-prefix}-pied-piper:before { content: @fa-var-pied-piper; }
    +.@{fa-css-prefix}-pied-piper-alt:before { content: @fa-var-pied-piper-alt; }
    +.@{fa-css-prefix}-drupal:before { content: @fa-var-drupal; }
    +.@{fa-css-prefix}-joomla:before { content: @fa-var-joomla; }
    +.@{fa-css-prefix}-language:before { content: @fa-var-language; }
    +.@{fa-css-prefix}-fax:before { content: @fa-var-fax; }
    +.@{fa-css-prefix}-building:before { content: @fa-var-building; }
    +.@{fa-css-prefix}-child:before { content: @fa-var-child; }
    +.@{fa-css-prefix}-paw:before { content: @fa-var-paw; }
    +.@{fa-css-prefix}-spoon:before { content: @fa-var-spoon; }
    +.@{fa-css-prefix}-cube:before { content: @fa-var-cube; }
    +.@{fa-css-prefix}-cubes:before { content: @fa-var-cubes; }
    +.@{fa-css-prefix}-behance:before { content: @fa-var-behance; }
    +.@{fa-css-prefix}-behance-square:before { content: @fa-var-behance-square; }
    +.@{fa-css-prefix}-steam:before { content: @fa-var-steam; }
    +.@{fa-css-prefix}-steam-square:before { content: @fa-var-steam-square; }
    +.@{fa-css-prefix}-recycle:before { content: @fa-var-recycle; }
    +.@{fa-css-prefix}-automobile:before,
    +.@{fa-css-prefix}-car:before { content: @fa-var-car; }
    +.@{fa-css-prefix}-cab:before,
    +.@{fa-css-prefix}-taxi:before { content: @fa-var-taxi; }
    +.@{fa-css-prefix}-tree:before { content: @fa-var-tree; }
    +.@{fa-css-prefix}-spotify:before { content: @fa-var-spotify; }
    +.@{fa-css-prefix}-deviantart:before { content: @fa-var-deviantart; }
    +.@{fa-css-prefix}-soundcloud:before { content: @fa-var-soundcloud; }
    +.@{fa-css-prefix}-database:before { content: @fa-var-database; }
    +.@{fa-css-prefix}-file-pdf-o:before { content: @fa-var-file-pdf-o; }
    +.@{fa-css-prefix}-file-word-o:before { content: @fa-var-file-word-o; }
    +.@{fa-css-prefix}-file-excel-o:before { content: @fa-var-file-excel-o; }
    +.@{fa-css-prefix}-file-powerpoint-o:before { content: @fa-var-file-powerpoint-o; }
    +.@{fa-css-prefix}-file-photo-o:before,
    +.@{fa-css-prefix}-file-picture-o:before,
    +.@{fa-css-prefix}-file-image-o:before { content: @fa-var-file-image-o; }
    +.@{fa-css-prefix}-file-zip-o:before,
    +.@{fa-css-prefix}-file-archive-o:before { content: @fa-var-file-archive-o; }
    +.@{fa-css-prefix}-file-sound-o:before,
    +.@{fa-css-prefix}-file-audio-o:before { content: @fa-var-file-audio-o; }
    +.@{fa-css-prefix}-file-movie-o:before,
    +.@{fa-css-prefix}-file-video-o:before { content: @fa-var-file-video-o; }
    +.@{fa-css-prefix}-file-code-o:before { content: @fa-var-file-code-o; }
    +.@{fa-css-prefix}-vine:before { content: @fa-var-vine; }
    +.@{fa-css-prefix}-codepen:before { content: @fa-var-codepen; }
    +.@{fa-css-prefix}-jsfiddle:before { content: @fa-var-jsfiddle; }
    +.@{fa-css-prefix}-life-bouy:before,
    +.@{fa-css-prefix}-life-saver:before,
    +.@{fa-css-prefix}-support:before,
    +.@{fa-css-prefix}-life-ring:before { content: @fa-var-life-ring; }
    +.@{fa-css-prefix}-circle-o-notch:before { content: @fa-var-circle-o-notch; }
    +.@{fa-css-prefix}-ra:before,
    +.@{fa-css-prefix}-rebel:before { content: @fa-var-rebel; }
    +.@{fa-css-prefix}-ge:before,
    +.@{fa-css-prefix}-empire:before { content: @fa-var-empire; }
    +.@{fa-css-prefix}-git-square:before { content: @fa-var-git-square; }
    +.@{fa-css-prefix}-git:before { content: @fa-var-git; }
    +.@{fa-css-prefix}-hacker-news:before { content: @fa-var-hacker-news; }
    +.@{fa-css-prefix}-tencent-weibo:before { content: @fa-var-tencent-weibo; }
    +.@{fa-css-prefix}-qq:before { content: @fa-var-qq; }
    +.@{fa-css-prefix}-wechat:before,
    +.@{fa-css-prefix}-weixin:before { content: @fa-var-weixin; }
    +.@{fa-css-prefix}-send:before,
    +.@{fa-css-prefix}-paper-plane:before { content: @fa-var-paper-plane; }
    +.@{fa-css-prefix}-send-o:before,
    +.@{fa-css-prefix}-paper-plane-o:before { content: @fa-var-paper-plane-o; }
    +.@{fa-css-prefix}-history:before { content: @fa-var-history; }
    +.@{fa-css-prefix}-circle-thin:before { content: @fa-var-circle-thin; }
    +.@{fa-css-prefix}-header:before { content: @fa-var-header; }
    +.@{fa-css-prefix}-paragraph:before { content: @fa-var-paragraph; }
    +.@{fa-css-prefix}-sliders:before { content: @fa-var-sliders; }
    +.@{fa-css-prefix}-share-alt:before { content: @fa-var-share-alt; }
    +.@{fa-css-prefix}-share-alt-square:before { content: @fa-var-share-alt-square; }
    +.@{fa-css-prefix}-bomb:before { content: @fa-var-bomb; }
    diff --git a/assets/less/font-awesome/larger.less b/assets/less/font-awesome/larger.less
    index c9d64677..3646d3d9 100644
    --- a/assets/less/font-awesome/larger.less
    +++ b/assets/less/font-awesome/larger.less
    @@ -1,13 +1,13 @@
    -// Icon Sizes
    -// -------------------------
    -
    -/* makes the font 33% larger relative to the icon container */
    -.@{fa-css-prefix}-lg {
    -  font-size: (4em / 3);
    -  line-height: (3em / 4);
    -  vertical-align: -15%;
    -}
    -.@{fa-css-prefix}-2x { font-size: 2em; }
    -.@{fa-css-prefix}-3x { font-size: 3em; }
    -.@{fa-css-prefix}-4x { font-size: 4em; }
    -.@{fa-css-prefix}-5x { font-size: 5em; }
    +// Icon Sizes
    +// -------------------------
    +
    +/* makes the font 33% larger relative to the icon container */
    +.@{fa-css-prefix}-lg {
    +  font-size: (4em / 3);
    +  line-height: (3em / 4);
    +  vertical-align: -15%;
    +}
    +.@{fa-css-prefix}-2x { font-size: 2em; }
    +.@{fa-css-prefix}-3x { font-size: 3em; }
    +.@{fa-css-prefix}-4x { font-size: 4em; }
    +.@{fa-css-prefix}-5x { font-size: 5em; }
    diff --git a/assets/less/font-awesome/list.less b/assets/less/font-awesome/list.less
    index eed93405..6a0e028d 100644
    --- a/assets/less/font-awesome/list.less
    +++ b/assets/less/font-awesome/list.less
    @@ -1,19 +1,19 @@
    -// List Icons
    -// -------------------------
    -
    -.@{fa-css-prefix}-ul {
    -  padding-left: 0;
    -  margin-left: @fa-li-width;
    -  list-style-type: none;
    -  > li { position: relative; }
    -}
    -.@{fa-css-prefix}-li {
    -  position: absolute;
    -  left: -@fa-li-width;
    -  width: @fa-li-width;
    -  top: (2em / 14);
    -  text-align: center;
    -  &.@{fa-css-prefix}-lg {
    -    left: -@fa-li-width + (4em / 14);
    -  }
    -}
    +// List Icons
    +// -------------------------
    +
    +.@{fa-css-prefix}-ul {
    +  padding-left: 0;
    +  margin-left: @fa-li-width;
    +  list-style-type: none;
    +  > li { position: relative; }
    +}
    +.@{fa-css-prefix}-li {
    +  position: absolute;
    +  left: -@fa-li-width;
    +  width: @fa-li-width;
    +  top: (2em / 14);
    +  text-align: center;
    +  &.@{fa-css-prefix}-lg {
    +    left: -@fa-li-width + (4em / 14);
    +  }
    +}
    diff --git a/assets/less/font-awesome/mixins.less b/assets/less/font-awesome/mixins.less
    index 19e5a645..b73ea074 100644
    --- a/assets/less/font-awesome/mixins.less
    +++ b/assets/less/font-awesome/mixins.less
    @@ -1,20 +1,20 @@
    -// Mixins
    -// --------------------------
    -
    -.fa-icon-rotate(@degrees, @rotation) {
    -  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
    -  -webkit-transform: rotate(@degrees);
    -     -moz-transform: rotate(@degrees);
    -      -ms-transform: rotate(@degrees);
    -       -o-transform: rotate(@degrees);
    -          transform: rotate(@degrees);
    -}
    -
    -.fa-icon-flip(@horiz, @vert, @rotation) {
    -  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1);
    -  -webkit-transform: scale(@horiz, @vert);
    -     -moz-transform: scale(@horiz, @vert);
    -      -ms-transform: scale(@horiz, @vert);
    -       -o-transform: scale(@horiz, @vert);
    -          transform: scale(@horiz, @vert);
    -}
    +// Mixins
    +// --------------------------
    +
    +.fa-icon-rotate(@degrees, @rotation) {
    +  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
    +  -webkit-transform: rotate(@degrees);
    +     -moz-transform: rotate(@degrees);
    +      -ms-transform: rotate(@degrees);
    +       -o-transform: rotate(@degrees);
    +          transform: rotate(@degrees);
    +}
    +
    +.fa-icon-flip(@horiz, @vert, @rotation) {
    +  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1);
    +  -webkit-transform: scale(@horiz, @vert);
    +     -moz-transform: scale(@horiz, @vert);
    +      -ms-transform: scale(@horiz, @vert);
    +       -o-transform: scale(@horiz, @vert);
    +          transform: scale(@horiz, @vert);
    +}
    diff --git a/assets/less/font-awesome/path.less b/assets/less/font-awesome/path.less
    index d73bff8b..ca9bae4b 100644
    --- a/assets/less/font-awesome/path.less
    +++ b/assets/less/font-awesome/path.less
    @@ -1,14 +1,14 @@
    -/* FONT PATH
    - * -------------------------- */
    -
    -@font-face {
    -  font-family: 'FontAwesome';
    -  src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}')";
    -  src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype')",
    -    ~"url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff')",
    -    ~"url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype')",
    -    ~"url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg')";
    -//  src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
    -  font-weight: normal;
    -  font-style: normal;
    -}
    +/* FONT PATH
    + * -------------------------- */
    +
    +@font-face {
    +  font-family: 'FontAwesome';
    +  src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}')";
    +  src: ~"url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype')",
    +    ~"url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff')",
    +    ~"url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype')",
    +    ~"url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg')";
    +//  src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
    +  font-weight: normal;
    +  font-style: normal;
    +}
    diff --git a/assets/less/font-awesome/rotated-flipped.less b/assets/less/font-awesome/rotated-flipped.less
    index 8fff3a6c..2483f256 100644
    --- a/assets/less/font-awesome/rotated-flipped.less
    +++ b/assets/less/font-awesome/rotated-flipped.less
    @@ -1,9 +1,9 @@
    -// Rotated & Flipped Icons
    -// -------------------------
    -
    -.@{fa-css-prefix}-rotate-90  { .fa-icon-rotate(90deg, 1);  }
    -.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
    -.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
    -
    -.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
    -.@{fa-css-prefix}-flip-vertical   { .fa-icon-flip(1, -1, 2); }
    +// Rotated & Flipped Icons
    +// -------------------------
    +
    +.@{fa-css-prefix}-rotate-90  { .fa-icon-rotate(90deg, 1);  }
    +.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
    +.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
    +
    +.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
    +.@{fa-css-prefix}-flip-vertical   { .fa-icon-flip(1, -1, 2); }
    diff --git a/assets/less/font-awesome/spinning.less b/assets/less/font-awesome/spinning.less
    index 06b71ecb..6c3903fe 100644
    --- a/assets/less/font-awesome/spinning.less
    +++ b/assets/less/font-awesome/spinning.less
    @@ -1,32 +1,32 @@
    -// Spinning Icons
    -// --------------------------
    -
    -.@{fa-css-prefix}-spin {
    -  -webkit-animation: spin 2s infinite linear;
    -  -moz-animation: spin 2s infinite linear;
    -  -o-animation: spin 2s infinite linear;
    -  animation: spin 2s infinite linear;
    -}
    -
    -@-moz-keyframes spin {
    -  0% { -moz-transform: rotate(0deg); }
    -  100% { -moz-transform: rotate(359deg); }
    -}
    -@-webkit-keyframes spin {
    -  0% { -webkit-transform: rotate(0deg); }
    -  100% { -webkit-transform: rotate(359deg); }
    -}
    -@-o-keyframes spin {
    -  0% { -o-transform: rotate(0deg); }
    -  100% { -o-transform: rotate(359deg); }
    -}
    -@keyframes spin {
    -  0% {
    -    -webkit-transform: rotate(0deg);
    -    transform: rotate(0deg);
    -  }
    -  100% {
    -    -webkit-transform: rotate(359deg);
    -    transform: rotate(359deg);
    -  }
    -}
    +// Spinning Icons
    +// --------------------------
    +
    +.@{fa-css-prefix}-spin {
    +  -webkit-animation: spin 2s infinite linear;
    +  -moz-animation: spin 2s infinite linear;
    +  -o-animation: spin 2s infinite linear;
    +  animation: spin 2s infinite linear;
    +}
    +
    +@-moz-keyframes spin {
    +  0% { -moz-transform: rotate(0deg); }
    +  100% { -moz-transform: rotate(359deg); }
    +}
    +@-webkit-keyframes spin {
    +  0% { -webkit-transform: rotate(0deg); }
    +  100% { -webkit-transform: rotate(359deg); }
    +}
    +@-o-keyframes spin {
    +  0% { -o-transform: rotate(0deg); }
    +  100% { -o-transform: rotate(359deg); }
    +}
    +@keyframes spin {
    +  0% {
    +    -webkit-transform: rotate(0deg);
    +    transform: rotate(0deg);
    +  }
    +  100% {
    +    -webkit-transform: rotate(359deg);
    +    transform: rotate(359deg);
    +  }
    +}
    diff --git a/assets/less/font-awesome/stacked.less b/assets/less/font-awesome/stacked.less
    index fc53fb0e..d3fc1016 100644
    --- a/assets/less/font-awesome/stacked.less
    +++ b/assets/less/font-awesome/stacked.less
    @@ -1,20 +1,20 @@
    -// Stacked Icons
    -// -------------------------
    -
    -.@{fa-css-prefix}-stack {
    -  position: relative;
    -  display: inline-block;
    -  width: 2em;
    -  height: 2em;
    -  line-height: 2em;
    -  vertical-align: middle;
    -}
    -.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
    -  position: absolute;
    -  left: 0;
    -  width: 100%;
    -  text-align: center;
    -}
    -.@{fa-css-prefix}-stack-1x { line-height: inherit; }
    -.@{fa-css-prefix}-stack-2x { font-size: 2em; }
    -.@{fa-css-prefix}-inverse { color: @fa-inverse; }
    +// Stacked Icons
    +// -------------------------
    +
    +.@{fa-css-prefix}-stack {
    +  position: relative;
    +  display: inline-block;
    +  width: 2em;
    +  height: 2em;
    +  line-height: 2em;
    +  vertical-align: middle;
    +}
    +.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
    +  position: absolute;
    +  left: 0;
    +  width: 100%;
    +  text-align: center;
    +}
    +.@{fa-css-prefix}-stack-1x { line-height: inherit; }
    +.@{fa-css-prefix}-stack-2x { font-size: 2em; }
    +.@{fa-css-prefix}-inverse { color: @fa-inverse; }
    diff --git a/assets/less/font-awesome/variables.less b/assets/less/font-awesome/variables.less
    index d7e8bd72..3a215521 100644
    --- a/assets/less/font-awesome/variables.less
    +++ b/assets/less/font-awesome/variables.less
    @@ -1,515 +1,515 @@
    -// Variables
    -// --------------------------
    -
    -@fa-font-path:        "../fonts";
    -//@fa-font-path:        "//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts"; // for referencing Bootstrap CDN font files directly
    -@fa-css-prefix:       fa;
    -@fa-version:          "4.1.0";
    -@fa-border-color:     #eee;
    -@fa-inverse:          #fff;
    -@fa-li-width:         (30em / 14);
    -
    -@fa-var-adjust: "\f042";
    -@fa-var-adn: "\f170";
    -@fa-var-align-center: "\f037";
    -@fa-var-align-justify: "\f039";
    -@fa-var-align-left: "\f036";
    -@fa-var-align-right: "\f038";
    -@fa-var-ambulance: "\f0f9";
    -@fa-var-anchor: "\f13d";
    -@fa-var-android: "\f17b";
    -@fa-var-angle-double-down: "\f103";
    -@fa-var-angle-double-left: "\f100";
    -@fa-var-angle-double-right: "\f101";
    -@fa-var-angle-double-up: "\f102";
    -@fa-var-angle-down: "\f107";
    -@fa-var-angle-left: "\f104";
    -@fa-var-angle-right: "\f105";
    -@fa-var-angle-up: "\f106";
    -@fa-var-apple: "\f179";
    -@fa-var-archive: "\f187";
    -@fa-var-arrow-circle-down: "\f0ab";
    -@fa-var-arrow-circle-left: "\f0a8";
    -@fa-var-arrow-circle-o-down: "\f01a";
    -@fa-var-arrow-circle-o-left: "\f190";
    -@fa-var-arrow-circle-o-right: "\f18e";
    -@fa-var-arrow-circle-o-up: "\f01b";
    -@fa-var-arrow-circle-right: "\f0a9";
    -@fa-var-arrow-circle-up: "\f0aa";
    -@fa-var-arrow-down: "\f063";
    -@fa-var-arrow-left: "\f060";
    -@fa-var-arrow-right: "\f061";
    -@fa-var-arrow-up: "\f062";
    -@fa-var-arrows: "\f047";
    -@fa-var-arrows-alt: "\f0b2";
    -@fa-var-arrows-h: "\f07e";
    -@fa-var-arrows-v: "\f07d";
    -@fa-var-asterisk: "\f069";
    -@fa-var-automobile: "\f1b9";
    -@fa-var-backward: "\f04a";
    -@fa-var-ban: "\f05e";
    -@fa-var-bank: "\f19c";
    -@fa-var-bar-chart-o: "\f080";
    -@fa-var-barcode: "\f02a";
    -@fa-var-bars: "\f0c9";
    -@fa-var-beer: "\f0fc";
    -@fa-var-behance: "\f1b4";
    -@fa-var-behance-square: "\f1b5";
    -@fa-var-bell: "\f0f3";
    -@fa-var-bell-o: "\f0a2";
    -@fa-var-bitbucket: "\f171";
    -@fa-var-bitbucket-square: "\f172";
    -@fa-var-bitcoin: "\f15a";
    -@fa-var-bold: "\f032";
    -@fa-var-bolt: "\f0e7";
    -@fa-var-bomb: "\f1e2";
    -@fa-var-book: "\f02d";
    -@fa-var-bookmark: "\f02e";
    -@fa-var-bookmark-o: "\f097";
    -@fa-var-briefcase: "\f0b1";
    -@fa-var-btc: "\f15a";
    -@fa-var-bug: "\f188";
    -@fa-var-building: "\f1ad";
    -@fa-var-building-o: "\f0f7";
    -@fa-var-bullhorn: "\f0a1";
    -@fa-var-bullseye: "\f140";
    -@fa-var-cab: "\f1ba";
    -@fa-var-calendar: "\f073";
    -@fa-var-calendar-o: "\f133";
    -@fa-var-camera: "\f030";
    -@fa-var-camera-retro: "\f083";
    -@fa-var-car: "\f1b9";
    -@fa-var-caret-down: "\f0d7";
    -@fa-var-caret-left: "\f0d9";
    -@fa-var-caret-right: "\f0da";
    -@fa-var-caret-square-o-down: "\f150";
    -@fa-var-caret-square-o-left: "\f191";
    -@fa-var-caret-square-o-right: "\f152";
    -@fa-var-caret-square-o-up: "\f151";
    -@fa-var-caret-up: "\f0d8";
    -@fa-var-certificate: "\f0a3";
    -@fa-var-chain: "\f0c1";
    -@fa-var-chain-broken: "\f127";
    -@fa-var-check: "\f00c";
    -@fa-var-check-circle: "\f058";
    -@fa-var-check-circle-o: "\f05d";
    -@fa-var-check-square: "\f14a";
    -@fa-var-check-square-o: "\f046";
    -@fa-var-chevron-circle-down: "\f13a";
    -@fa-var-chevron-circle-left: "\f137";
    -@fa-var-chevron-circle-right: "\f138";
    -@fa-var-chevron-circle-up: "\f139";
    -@fa-var-chevron-down: "\f078";
    -@fa-var-chevron-left: "\f053";
    -@fa-var-chevron-right: "\f054";
    -@fa-var-chevron-up: "\f077";
    -@fa-var-child: "\f1ae";
    -@fa-var-circle: "\f111";
    -@fa-var-circle-o: "\f10c";
    -@fa-var-circle-o-notch: "\f1ce";
    -@fa-var-circle-thin: "\f1db";
    -@fa-var-clipboard: "\f0ea";
    -@fa-var-clock-o: "\f017";
    -@fa-var-cloud: "\f0c2";
    -@fa-var-cloud-download: "\f0ed";
    -@fa-var-cloud-upload: "\f0ee";
    -@fa-var-cny: "\f157";
    -@fa-var-code: "\f121";
    -@fa-var-code-fork: "\f126";
    -@fa-var-codepen: "\f1cb";
    -@fa-var-coffee: "\f0f4";
    -@fa-var-cog: "\f013";
    -@fa-var-cogs: "\f085";
    -@fa-var-columns: "\f0db";
    -@fa-var-comment: "\f075";
    -@fa-var-comment-o: "\f0e5";
    -@fa-var-comments: "\f086";
    -@fa-var-comments-o: "\f0e6";
    -@fa-var-compass: "\f14e";
    -@fa-var-compress: "\f066";
    -@fa-var-copy: "\f0c5";
    -@fa-var-credit-card: "\f09d";
    -@fa-var-crop: "\f125";
    -@fa-var-crosshairs: "\f05b";
    -@fa-var-css3: "\f13c";
    -@fa-var-cube: "\f1b2";
    -@fa-var-cubes: "\f1b3";
    -@fa-var-cut: "\f0c4";
    -@fa-var-cutlery: "\f0f5";
    -@fa-var-dashboard: "\f0e4";
    -@fa-var-database: "\f1c0";
    -@fa-var-dedent: "\f03b";
    -@fa-var-delicious: "\f1a5";
    -@fa-var-desktop: "\f108";
    -@fa-var-deviantart: "\f1bd";
    -@fa-var-digg: "\f1a6";
    -@fa-var-dollar: "\f155";
    -@fa-var-dot-circle-o: "\f192";
    -@fa-var-download: "\f019";
    -@fa-var-dribbble: "\f17d";
    -@fa-var-dropbox: "\f16b";
    -@fa-var-drupal: "\f1a9";
    -@fa-var-edit: "\f044";
    -@fa-var-eject: "\f052";
    -@fa-var-ellipsis-h: "\f141";
    -@fa-var-ellipsis-v: "\f142";
    -@fa-var-empire: "\f1d1";
    -@fa-var-envelope: "\f0e0";
    -@fa-var-envelope-o: "\f003";
    -@fa-var-envelope-square: "\f199";
    -@fa-var-eraser: "\f12d";
    -@fa-var-eur: "\f153";
    -@fa-var-euro: "\f153";
    -@fa-var-exchange: "\f0ec";
    -@fa-var-exclamation: "\f12a";
    -@fa-var-exclamation-circle: "\f06a";
    -@fa-var-exclamation-triangle: "\f071";
    -@fa-var-expand: "\f065";
    -@fa-var-external-link: "\f08e";
    -@fa-var-external-link-square: "\f14c";
    -@fa-var-eye: "\f06e";
    -@fa-var-eye-slash: "\f070";
    -@fa-var-facebook: "\f09a";
    -@fa-var-facebook-square: "\f082";
    -@fa-var-fast-backward: "\f049";
    -@fa-var-fast-forward: "\f050";
    -@fa-var-fax: "\f1ac";
    -@fa-var-female: "\f182";
    -@fa-var-fighter-jet: "\f0fb";
    -@fa-var-file: "\f15b";
    -@fa-var-file-archive-o: "\f1c6";
    -@fa-var-file-audio-o: "\f1c7";
    -@fa-var-file-code-o: "\f1c9";
    -@fa-var-file-excel-o: "\f1c3";
    -@fa-var-file-image-o: "\f1c5";
    -@fa-var-file-movie-o: "\f1c8";
    -@fa-var-file-o: "\f016";
    -@fa-var-file-pdf-o: "\f1c1";
    -@fa-var-file-photo-o: "\f1c5";
    -@fa-var-file-picture-o: "\f1c5";
    -@fa-var-file-powerpoint-o: "\f1c4";
    -@fa-var-file-sound-o: "\f1c7";
    -@fa-var-file-text: "\f15c";
    -@fa-var-file-text-o: "\f0f6";
    -@fa-var-file-video-o: "\f1c8";
    -@fa-var-file-word-o: "\f1c2";
    -@fa-var-file-zip-o: "\f1c6";
    -@fa-var-files-o: "\f0c5";
    -@fa-var-film: "\f008";
    -@fa-var-filter: "\f0b0";
    -@fa-var-fire: "\f06d";
    -@fa-var-fire-extinguisher: "\f134";
    -@fa-var-flag: "\f024";
    -@fa-var-flag-checkered: "\f11e";
    -@fa-var-flag-o: "\f11d";
    -@fa-var-flash: "\f0e7";
    -@fa-var-flask: "\f0c3";
    -@fa-var-flickr: "\f16e";
    -@fa-var-floppy-o: "\f0c7";
    -@fa-var-folder: "\f07b";
    -@fa-var-folder-o: "\f114";
    -@fa-var-folder-open: "\f07c";
    -@fa-var-folder-open-o: "\f115";
    -@fa-var-font: "\f031";
    -@fa-var-forward: "\f04e";
    -@fa-var-foursquare: "\f180";
    -@fa-var-frown-o: "\f119";
    -@fa-var-gamepad: "\f11b";
    -@fa-var-gavel: "\f0e3";
    -@fa-var-gbp: "\f154";
    -@fa-var-ge: "\f1d1";
    -@fa-var-gear: "\f013";
    -@fa-var-gears: "\f085";
    -@fa-var-gift: "\f06b";
    -@fa-var-git: "\f1d3";
    -@fa-var-git-square: "\f1d2";
    -@fa-var-github: "\f09b";
    -@fa-var-github-alt: "\f113";
    -@fa-var-github-square: "\f092";
    -@fa-var-gittip: "\f184";
    -@fa-var-glass: "\f000";
    -@fa-var-globe: "\f0ac";
    -@fa-var-google: "\f1a0";
    -@fa-var-google-plus: "\f0d5";
    -@fa-var-google-plus-square: "\f0d4";
    -@fa-var-graduation-cap: "\f19d";
    -@fa-var-group: "\f0c0";
    -@fa-var-h-square: "\f0fd";
    -@fa-var-hacker-news: "\f1d4";
    -@fa-var-hand-o-down: "\f0a7";
    -@fa-var-hand-o-left: "\f0a5";
    -@fa-var-hand-o-right: "\f0a4";
    -@fa-var-hand-o-up: "\f0a6";
    -@fa-var-hdd-o: "\f0a0";
    -@fa-var-header: "\f1dc";
    -@fa-var-headphones: "\f025";
    -@fa-var-heart: "\f004";
    -@fa-var-heart-o: "\f08a";
    -@fa-var-history: "\f1da";
    -@fa-var-home: "\f015";
    -@fa-var-hospital-o: "\f0f8";
    -@fa-var-html5: "\f13b";
    -@fa-var-image: "\f03e";
    -@fa-var-inbox: "\f01c";
    -@fa-var-indent: "\f03c";
    -@fa-var-info: "\f129";
    -@fa-var-info-circle: "\f05a";
    -@fa-var-inr: "\f156";
    -@fa-var-instagram: "\f16d";
    -@fa-var-institution: "\f19c";
    -@fa-var-italic: "\f033";
    -@fa-var-joomla: "\f1aa";
    -@fa-var-jpy: "\f157";
    -@fa-var-jsfiddle: "\f1cc";
    -@fa-var-key: "\f084";
    -@fa-var-keyboard-o: "\f11c";
    -@fa-var-krw: "\f159";
    -@fa-var-language: "\f1ab";
    -@fa-var-laptop: "\f109";
    -@fa-var-leaf: "\f06c";
    -@fa-var-legal: "\f0e3";
    -@fa-var-lemon-o: "\f094";
    -@fa-var-level-down: "\f149";
    -@fa-var-level-up: "\f148";
    -@fa-var-life-bouy: "\f1cd";
    -@fa-var-life-ring: "\f1cd";
    -@fa-var-life-saver: "\f1cd";
    -@fa-var-lightbulb-o: "\f0eb";
    -@fa-var-link: "\f0c1";
    -@fa-var-linkedin: "\f0e1";
    -@fa-var-linkedin-square: "\f08c";
    -@fa-var-linux: "\f17c";
    -@fa-var-list: "\f03a";
    -@fa-var-list-alt: "\f022";
    -@fa-var-list-ol: "\f0cb";
    -@fa-var-list-ul: "\f0ca";
    -@fa-var-location-arrow: "\f124";
    -@fa-var-lock: "\f023";
    -@fa-var-long-arrow-down: "\f175";
    -@fa-var-long-arrow-left: "\f177";
    -@fa-var-long-arrow-right: "\f178";
    -@fa-var-long-arrow-up: "\f176";
    -@fa-var-magic: "\f0d0";
    -@fa-var-magnet: "\f076";
    -@fa-var-mail-forward: "\f064";
    -@fa-var-mail-reply: "\f112";
    -@fa-var-mail-reply-all: "\f122";
    -@fa-var-male: "\f183";
    -@fa-var-map-marker: "\f041";
    -@fa-var-maxcdn: "\f136";
    -@fa-var-medkit: "\f0fa";
    -@fa-var-meh-o: "\f11a";
    -@fa-var-microphone: "\f130";
    -@fa-var-microphone-slash: "\f131";
    -@fa-var-minus: "\f068";
    -@fa-var-minus-circle: "\f056";
    -@fa-var-minus-square: "\f146";
    -@fa-var-minus-square-o: "\f147";
    -@fa-var-mobile: "\f10b";
    -@fa-var-mobile-phone: "\f10b";
    -@fa-var-money: "\f0d6";
    -@fa-var-moon-o: "\f186";
    -@fa-var-mortar-board: "\f19d";
    -@fa-var-music: "\f001";
    -@fa-var-navicon: "\f0c9";
    -@fa-var-openid: "\f19b";
    -@fa-var-outdent: "\f03b";
    -@fa-var-pagelines: "\f18c";
    -@fa-var-paper-plane: "\f1d8";
    -@fa-var-paper-plane-o: "\f1d9";
    -@fa-var-paperclip: "\f0c6";
    -@fa-var-paragraph: "\f1dd";
    -@fa-var-paste: "\f0ea";
    -@fa-var-pause: "\f04c";
    -@fa-var-paw: "\f1b0";
    -@fa-var-pencil: "\f040";
    -@fa-var-pencil-square: "\f14b";
    -@fa-var-pencil-square-o: "\f044";
    -@fa-var-phone: "\f095";
    -@fa-var-phone-square: "\f098";
    -@fa-var-photo: "\f03e";
    -@fa-var-picture-o: "\f03e";
    -@fa-var-pied-piper: "\f1a7";
    -@fa-var-pied-piper-alt: "\f1a8";
    -@fa-var-pied-piper-square: "\f1a7";
    -@fa-var-pinterest: "\f0d2";
    -@fa-var-pinterest-square: "\f0d3";
    -@fa-var-plane: "\f072";
    -@fa-var-play: "\f04b";
    -@fa-var-play-circle: "\f144";
    -@fa-var-play-circle-o: "\f01d";
    -@fa-var-plus: "\f067";
    -@fa-var-plus-circle: "\f055";
    -@fa-var-plus-square: "\f0fe";
    -@fa-var-plus-square-o: "\f196";
    -@fa-var-power-off: "\f011";
    -@fa-var-print: "\f02f";
    -@fa-var-puzzle-piece: "\f12e";
    -@fa-var-qq: "\f1d6";
    -@fa-var-qrcode: "\f029";
    -@fa-var-question: "\f128";
    -@fa-var-question-circle: "\f059";
    -@fa-var-quote-left: "\f10d";
    -@fa-var-quote-right: "\f10e";
    -@fa-var-ra: "\f1d0";
    -@fa-var-random: "\f074";
    -@fa-var-rebel: "\f1d0";
    -@fa-var-recycle: "\f1b8";
    -@fa-var-reddit: "\f1a1";
    -@fa-var-reddit-square: "\f1a2";
    -@fa-var-refresh: "\f021";
    -@fa-var-renren: "\f18b";
    -@fa-var-reorder: "\f0c9";
    -@fa-var-repeat: "\f01e";
    -@fa-var-reply: "\f112";
    -@fa-var-reply-all: "\f122";
    -@fa-var-retweet: "\f079";
    -@fa-var-rmb: "\f157";
    -@fa-var-road: "\f018";
    -@fa-var-rocket: "\f135";
    -@fa-var-rotate-left: "\f0e2";
    -@fa-var-rotate-right: "\f01e";
    -@fa-var-rouble: "\f158";
    -@fa-var-rss: "\f09e";
    -@fa-var-rss-square: "\f143";
    -@fa-var-rub: "\f158";
    -@fa-var-ruble: "\f158";
    -@fa-var-rupee: "\f156";
    -@fa-var-save: "\f0c7";
    -@fa-var-scissors: "\f0c4";
    -@fa-var-search: "\f002";
    -@fa-var-search-minus: "\f010";
    -@fa-var-search-plus: "\f00e";
    -@fa-var-send: "\f1d8";
    -@fa-var-send-o: "\f1d9";
    -@fa-var-share: "\f064";
    -@fa-var-share-alt: "\f1e0";
    -@fa-var-share-alt-square: "\f1e1";
    -@fa-var-share-square: "\f14d";
    -@fa-var-share-square-o: "\f045";
    -@fa-var-shield: "\f132";
    -@fa-var-shopping-cart: "\f07a";
    -@fa-var-sign-in: "\f090";
    -@fa-var-sign-out: "\f08b";
    -@fa-var-signal: "\f012";
    -@fa-var-sitemap: "\f0e8";
    -@fa-var-skype: "\f17e";
    -@fa-var-slack: "\f198";
    -@fa-var-sliders: "\f1de";
    -@fa-var-smile-o: "\f118";
    -@fa-var-sort: "\f0dc";
    -@fa-var-sort-alpha-asc: "\f15d";
    -@fa-var-sort-alpha-desc: "\f15e";
    -@fa-var-sort-amount-asc: "\f160";
    -@fa-var-sort-amount-desc: "\f161";
    -@fa-var-sort-asc: "\f0de";
    -@fa-var-sort-desc: "\f0dd";
    -@fa-var-sort-down: "\f0dd";
    -@fa-var-sort-numeric-asc: "\f162";
    -@fa-var-sort-numeric-desc: "\f163";
    -@fa-var-sort-up: "\f0de";
    -@fa-var-soundcloud: "\f1be";
    -@fa-var-space-shuttle: "\f197";
    -@fa-var-spinner: "\f110";
    -@fa-var-spoon: "\f1b1";
    -@fa-var-spotify: "\f1bc";
    -@fa-var-square: "\f0c8";
    -@fa-var-square-o: "\f096";
    -@fa-var-stack-exchange: "\f18d";
    -@fa-var-stack-overflow: "\f16c";
    -@fa-var-star: "\f005";
    -@fa-var-star-half: "\f089";
    -@fa-var-star-half-empty: "\f123";
    -@fa-var-star-half-full: "\f123";
    -@fa-var-star-half-o: "\f123";
    -@fa-var-star-o: "\f006";
    -@fa-var-steam: "\f1b6";
    -@fa-var-steam-square: "\f1b7";
    -@fa-var-step-backward: "\f048";
    -@fa-var-step-forward: "\f051";
    -@fa-var-stethoscope: "\f0f1";
    -@fa-var-stop: "\f04d";
    -@fa-var-strikethrough: "\f0cc";
    -@fa-var-stumbleupon: "\f1a4";
    -@fa-var-stumbleupon-circle: "\f1a3";
    -@fa-var-subscript: "\f12c";
    -@fa-var-suitcase: "\f0f2";
    -@fa-var-sun-o: "\f185";
    -@fa-var-superscript: "\f12b";
    -@fa-var-support: "\f1cd";
    -@fa-var-table: "\f0ce";
    -@fa-var-tablet: "\f10a";
    -@fa-var-tachometer: "\f0e4";
    -@fa-var-tag: "\f02b";
    -@fa-var-tags: "\f02c";
    -@fa-var-tasks: "\f0ae";
    -@fa-var-taxi: "\f1ba";
    -@fa-var-tencent-weibo: "\f1d5";
    -@fa-var-terminal: "\f120";
    -@fa-var-text-height: "\f034";
    -@fa-var-text-width: "\f035";
    -@fa-var-th: "\f00a";
    -@fa-var-th-large: "\f009";
    -@fa-var-th-list: "\f00b";
    -@fa-var-thumb-tack: "\f08d";
    -@fa-var-thumbs-down: "\f165";
    -@fa-var-thumbs-o-down: "\f088";
    -@fa-var-thumbs-o-up: "\f087";
    -@fa-var-thumbs-up: "\f164";
    -@fa-var-ticket: "\f145";
    -@fa-var-times: "\f00d";
    -@fa-var-times-circle: "\f057";
    -@fa-var-times-circle-o: "\f05c";
    -@fa-var-tint: "\f043";
    -@fa-var-toggle-down: "\f150";
    -@fa-var-toggle-left: "\f191";
    -@fa-var-toggle-right: "\f152";
    -@fa-var-toggle-up: "\f151";
    -@fa-var-trash-o: "\f014";
    -@fa-var-tree: "\f1bb";
    -@fa-var-trello: "\f181";
    -@fa-var-trophy: "\f091";
    -@fa-var-truck: "\f0d1";
    -@fa-var-try: "\f195";
    -@fa-var-tumblr: "\f173";
    -@fa-var-tumblr-square: "\f174";
    -@fa-var-turkish-lira: "\f195";
    -@fa-var-twitter: "\f099";
    -@fa-var-twitter-square: "\f081";
    -@fa-var-umbrella: "\f0e9";
    -@fa-var-underline: "\f0cd";
    -@fa-var-undo: "\f0e2";
    -@fa-var-university: "\f19c";
    -@fa-var-unlink: "\f127";
    -@fa-var-unlock: "\f09c";
    -@fa-var-unlock-alt: "\f13e";
    -@fa-var-unsorted: "\f0dc";
    -@fa-var-upload: "\f093";
    -@fa-var-usd: "\f155";
    -@fa-var-user: "\f007";
    -@fa-var-user-md: "\f0f0";
    -@fa-var-users: "\f0c0";
    -@fa-var-video-camera: "\f03d";
    -@fa-var-vimeo-square: "\f194";
    -@fa-var-vine: "\f1ca";
    -@fa-var-vk: "\f189";
    -@fa-var-volume-down: "\f027";
    -@fa-var-volume-off: "\f026";
    -@fa-var-volume-up: "\f028";
    -@fa-var-warning: "\f071";
    -@fa-var-wechat: "\f1d7";
    -@fa-var-weibo: "\f18a";
    -@fa-var-weixin: "\f1d7";
    -@fa-var-wheelchair: "\f193";
    -@fa-var-windows: "\f17a";
    -@fa-var-won: "\f159";
    -@fa-var-wordpress: "\f19a";
    -@fa-var-wrench: "\f0ad";
    -@fa-var-xing: "\f168";
    -@fa-var-xing-square: "\f169";
    -@fa-var-yahoo: "\f19e";
    -@fa-var-yen: "\f157";
    -@fa-var-youtube: "\f167";
    -@fa-var-youtube-play: "\f16a";
    -@fa-var-youtube-square: "\f166";
    -
    +// Variables
    +// --------------------------
    +
    +@fa-font-path:        "../fonts";
    +//@fa-font-path:        "//netdna.bootstrapcdn.com/font-awesome/4.1.0/fonts"; // for referencing Bootstrap CDN font files directly
    +@fa-css-prefix:       fa;
    +@fa-version:          "4.1.0";
    +@fa-border-color:     #eee;
    +@fa-inverse:          #fff;
    +@fa-li-width:         (30em / 14);
    +
    +@fa-var-adjust: "\f042";
    +@fa-var-adn: "\f170";
    +@fa-var-align-center: "\f037";
    +@fa-var-align-justify: "\f039";
    +@fa-var-align-left: "\f036";
    +@fa-var-align-right: "\f038";
    +@fa-var-ambulance: "\f0f9";
    +@fa-var-anchor: "\f13d";
    +@fa-var-android: "\f17b";
    +@fa-var-angle-double-down: "\f103";
    +@fa-var-angle-double-left: "\f100";
    +@fa-var-angle-double-right: "\f101";
    +@fa-var-angle-double-up: "\f102";
    +@fa-var-angle-down: "\f107";
    +@fa-var-angle-left: "\f104";
    +@fa-var-angle-right: "\f105";
    +@fa-var-angle-up: "\f106";
    +@fa-var-apple: "\f179";
    +@fa-var-archive: "\f187";
    +@fa-var-arrow-circle-down: "\f0ab";
    +@fa-var-arrow-circle-left: "\f0a8";
    +@fa-var-arrow-circle-o-down: "\f01a";
    +@fa-var-arrow-circle-o-left: "\f190";
    +@fa-var-arrow-circle-o-right: "\f18e";
    +@fa-var-arrow-circle-o-up: "\f01b";
    +@fa-var-arrow-circle-right: "\f0a9";
    +@fa-var-arrow-circle-up: "\f0aa";
    +@fa-var-arrow-down: "\f063";
    +@fa-var-arrow-left: "\f060";
    +@fa-var-arrow-right: "\f061";
    +@fa-var-arrow-up: "\f062";
    +@fa-var-arrows: "\f047";
    +@fa-var-arrows-alt: "\f0b2";
    +@fa-var-arrows-h: "\f07e";
    +@fa-var-arrows-v: "\f07d";
    +@fa-var-asterisk: "\f069";
    +@fa-var-automobile: "\f1b9";
    +@fa-var-backward: "\f04a";
    +@fa-var-ban: "\f05e";
    +@fa-var-bank: "\f19c";
    +@fa-var-bar-chart-o: "\f080";
    +@fa-var-barcode: "\f02a";
    +@fa-var-bars: "\f0c9";
    +@fa-var-beer: "\f0fc";
    +@fa-var-behance: "\f1b4";
    +@fa-var-behance-square: "\f1b5";
    +@fa-var-bell: "\f0f3";
    +@fa-var-bell-o: "\f0a2";
    +@fa-var-bitbucket: "\f171";
    +@fa-var-bitbucket-square: "\f172";
    +@fa-var-bitcoin: "\f15a";
    +@fa-var-bold: "\f032";
    +@fa-var-bolt: "\f0e7";
    +@fa-var-bomb: "\f1e2";
    +@fa-var-book: "\f02d";
    +@fa-var-bookmark: "\f02e";
    +@fa-var-bookmark-o: "\f097";
    +@fa-var-briefcase: "\f0b1";
    +@fa-var-btc: "\f15a";
    +@fa-var-bug: "\f188";
    +@fa-var-building: "\f1ad";
    +@fa-var-building-o: "\f0f7";
    +@fa-var-bullhorn: "\f0a1";
    +@fa-var-bullseye: "\f140";
    +@fa-var-cab: "\f1ba";
    +@fa-var-calendar: "\f073";
    +@fa-var-calendar-o: "\f133";
    +@fa-var-camera: "\f030";
    +@fa-var-camera-retro: "\f083";
    +@fa-var-car: "\f1b9";
    +@fa-var-caret-down: "\f0d7";
    +@fa-var-caret-left: "\f0d9";
    +@fa-var-caret-right: "\f0da";
    +@fa-var-caret-square-o-down: "\f150";
    +@fa-var-caret-square-o-left: "\f191";
    +@fa-var-caret-square-o-right: "\f152";
    +@fa-var-caret-square-o-up: "\f151";
    +@fa-var-caret-up: "\f0d8";
    +@fa-var-certificate: "\f0a3";
    +@fa-var-chain: "\f0c1";
    +@fa-var-chain-broken: "\f127";
    +@fa-var-check: "\f00c";
    +@fa-var-check-circle: "\f058";
    +@fa-var-check-circle-o: "\f05d";
    +@fa-var-check-square: "\f14a";
    +@fa-var-check-square-o: "\f046";
    +@fa-var-chevron-circle-down: "\f13a";
    +@fa-var-chevron-circle-left: "\f137";
    +@fa-var-chevron-circle-right: "\f138";
    +@fa-var-chevron-circle-up: "\f139";
    +@fa-var-chevron-down: "\f078";
    +@fa-var-chevron-left: "\f053";
    +@fa-var-chevron-right: "\f054";
    +@fa-var-chevron-up: "\f077";
    +@fa-var-child: "\f1ae";
    +@fa-var-circle: "\f111";
    +@fa-var-circle-o: "\f10c";
    +@fa-var-circle-o-notch: "\f1ce";
    +@fa-var-circle-thin: "\f1db";
    +@fa-var-clipboard: "\f0ea";
    +@fa-var-clock-o: "\f017";
    +@fa-var-cloud: "\f0c2";
    +@fa-var-cloud-download: "\f0ed";
    +@fa-var-cloud-upload: "\f0ee";
    +@fa-var-cny: "\f157";
    +@fa-var-code: "\f121";
    +@fa-var-code-fork: "\f126";
    +@fa-var-codepen: "\f1cb";
    +@fa-var-coffee: "\f0f4";
    +@fa-var-cog: "\f013";
    +@fa-var-cogs: "\f085";
    +@fa-var-columns: "\f0db";
    +@fa-var-comment: "\f075";
    +@fa-var-comment-o: "\f0e5";
    +@fa-var-comments: "\f086";
    +@fa-var-comments-o: "\f0e6";
    +@fa-var-compass: "\f14e";
    +@fa-var-compress: "\f066";
    +@fa-var-copy: "\f0c5";
    +@fa-var-credit-card: "\f09d";
    +@fa-var-crop: "\f125";
    +@fa-var-crosshairs: "\f05b";
    +@fa-var-css3: "\f13c";
    +@fa-var-cube: "\f1b2";
    +@fa-var-cubes: "\f1b3";
    +@fa-var-cut: "\f0c4";
    +@fa-var-cutlery: "\f0f5";
    +@fa-var-dashboard: "\f0e4";
    +@fa-var-database: "\f1c0";
    +@fa-var-dedent: "\f03b";
    +@fa-var-delicious: "\f1a5";
    +@fa-var-desktop: "\f108";
    +@fa-var-deviantart: "\f1bd";
    +@fa-var-digg: "\f1a6";
    +@fa-var-dollar: "\f155";
    +@fa-var-dot-circle-o: "\f192";
    +@fa-var-download: "\f019";
    +@fa-var-dribbble: "\f17d";
    +@fa-var-dropbox: "\f16b";
    +@fa-var-drupal: "\f1a9";
    +@fa-var-edit: "\f044";
    +@fa-var-eject: "\f052";
    +@fa-var-ellipsis-h: "\f141";
    +@fa-var-ellipsis-v: "\f142";
    +@fa-var-empire: "\f1d1";
    +@fa-var-envelope: "\f0e0";
    +@fa-var-envelope-o: "\f003";
    +@fa-var-envelope-square: "\f199";
    +@fa-var-eraser: "\f12d";
    +@fa-var-eur: "\f153";
    +@fa-var-euro: "\f153";
    +@fa-var-exchange: "\f0ec";
    +@fa-var-exclamation: "\f12a";
    +@fa-var-exclamation-circle: "\f06a";
    +@fa-var-exclamation-triangle: "\f071";
    +@fa-var-expand: "\f065";
    +@fa-var-external-link: "\f08e";
    +@fa-var-external-link-square: "\f14c";
    +@fa-var-eye: "\f06e";
    +@fa-var-eye-slash: "\f070";
    +@fa-var-facebook: "\f09a";
    +@fa-var-facebook-square: "\f082";
    +@fa-var-fast-backward: "\f049";
    +@fa-var-fast-forward: "\f050";
    +@fa-var-fax: "\f1ac";
    +@fa-var-female: "\f182";
    +@fa-var-fighter-jet: "\f0fb";
    +@fa-var-file: "\f15b";
    +@fa-var-file-archive-o: "\f1c6";
    +@fa-var-file-audio-o: "\f1c7";
    +@fa-var-file-code-o: "\f1c9";
    +@fa-var-file-excel-o: "\f1c3";
    +@fa-var-file-image-o: "\f1c5";
    +@fa-var-file-movie-o: "\f1c8";
    +@fa-var-file-o: "\f016";
    +@fa-var-file-pdf-o: "\f1c1";
    +@fa-var-file-photo-o: "\f1c5";
    +@fa-var-file-picture-o: "\f1c5";
    +@fa-var-file-powerpoint-o: "\f1c4";
    +@fa-var-file-sound-o: "\f1c7";
    +@fa-var-file-text: "\f15c";
    +@fa-var-file-text-o: "\f0f6";
    +@fa-var-file-video-o: "\f1c8";
    +@fa-var-file-word-o: "\f1c2";
    +@fa-var-file-zip-o: "\f1c6";
    +@fa-var-files-o: "\f0c5";
    +@fa-var-film: "\f008";
    +@fa-var-filter: "\f0b0";
    +@fa-var-fire: "\f06d";
    +@fa-var-fire-extinguisher: "\f134";
    +@fa-var-flag: "\f024";
    +@fa-var-flag-checkered: "\f11e";
    +@fa-var-flag-o: "\f11d";
    +@fa-var-flash: "\f0e7";
    +@fa-var-flask: "\f0c3";
    +@fa-var-flickr: "\f16e";
    +@fa-var-floppy-o: "\f0c7";
    +@fa-var-folder: "\f07b";
    +@fa-var-folder-o: "\f114";
    +@fa-var-folder-open: "\f07c";
    +@fa-var-folder-open-o: "\f115";
    +@fa-var-font: "\f031";
    +@fa-var-forward: "\f04e";
    +@fa-var-foursquare: "\f180";
    +@fa-var-frown-o: "\f119";
    +@fa-var-gamepad: "\f11b";
    +@fa-var-gavel: "\f0e3";
    +@fa-var-gbp: "\f154";
    +@fa-var-ge: "\f1d1";
    +@fa-var-gear: "\f013";
    +@fa-var-gears: "\f085";
    +@fa-var-gift: "\f06b";
    +@fa-var-git: "\f1d3";
    +@fa-var-git-square: "\f1d2";
    +@fa-var-github: "\f09b";
    +@fa-var-github-alt: "\f113";
    +@fa-var-github-square: "\f092";
    +@fa-var-gittip: "\f184";
    +@fa-var-glass: "\f000";
    +@fa-var-globe: "\f0ac";
    +@fa-var-google: "\f1a0";
    +@fa-var-google-plus: "\f0d5";
    +@fa-var-google-plus-square: "\f0d4";
    +@fa-var-graduation-cap: "\f19d";
    +@fa-var-group: "\f0c0";
    +@fa-var-h-square: "\f0fd";
    +@fa-var-hacker-news: "\f1d4";
    +@fa-var-hand-o-down: "\f0a7";
    +@fa-var-hand-o-left: "\f0a5";
    +@fa-var-hand-o-right: "\f0a4";
    +@fa-var-hand-o-up: "\f0a6";
    +@fa-var-hdd-o: "\f0a0";
    +@fa-var-header: "\f1dc";
    +@fa-var-headphones: "\f025";
    +@fa-var-heart: "\f004";
    +@fa-var-heart-o: "\f08a";
    +@fa-var-history: "\f1da";
    +@fa-var-home: "\f015";
    +@fa-var-hospital-o: "\f0f8";
    +@fa-var-html5: "\f13b";
    +@fa-var-image: "\f03e";
    +@fa-var-inbox: "\f01c";
    +@fa-var-indent: "\f03c";
    +@fa-var-info: "\f129";
    +@fa-var-info-circle: "\f05a";
    +@fa-var-inr: "\f156";
    +@fa-var-instagram: "\f16d";
    +@fa-var-institution: "\f19c";
    +@fa-var-italic: "\f033";
    +@fa-var-joomla: "\f1aa";
    +@fa-var-jpy: "\f157";
    +@fa-var-jsfiddle: "\f1cc";
    +@fa-var-key: "\f084";
    +@fa-var-keyboard-o: "\f11c";
    +@fa-var-krw: "\f159";
    +@fa-var-language: "\f1ab";
    +@fa-var-laptop: "\f109";
    +@fa-var-leaf: "\f06c";
    +@fa-var-legal: "\f0e3";
    +@fa-var-lemon-o: "\f094";
    +@fa-var-level-down: "\f149";
    +@fa-var-level-up: "\f148";
    +@fa-var-life-bouy: "\f1cd";
    +@fa-var-life-ring: "\f1cd";
    +@fa-var-life-saver: "\f1cd";
    +@fa-var-lightbulb-o: "\f0eb";
    +@fa-var-link: "\f0c1";
    +@fa-var-linkedin: "\f0e1";
    +@fa-var-linkedin-square: "\f08c";
    +@fa-var-linux: "\f17c";
    +@fa-var-list: "\f03a";
    +@fa-var-list-alt: "\f022";
    +@fa-var-list-ol: "\f0cb";
    +@fa-var-list-ul: "\f0ca";
    +@fa-var-location-arrow: "\f124";
    +@fa-var-lock: "\f023";
    +@fa-var-long-arrow-down: "\f175";
    +@fa-var-long-arrow-left: "\f177";
    +@fa-var-long-arrow-right: "\f178";
    +@fa-var-long-arrow-up: "\f176";
    +@fa-var-magic: "\f0d0";
    +@fa-var-magnet: "\f076";
    +@fa-var-mail-forward: "\f064";
    +@fa-var-mail-reply: "\f112";
    +@fa-var-mail-reply-all: "\f122";
    +@fa-var-male: "\f183";
    +@fa-var-map-marker: "\f041";
    +@fa-var-maxcdn: "\f136";
    +@fa-var-medkit: "\f0fa";
    +@fa-var-meh-o: "\f11a";
    +@fa-var-microphone: "\f130";
    +@fa-var-microphone-slash: "\f131";
    +@fa-var-minus: "\f068";
    +@fa-var-minus-circle: "\f056";
    +@fa-var-minus-square: "\f146";
    +@fa-var-minus-square-o: "\f147";
    +@fa-var-mobile: "\f10b";
    +@fa-var-mobile-phone: "\f10b";
    +@fa-var-money: "\f0d6";
    +@fa-var-moon-o: "\f186";
    +@fa-var-mortar-board: "\f19d";
    +@fa-var-music: "\f001";
    +@fa-var-navicon: "\f0c9";
    +@fa-var-openid: "\f19b";
    +@fa-var-outdent: "\f03b";
    +@fa-var-pagelines: "\f18c";
    +@fa-var-paper-plane: "\f1d8";
    +@fa-var-paper-plane-o: "\f1d9";
    +@fa-var-paperclip: "\f0c6";
    +@fa-var-paragraph: "\f1dd";
    +@fa-var-paste: "\f0ea";
    +@fa-var-pause: "\f04c";
    +@fa-var-paw: "\f1b0";
    +@fa-var-pencil: "\f040";
    +@fa-var-pencil-square: "\f14b";
    +@fa-var-pencil-square-o: "\f044";
    +@fa-var-phone: "\f095";
    +@fa-var-phone-square: "\f098";
    +@fa-var-photo: "\f03e";
    +@fa-var-picture-o: "\f03e";
    +@fa-var-pied-piper: "\f1a7";
    +@fa-var-pied-piper-alt: "\f1a8";
    +@fa-var-pied-piper-square: "\f1a7";
    +@fa-var-pinterest: "\f0d2";
    +@fa-var-pinterest-square: "\f0d3";
    +@fa-var-plane: "\f072";
    +@fa-var-play: "\f04b";
    +@fa-var-play-circle: "\f144";
    +@fa-var-play-circle-o: "\f01d";
    +@fa-var-plus: "\f067";
    +@fa-var-plus-circle: "\f055";
    +@fa-var-plus-square: "\f0fe";
    +@fa-var-plus-square-o: "\f196";
    +@fa-var-power-off: "\f011";
    +@fa-var-print: "\f02f";
    +@fa-var-puzzle-piece: "\f12e";
    +@fa-var-qq: "\f1d6";
    +@fa-var-qrcode: "\f029";
    +@fa-var-question: "\f128";
    +@fa-var-question-circle: "\f059";
    +@fa-var-quote-left: "\f10d";
    +@fa-var-quote-right: "\f10e";
    +@fa-var-ra: "\f1d0";
    +@fa-var-random: "\f074";
    +@fa-var-rebel: "\f1d0";
    +@fa-var-recycle: "\f1b8";
    +@fa-var-reddit: "\f1a1";
    +@fa-var-reddit-square: "\f1a2";
    +@fa-var-refresh: "\f021";
    +@fa-var-renren: "\f18b";
    +@fa-var-reorder: "\f0c9";
    +@fa-var-repeat: "\f01e";
    +@fa-var-reply: "\f112";
    +@fa-var-reply-all: "\f122";
    +@fa-var-retweet: "\f079";
    +@fa-var-rmb: "\f157";
    +@fa-var-road: "\f018";
    +@fa-var-rocket: "\f135";
    +@fa-var-rotate-left: "\f0e2";
    +@fa-var-rotate-right: "\f01e";
    +@fa-var-rouble: "\f158";
    +@fa-var-rss: "\f09e";
    +@fa-var-rss-square: "\f143";
    +@fa-var-rub: "\f158";
    +@fa-var-ruble: "\f158";
    +@fa-var-rupee: "\f156";
    +@fa-var-save: "\f0c7";
    +@fa-var-scissors: "\f0c4";
    +@fa-var-search: "\f002";
    +@fa-var-search-minus: "\f010";
    +@fa-var-search-plus: "\f00e";
    +@fa-var-send: "\f1d8";
    +@fa-var-send-o: "\f1d9";
    +@fa-var-share: "\f064";
    +@fa-var-share-alt: "\f1e0";
    +@fa-var-share-alt-square: "\f1e1";
    +@fa-var-share-square: "\f14d";
    +@fa-var-share-square-o: "\f045";
    +@fa-var-shield: "\f132";
    +@fa-var-shopping-cart: "\f07a";
    +@fa-var-sign-in: "\f090";
    +@fa-var-sign-out: "\f08b";
    +@fa-var-signal: "\f012";
    +@fa-var-sitemap: "\f0e8";
    +@fa-var-skype: "\f17e";
    +@fa-var-slack: "\f198";
    +@fa-var-sliders: "\f1de";
    +@fa-var-smile-o: "\f118";
    +@fa-var-sort: "\f0dc";
    +@fa-var-sort-alpha-asc: "\f15d";
    +@fa-var-sort-alpha-desc: "\f15e";
    +@fa-var-sort-amount-asc: "\f160";
    +@fa-var-sort-amount-desc: "\f161";
    +@fa-var-sort-asc: "\f0de";
    +@fa-var-sort-desc: "\f0dd";
    +@fa-var-sort-down: "\f0dd";
    +@fa-var-sort-numeric-asc: "\f162";
    +@fa-var-sort-numeric-desc: "\f163";
    +@fa-var-sort-up: "\f0de";
    +@fa-var-soundcloud: "\f1be";
    +@fa-var-space-shuttle: "\f197";
    +@fa-var-spinner: "\f110";
    +@fa-var-spoon: "\f1b1";
    +@fa-var-spotify: "\f1bc";
    +@fa-var-square: "\f0c8";
    +@fa-var-square-o: "\f096";
    +@fa-var-stack-exchange: "\f18d";
    +@fa-var-stack-overflow: "\f16c";
    +@fa-var-star: "\f005";
    +@fa-var-star-half: "\f089";
    +@fa-var-star-half-empty: "\f123";
    +@fa-var-star-half-full: "\f123";
    +@fa-var-star-half-o: "\f123";
    +@fa-var-star-o: "\f006";
    +@fa-var-steam: "\f1b6";
    +@fa-var-steam-square: "\f1b7";
    +@fa-var-step-backward: "\f048";
    +@fa-var-step-forward: "\f051";
    +@fa-var-stethoscope: "\f0f1";
    +@fa-var-stop: "\f04d";
    +@fa-var-strikethrough: "\f0cc";
    +@fa-var-stumbleupon: "\f1a4";
    +@fa-var-stumbleupon-circle: "\f1a3";
    +@fa-var-subscript: "\f12c";
    +@fa-var-suitcase: "\f0f2";
    +@fa-var-sun-o: "\f185";
    +@fa-var-superscript: "\f12b";
    +@fa-var-support: "\f1cd";
    +@fa-var-table: "\f0ce";
    +@fa-var-tablet: "\f10a";
    +@fa-var-tachometer: "\f0e4";
    +@fa-var-tag: "\f02b";
    +@fa-var-tags: "\f02c";
    +@fa-var-tasks: "\f0ae";
    +@fa-var-taxi: "\f1ba";
    +@fa-var-tencent-weibo: "\f1d5";
    +@fa-var-terminal: "\f120";
    +@fa-var-text-height: "\f034";
    +@fa-var-text-width: "\f035";
    +@fa-var-th: "\f00a";
    +@fa-var-th-large: "\f009";
    +@fa-var-th-list: "\f00b";
    +@fa-var-thumb-tack: "\f08d";
    +@fa-var-thumbs-down: "\f165";
    +@fa-var-thumbs-o-down: "\f088";
    +@fa-var-thumbs-o-up: "\f087";
    +@fa-var-thumbs-up: "\f164";
    +@fa-var-ticket: "\f145";
    +@fa-var-times: "\f00d";
    +@fa-var-times-circle: "\f057";
    +@fa-var-times-circle-o: "\f05c";
    +@fa-var-tint: "\f043";
    +@fa-var-toggle-down: "\f150";
    +@fa-var-toggle-left: "\f191";
    +@fa-var-toggle-right: "\f152";
    +@fa-var-toggle-up: "\f151";
    +@fa-var-trash-o: "\f014";
    +@fa-var-tree: "\f1bb";
    +@fa-var-trello: "\f181";
    +@fa-var-trophy: "\f091";
    +@fa-var-truck: "\f0d1";
    +@fa-var-try: "\f195";
    +@fa-var-tumblr: "\f173";
    +@fa-var-tumblr-square: "\f174";
    +@fa-var-turkish-lira: "\f195";
    +@fa-var-twitter: "\f099";
    +@fa-var-twitter-square: "\f081";
    +@fa-var-umbrella: "\f0e9";
    +@fa-var-underline: "\f0cd";
    +@fa-var-undo: "\f0e2";
    +@fa-var-university: "\f19c";
    +@fa-var-unlink: "\f127";
    +@fa-var-unlock: "\f09c";
    +@fa-var-unlock-alt: "\f13e";
    +@fa-var-unsorted: "\f0dc";
    +@fa-var-upload: "\f093";
    +@fa-var-usd: "\f155";
    +@fa-var-user: "\f007";
    +@fa-var-user-md: "\f0f0";
    +@fa-var-users: "\f0c0";
    +@fa-var-video-camera: "\f03d";
    +@fa-var-vimeo-square: "\f194";
    +@fa-var-vine: "\f1ca";
    +@fa-var-vk: "\f189";
    +@fa-var-volume-down: "\f027";
    +@fa-var-volume-off: "\f026";
    +@fa-var-volume-up: "\f028";
    +@fa-var-warning: "\f071";
    +@fa-var-wechat: "\f1d7";
    +@fa-var-weibo: "\f18a";
    +@fa-var-weixin: "\f1d7";
    +@fa-var-wheelchair: "\f193";
    +@fa-var-windows: "\f17a";
    +@fa-var-won: "\f159";
    +@fa-var-wordpress: "\f19a";
    +@fa-var-wrench: "\f0ad";
    +@fa-var-xing: "\f168";
    +@fa-var-xing-square: "\f169";
    +@fa-var-yahoo: "\f19e";
    +@fa-var-yen: "\f157";
    +@fa-var-youtube: "\f167";
    +@fa-var-youtube-play: "\f16a";
    +@fa-var-youtube-square: "\f166";
    +
    diff --git a/assets/less/musicapristina-custom.less b/assets/less/musicapristina-custom.less
    new file mode 100644
    index 00000000..aff30cd9
    --- /dev/null
    +++ b/assets/less/musicapristina-custom.less
    @@ -0,0 +1,1842 @@
    +@import "musicapristina-variables.less";
    +
    +// Structure
    +// --------------------------------------------------------------
    +
    +html, body {
    +    height: 100%;
    +}
    +#wrap {
    +    margin: 0 auto -180px;
    +    min-height: 100%;
    +    height: auto !important;
    +    height: 100%;
    +}
    +#section-index {
    +    .container {
    +        padding-top: 40px;
    +        padding-bottom: 40px;
    +    }
    +}
    +.push, #footer {
    +    height: 180px;
    +    overflow: hidden;
    +}
    +.container {
    +    padding-top: 60px;
    +    padding-bottom: 60px;
    +}
    +#container {
    +    height: 100%;
    +}
    +
    +
    +
    +// Common use
    +// --------------------------------------------------------------
    +
    +.txtsx {
    +    text-align: left;
    +}
    +.rbg {
    +    background: #ff0000;
    +}
    +.rel {
    +    position: relative;
    +}
    +.txtdx {
    +    text-align: right;
    +}
    +.floatdx {
    +    float: right;
    +}
    +.red {
    +    color: #f00;
    +}
    +.bbg {
    +    background: #0000ff;
    +}
    +.uppercase {
    +    text-transform: uppercase;
    +}
    +.mid {
    +    margin-bottom: -3px;
    +}
    +.txtmid {
    +    text-align: center;
    +}
    +.justified {
    +    text-align: justify;
    +}
    +.clear {
    +    clear: both;
    +}
    +.green {
    +    color: #0f0;
    +}
    +.gbg {
    +    background: #00ff00;
    +}
    +.floatsx {
    +    float: left;
    +}
    +
    +
    +
    +// Top Bar
    +// --------------------------------------------------------------
    +
    +.logo {
    +    display: none;
    +}
    +
    +#menu-top {
    +    .fixed-menu;
    +    top: 0;
    +    .dropdown {
    +        display: block;
    +        float: right;
    +        height: 40px;
    +        line-height: 40px;
    +        margin: 0 20px 0 0;
    +        .dropdown-menu i {
    +            display: inline-block;
    +            width: 1.28571em;
    +            line-height: 16px;
    +            margin: 0 10px 0 5px;
    +            font-size: 14px;
    +            text-align: center;
    +        }
    +    }
    +    .dropdown-menu {
    +        .right-floating-menu;
    +        right: -20px;
    +        min-width: 160px;
    +        top: 100%;
    +        z-index: 1000;
    +    }
    +    .home {
    +        padding: 0 20px;
    +        line-height: 40px;
    +        font-weight: bold;
    +    }
    +    a {
    +        text-decoration: none;
    +        color: @text-color-alt;
    +        &:hover, &:focus {
    +            text-decoration: none;
    +            outline: none;
    +            color: @text-color-alt;
    +        }
    +    }
    +}
    +#menu-top .dropdown-menu &>li:first-child &>a,
    +#context-menus .dropdown-menu &>li:first-child &>a {
    +    border-top: 1px solid @brand-alt-dark;
    +}
    +#menu-top .dropdown-menu &>li &>a,
    +#context-menus .dropdown-menu &>li &>a {
    +    line-height: 40px;
    +    margin: 0;
    +    padding: 0 10px;
    +    background: @brand-alt;
    +    border-bottom: 1px solid @brand-alt-dark;
    +}
    +#menu-top .dropdown-menu &>li &>a:hover,
    +#menu-top .dropdown-menu &>li &>a:focus,
    +#menu-top .dropdown-menu &>li.active &>a,
    +#context-menus .dropdown-menu &>li &>a:hover,
    +#context-menus .dropdown-menu &>li.active &>a {
    +    background: @brand-primary;
    +}
    +
    +#menu-settings {
    +    display: block;
    +    height: 40px;
    +    line-height: 40px;
    +    margin: 0 -30px 0 0;
    +    padding: 0 30px;
    +}
    +
    +#context-menus {
    +    .dropdown-menu {
    +        .right-floating-menu;
    +        padding-right: 20px;
    +        min-width: 160px;
    +        & > li > a {
    +            border-left: 1px solid @brand-alt-dark;
    +            border-right: 1px solid @brand-alt-dark;
    +        }
    +        a {
    +            text-decoration: none;
    +            color: @text-color;
    +        }
    +        a:hover, a:focus {
    +            text-decoration: none;
    +            outline: none;
    +            color: @text-color;
    +        }
    +    }
    +    i {
    +        display: inline-block;
    +        width: 16px;
    +        text-align: center;
    +    }
    +}
    +.right-floating-menu {
    +    float: right;
    +    position: absolute;
    +    left: auto;
    +    background: transparent;
    +    border: none;
    +    border-radius: 0;
    +    .box-shadow(none);
    +    list-style: none outside none;
    +    margin: 0;
    +    padding: 0;
    +}
    +.fixed-menu {
    +    position: fixed;
    +    left: 0;
    +    right: 0;
    +    height: 40px;
    +    line-height: 40px;
    +    background: @brand-alt;
    +    color: @text-color;
    +    z-index: 1000;
    +}
    +
    +
    +
    +// Bottom Bar
    +// --------------------------------------------------------------
    +
    +#menu-bottom {
    +    .fixed-menu;
    +    bottom: 0;
    +    a {
    +        display: block;
    +        float: left;
    +        width: 33.333%;
    +        line-height: 40px;
    +        margin: 0;
    +        padding: 0;
    +        background: @brand-alt;
    +        font-size: 14px;
    +        text-decoration: none;
    +        text-align: center;
    +        color: @text-color-alt;
    +    }
    +    ul {
    +        display: block;
    +        margin: 0 auto;
    +        padding: 0;
    +    }
    +    li.active {
    +        a {
    +            background: @brand-primary;
    +        }
    +    }
    +    li {
    +        display: block;
    +        margin: 0;
    +        padding: 0;
    +    }
    +    i {
    +        width: 14px;
    +        line-height: 16px;
    +        margin-right: 5px;
    +        font-size: 14px;
    +    }
    +}
    +#menu-bottom a:hover, #menu-bottom a:focus {
    +    text-decoration: none;
    +    outline: none;
    +    color: @text-color-alt;
    +}
    +#open-playback {
    +    a {
    +        background: darken(@brand-alt, 5%);
    +    }
    +}
    +
    +
    +
    +// Playback main screen
    +// --------------------------------------------------------------
    +
    +#playback {
    +    padding: 40px 0;
    +    .container-fluid {
    +        max-width: 1140px;
    +        padding-top: 20px;
    +        padding-bottom: 20px;
    +        text-align: center;
    +    }
    +}
    +#currentartist, #currentsong, #currentalbum, #format-bitrate, #elapsed, #countdown-display {
    +    display: block;
    +}
    +#currentsong {
    +    font-size: 30px;
    +    font-weight: bold;
    +    color: @brand-primary;
    +    line-height: 34px;
    +    .notag {
    +        color: @brand-alt;
    +    }
    +}
    +#currentartist, #currentalbum {
    +    font-size: 18px;
    +    .notag {
    +        color: @brand-alt;
    +    }
    +}
    +#currentalbum {
    +    margin-bottom: 20px;
    +}
    +#overlay-playsource-open {
    +    display: inline-block;
    +    margin: 0 auto;
    +    &:hover {
    +        cursor: pointer;
    +        button {
    +            background-color: @brand-alt-dark;
    +        }
    +    }
    +}
    +#playlist-position,
    +#format-bitrate {
    +    color: @brand-alt-lightest;
    +}
    +#playlist-position button {
    +    margin: -2px 5px 2px 0;
    +}
    +.knobs {
    +    margin-top: 30px;
    +}
    +#time-knob,
    +#volume-knob {
    +    position: relative;
    +    text-align: center;
    +    & > div:first-child {
    +        margin: 10px auto 20px;
    +        &:hover,
    +        &:focus {
    +            cursor: pointer;
    +        }
    +    }
    +}
    +#time-knob {
    +    margin-bottom: 30px;
    +}
    +#volume-knob {
    +    margin-top: 30px;
    +}
    +@countdown-width: 150px;
    +@countdown-height: 38px;
    +@total-width: 100px;
    +@total-height: 14px;
    +#countdown-display {
    +    position: absolute;
    +    top: 115px;
    +    left: 50%;
    +    width: @countdown-width;
    +    margin: -(@countdown-height / 2) 0 0 -(@countdown-width / 2);
    +    line-height: @countdown-height;
    +    font-size: @countdown-height;
    +    font-weight: bold;
    +    text-align: center;
    +}
    +#total {
    +    position: absolute;
    +    top: 115px;
    +    left: 50%;
    +    width: @total-width;
    +    margin: (-(@total-height / 2) + (@countdown-height * 0.8)) 0 0 -(@total-width / 2);
    +    line-height: @total-height;
    +    font-size: @total-height;
    +    font-family: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif;
    +    font-weight: normal;
    +    text-align: center;
    +}
    +#time,
    +#volume {
    +    background: @body-bg;
    +    border: none;
    +    color: @body-bg;
    +}
    +.volume {
    +    .btn-toolbar {
    +        margin-top: 20px;
    +    }
    +    .btn.disabled, .btn[disabled] {
    +        margin: 0;
    +    }
    +    &.nomixer {
    +        padding-top: 0;
    +    }
    +}
    +#volume {
    +    font-family: inherit !important;
    +    &:hover,
    +    &:focus {
    +        cursor: default;
    +    }
    +}
    +
    +.playback-controls {
    +    display: block;
    +    position: absolute;
    +    top: 0;
    +    left: 0;
    +    width: 180px;
    +    margin: 0;
    +    text-align: center;
    +}
    +.countdown_holding span {
    +    color: #888;
    +}
    +.countdown_row {
    +    clear: both;
    +    width: 100%;
    +    padding: 0px 2px;
    +    text-align: center;
    +}
    +#cover-art {
    +    display: block;
    +    position: relative;
    +    width: 100%;
    +    // max-width: 294px;
    +    max-width: 250px;
    +    height: auto;
    +    margin: 10px auto 11px;
    +    background: #111 url(../img/cover-default.png) no-repeat center center;
    +    background-size: 100% 100%;
    +    border: 2px solid @brand-alt;
    +    border-radius: 6px;
    +}
    +.btn-cmd:focus {
    +    outline: none;
    +}
    +
    +
    +
    +// Playlist and Database panels
    +// --------------------------------------------------------------
    +
    +#db-level-up,
    +#db-search-results,
    +#pl-filter-results {
    +    height: 40px;
    +    margin: 0 0 0 -10px;
    +    padding: 7px 12px 5px;
    +    font-size: 16px;
    +    font-weight: normal;
    +    cursor: pointer;
    +    border-radius: 0;
    +    color: @text-color;
    +    &,
    +    &:active,
    +    &[disabled],
    +    fieldset[disabled] & {
    +        background-color: transparent;
    +        .box-shadow(none);
    +    }
    +    &,
    +    &:hover,
    +    &:focus,
    +    &:active {
    +        border-color: transparent;
    +        text-decoration: none;
    +        background-color: transparent;
    +        color: @text-color;
    +    }
    +    i {
    +        margin: 0 11px 0 2px;
    +    }
    +}
    +#pl-count {
    +    height: 40px;
    +    line-height: 40px;
    +    margin: 0 0 0 3px;
    +    font-size: 14px;
    +    font-weight: normal;
    +    cursor: pointer;
    +    border-radius: 0;
    +    color: @brand-alt-lightest;
    +}
    +.btnlist {
    +    position: fixed;
    +    left: 0;
    +    right: 0;
    +    display: block;
    +    width: auto;
    +    height: 40px;
    +    padding: 0;
    +    // background: #111D29;
    +    background: @brand-alt-darker;
    +    -webkit-border-radius: 0px;
    +    -moz-border-radius: 0px;
    +    border-radius: 0px;
    +    z-index: 999;
    +    :focus {
    +        outline: none;
    +    }
    +    a {
    +        font-size: 16px;
    +        text-decoration: none;
    +        color: @text-color;
    +    }
    +    a:hover, a:focus {
    +        text-decoration: none;
    +        outline: none;
    +        color: @text-color;
    +    }
    +}
    +.btnlist.btnlist-bottom {
    +    .btn {
    +        margin-top: 5px;
    +        padding: 0 10px;
    +        font-size: 20px;
    +    }
    +}
    +.pl-prevPage, .db-prevPage, .pl-firstPage, .db-firstPage, .btnlist-top {
    +    top: 40px;
    +    padding: 0 10px;
    +    // border-top: 1px solid @brand-alt-darkest;
    +}
    +.pl-nextPage, .db-nextPage, .pl-lastPage, .db-lastPage, .btnlist-bottom {
    +    bottom: 40px;
    +    padding: 0 10px;
    +    // border-bottom: 1px solid @brand-alt-darkest;
    +}
    +.keyword {
    +    color: @brand-primary;
    +}
    +.list-main {
    +    display: block;
    +    margin: 0;
    +    padding: 0;
    +    list-style: none;
    +    // counter-reset: item;
    +    // -webkit-transform: translateZ(0);
    +    // -webkit-transform: translate3d(0,0,0);
    +}
    +.list-li {
    +    display: block;
    +    // position: relative;
    +    height: 50px;
    +    line-height: 19px;
    +    // margin: 0;
    +    // background: @body-bg;
    +    border-bottom: 1px solid @brand-alt-darker;
    +    // color: @text-color;
    +    font-size: 15px;
    +    // text-align: left;
    +    .no-selectable;
    +    white-space: nowrap;
    +    overflow: hidden;
    +    text-overflow: ellipsis;
    +}
    +.list-entry {
    +    margin-right: 70px;
    +    height: 100%;
    +    line-height: 19px;
    +    padding: 5px 10px;
    +    white-space: nowrap;
    +    overflow: hidden;
    +    text-overflow: ellipsis;
    +}
    +.list-span {
    +    display: block;
    +    font-size: 12px;
    +    font-weight: normal;
    +    color: @brand-alt-lightest;
    +}
    +.list-action {
    +    float: right; 
    +    display: block;
    +    position: relative;
    +    width: 50px;
    +    height: 49px;
    +    line-height: 49px;
    +    font-size: 18px;
    +    font-weight: normal;
    +    text-align: center;
    +    color: @text-color;
    +    text-decoration: none;
    +}
    +.list-active {
    +    color: @text-color;
    +    background: @brand-primary;
    +}
    +#database-entries {
    +    .list-main;
    +    .search-results {
    +        .db-entry {
    +            color: #8FA7BF;
    +        }
    +        i {
    +            color: #ddd;
    +        }
    +    }
    +    li {
    +        .list-li;
    +        .clearfix();
    +    }
    +    .db-icon, .fa-folder-open, .fa-hdd-o, .fa-dot-circle-o, .fa-user, .fa-tags {
    +        display: none;
    +    }
    +    .db-entry {
    +        .list-entry;
    +    }
    +    span {
    +        white-space: nowrap;
    +        overflow: hidden;
    +        text-overflow: ellipsis;
    +    }
    +    .active, .active:hover, .active:focus {
    +        .list-active;
    +        .bl {
    +            color: @brand-alt;
    +        }
    +    }
    +    .db-folder, .db-album, .db-artist {
    +        line-height: 49px;
    +        span {
    +            display: block;
    +            padding-left: 10px;
    +        }
    +    }
    +    .db-action {
    +        .list-action;
    +    }
    +    .sn {
    +        display: block;
    +        padding: 5px 0 0 10px;
    +        span {
    +            padding-left: 10px;
    +            font-size: 14px;
    +            font-style: normal;
    +            color: @brand-alt;
    +            &.infinite {
    +                font-size: 28px;
    +            }
    +        }
    +    }
    +    .bl {
    +        display: block;
    +        padding: 0 50px 0 10px;
    +        font-size: 12px;
    +        color: @brand-alt-lightest;
    +    }
    +    #webradio-add,
    +    #album-add {
    +        background: @brand-alt-darkest;
    +        .sn {
    +            color: @brand-primary;
    +        }
    +        i,
    +        strong {
    +            color: @text-color;
    +        }
    +        strong {
    +            font-weight: inherit;
    +        }
    +    }
    +}
    +#playlist-entries {
    +    .list-main;
    +    li {
    +        .list-li;
    +        .clearfix();
    +        .fa-microphone {
    +            margin-right: 8px;
    +        }
    +    }
    +    .active, .active:hover, .active:focus {
    +        .list-active;
    +        .bl {
    +            color: @brand-alt;
    +        }
    +    }
    +    .pl-action {
    +        .list-action;
    +    }
    +    span {
    +        white-space: nowrap;
    +        overflow: hidden;
    +        text-overflow: ellipsis;
    +    }
    +    .sn {
    +        display: block;
    +        padding: 5px 0 0 10px;
    +        span {
    +            padding-left: 10px;
    +            font-size: 14px;
    +            font-style: normal;
    +            color: @brand-alt;
    +            &.infinite {
    +                font-size: 28px;
    +            }
    +        }
    +    }
    +    .bl {
    +        display: block;
    +        padding: 0 50px 0 10px;
    +        font-size: 12px;
    +        color: @brand-alt-lightest;
    +    }
    +}
    +#playlist-entries-container {
    +    position: relative;
    +}
    +#pl-editor {
    +    .list-main;
    +    li {
    +        .list-li;
    +        .clearfix();
    +    }
    +    .active, .active:hover, .active:focus {
    +        .list-active;
    +    }
    +    .pl-folder {
    +        line-height: 49px;
    +        span {
    +            display: block;
    +            padding-left: 10px;
    +        }
    +    }
    +    .fa-list-ol {
    +        display: none;
    +    }
    +    .pl-action {
    +        .list-action;
    +    }
    +    span {
    +        white-space: nowrap;
    +        overflow: hidden;
    +        text-overflow: ellipsis;
    +    }
    +}
    +#db-browse {
    +    margin: 0;
    +    width: 200px;
    +    .dropdown {
    +        display: block;
    +        height: 40px;
    +        line-height: 40px;
    +        margin: 0 20px 0 0;
    +    }
    +    .dropdown-menu {
    +        background: transparent;
    +        border: none;
    +        border-radius: 0px;
    +        .box-shadow(none);
    +        list-style: none outside none;
    +        margin: 0;
    +        min-width: 100px;
    +        padding: 0;
    +        border-top: 1px solid @body-bg;
    +        border-left: 1px solid @body-bg;
    +        border-right: 1px solid @body-bg;
    +        position: absolute;
    +        top: 100%;
    +        z-index: 1000;
    +        & > li {
    +            & > a {
    +                line-height: 40px;
    +                margin: 0;
    +                padding: 0 10px;
    +                background: @brand-alt;
    +                border-bottom: 1px solid @body-bg;
    +                color: @text-color;
    +            }
    +        }
    +    }
    +}
    +
    +#browse-mode {
    +    display: none;
    +}
    +#panel-sx, #panel-dx {
    +    .btn.disabled, .btn[disabled] {
    +        background-color: @brand-alt;
    +        color: white;
    +    }
    +}
    +#db-controls, #pl-controls {
    +    float: right;
    +}
    +#pl-manage {
    +    &.pl-manage-spotify #pl-manage-list,
    +    &.pl-manage-spotify #pl-manage-save {
    +        display: none;
    +    }
    +}
    +#db-currentpath, #pl-currentpath {
    +    line-height: 40px;
    +    i {
    +        margin: 0 3px 0 2px;
    +    }
    +}
    +#db-browse .dropdown-menu &>li &>a:hover,#db-browse .dropdown-menu &>li &>a:focus,#db-browse .dropdown-menu &>li.active &>a {
    +    background: @brand-primary;
    +}
    +// #pl-filter-results i {
    +    // color: @brand-alt;
    +// }
    +#db-search,
    +#pl-search {
    +    display: block;
    +    white-space: nowrap;
    +    float: right;
    +    max-width: 180px;
    +    margin: 4px 0 0 20px;
    +    z-index: 1001;
    +    input {
    +        display: inline-block;
    +        height: 32px;
    +        margin: 0;
    +        .border-right-radius(0);
    +    }
    +}
    +
    +#playlist, #database {
    +    padding: 80px 0;
    +}
    +
    +.sortable-ghost {
    +    background: @brand-alt-dark;
    +}
    +
    +#home-blocks {
    +    margin: 0 0 20px;
    +}
    +.home-block, .empty-block {
    +    display: block;
    +    position: relative;
    +    margin: 10px 0;
    +    padding: 20px 30px;
    +    background: @brand-alt-darkest;
    +    border-radius: @border-radius-base;
    +    color: @brand-alt-lightest;
    +    text-align: center;
    +    i {
    +        display: inline-block;
    +        width: 40px;
    +        height: 40px;
    +        line-height: 40px;
    +        font-size: 40px;
    +        text-align: center;
    +        color: @brand-alt-lightest;
    +    }
    +    h3 {
    +        margin-top: 10px;
    +        color: @text-color;
    +        white-space: nowrap;
    +        overflow: hidden;
    +        text-overflow: ellipsis;
    +    }
    +    .home-action {
    +        display: block;
    +        position: absolute;
    +        top: 0;
    +        right: 0;
    +        width: 40px;
    +        height: 40px;
    +        line-height: 40px;
    +    }
    +}
    +.home-block {
    +    &:hover, &:focus {
    +        cursor: pointer;
    +        background: @brand-alt-dark;
    +        text-decoration: none;
    +        outline: none;
    +        color: @brand-alt-lightest;
    +    }
    +    .home-block-remove {
    +        position: absolute;
    +        top: 0;
    +        bottom: 0;
    +        left: 0;
    +        right: 0;
    +        width: 100%;
    +        height: 100%;
    +        background: rgba(0,0,0,.5);
    +        span {
    +            position: absolute;
    +            top: 0;
    +            right: 0;
    +            width: 40px;
    +            height: 40px;
    +            line-height: 40px;
    +            background: @brand-alt;
    +            border-radius: @border-radius-base;
    +            color: @text-color;
    +            font-size: 30px;
    +            font-weight: bold;
    +        }
    +    }
    +}
    +
    +
    +
    +// Config screens
    +// --------------------------------------------------------------
    +
    +.boxed {
    +    padding: 15px 20px;
    +    background: darken(@brand-alt, 15%);
    +    border-radius: @border-radius-base;
    +}
    +.boxed-group {
    +    margin: 0 -15px 15px;
    +    padding: 10px 15px 5px;
    +    background: darken(@brand-alt, 15%);
    +    .form-group:last-child {
    +        margin-bottom: 0;
    +    }
    +}
    +.form-actions {
    +    padding: 10px 0 20px;
    +}
    +.manual-edit-confirm {
    +    margin-bottom: 20px;
    +}
    +.optional {
    +    position: relative;
    +}
    +.disabler {
    +    display: block;
    +    position: absolute;
    +    top: 0;
    +    right: 0;
    +    bottom: 0;
    +    left: 0;
    +    width: 100%;
    +    height: 100%;
    +    background: @body-bg;
    +    .opacity(.7);
    +    z-index: 999;
    +    &:hover {
    +        cursor: not-allowed;
    +    }
    +}
    +.disabled .disabled .disabler {
    +    background: transparent;
    +}
    +.button-list {
    +    .btn {
    +        overflow: hidden;
    +        text-overflow: ellipsis;
    +        span {
    +            font-size: 13px;
    +        }
    +        strong {
    +            font-weight: normal;
    +            text-transform: uppercase;
    +        }
    +    }
    +}
    +.info-table {
    +    width: 100%;
    +    th, td {
    +        line-height: 30px;
    +        vertical-align: top;
    +    }
    +    th {
    +        padding-right: 30px;
    +        font-weight: normal;
    +        text-align: right;
    +        color: @brand-alt-lightest;
    +    }
    +    td {
    +        font-size: 16px;
    +    }
    +}
    +#mpdconf {
    +    max-width: 100%;
    +    background: @pre-bg;
    +    font-family: @font-family-monospace;
    +    font-size: 15px;
    +    color: @pre-color;
    +}
    +.fa.fa-wifi {
    +    display: inline-block;
    +    width: 30px;
    +    height: 1px;
    +    -o-transform: rotate(-45deg);
    +    -webkit-transform: rotate(-45deg);
    +    transform: rotate(-45deg);
    +    -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(SizingMethod='auto expand', M11=-0.7071067811865476, M12=0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)"; /* IE8 */
    +    &.sx {
    +        margin-right: 15px;
    +    }
    +}
    +#wifi-signal-strength {
    +    margin: 6px 0;
    +}
    +#sysinfo-modal-body p {
    +    color: @brand-alt-lightest;
    +}
    +
    +
    +
    +// Credits
    +// --------------------------------------------------------------
    +
    +.help, .credits {
    +    font-size: 16px;
    +    line-height: 24px;
    +    .help-block {
    +        display: inline;
    +    }
    +}
    +#release-version {
    +    font-size: 20px;
    +}
    +#license {
    +    line-height: 20px;
    +    font-size: 13px;
    +    color: @brand-alt-lightest;
    +}
    +#form-paypal {
    +    text-align: center;
    +}
    +.social-buttons {
    +    text-align: center;
    +    a {
    +        margin: 0 2px 6px;
    +        padding: 8px 10px;
    +        i {
    +            display: inline-block;
    +            width: 26px;
    +            font-size: 26px;
    +        }
    +    }
    +}
    +
    +
    +
    +// Loading layer
    +// --------------------------------------------------------------
    +
    +#loader {
    +    position: fixed;
    +    top: 0;
    +    left: 0;
    +    margin: 0;
    +    padding: 0;
    +    text-align: center;
    +    width: 100%;
    +    height: 100%;
    +    z-index: 99999;
    +    // display: none;
    +}
    +#loadercontent {
    +    position: absolute;
    +    top: 50%;
    +    left: 50%;
    +    margin: -70px 0 0 -50px;
    +    width: 100px;
    +    height: 120px;
    +    line-height: 40px;
    +    text-align: center;
    +    text-transform: uppercase;
    +    color: #999;
    +    i {
    +        display: block;
    +        line-height: 100px;
    +        font-size: 100px;
    +    }
    +}
    +#loaderbg {
    +    position: absolute;
    +    top: 0;
    +    left: 0;
    +    margin: 0;
    +    padding: 0;
    +    text-align: center;
    +    background: rgba(0,0,0,0.8);
    +    width: 100%;
    +    height: 100%;
    +}
    +
    +
    +
    +// PNotify
    +// --------------------------------------------------------------
    +
    +.ui-pnotify {
    +    // top: auto;
    +    // right: auto;
    +    // bottom: 50px;
    +    // left: 10px;
    +    position: absolute;
    +    height: auto;
    +    z-index: 9999; // Ensures notices are above everything
    +    .alert {
    +        background: @brand-primary;
    +        border: 0;
    +        -webkit-border-radius: 0;
    +        -moz-border-radius: 0;
    +        border-radius: 0;
    +        color: @text-color;
    +        text-shadow: none;
    +        h4 {
    +            margin: 0 0 2px;
    +            font-size: 16px;
    +            font-weight: bold;
    +            text-transform: uppercase;
    +            color: @text-color;
    +            text-shadow: none;
    +        }
    +        span {
    +            margin-top: 2px;
    +        }
    +        .ui-pnotify-sticker {
    +            margin-right: 3px;
    +        }
    +    }
    +    .alert, .alert h4 {
    +        color: @text-color;
    +        text-shadow: none;
    +    }
    +    .ui-pnotify-shadow {
    +        .box-shadow(none);
    +    }
    +    .ui-pnotify-container {
    +        background-position: 0 0;
    +        padding: 10px 15px 8px;
    +        height: 100%;
    +        margin: 0;
    +    }
    +}
    +/* Hides position: fixed from IE6 */
    +html > body > .ui-pnotify {
    +    position: fixed;
    +}
    +.ui-pnotify-sharp {
    +    -webkit-border-radius: 0;
    +    -moz-border-radius: 0;
    +    border-radius: 0;
    +}
    +.ui-pnotify-closer, .ui-pnotify-sticker {
    +    float: right;
    +    margin-left: .2em;
    +}
    +.ui-pnotify-title {
    +    display: block;
    +    margin-bottom: .4em;
    +}
    +.ui-pnotify-text {
    +    display: block;
    +}
    +.ui-pnotify-icon, .ui-pnotify-icon span {
    +    display: block;
    +    float: left;
    +    margin-right: .2em;
    +}
    +
    +
    +
    +// Bootstrap-select
    +// --------------------------------------------------------------
    +
    +select.selectpicker {
    +    height: 43px;
    +    margin-bottom: 10px;
    +}
    +
    +.form-search, .form-inline, .form-horizontal {
    +    .bootstrap-select.btn-group {
    +        margin-bottom: 10px;
    +    }
    +}
    +.bootstrap-select .dropdown-menu {
    +    background: @brand-alt;
    +    > li > a {
    +        padding: 8px 20px;
    +        color: @text-color;
    +        &:hover, &:focus {
    +            background-color: @brand-primary;
    +            color: @text-color;
    +        }
    +    }
    +    > li.selected > a {
    +        background-color: @brand-alt-dark;
    +        outline: none;
    +    }
    +}
    +
    +
    +
    +// Switch toggles
    +// --------------------------------------------------------------
    +
    +.has-switch {
    +    border-radius: @input-border-radius;
    +    border: 2px solid @input-border;
    +    overflow: hidden;
    +    span {
    +        &.switch-left {
    +            .border-left-radius(0);
    +        }
    +        &.switch-right {
    +            color: @text-color;
    +            background: @body-bg;
    +        }
    +    }
    +}
    +.switch-block {
    +    max-width: 100%;
    +}
    +
    +
    +
    +// Social share and Renderer display/switch overlays
    +// --------------------------------------------------------------
    +
    +// 
    +// Overlay as a class... check this. I just added it to the minified CSS
    +
    +.overlay {
    +    position: fixed;
    +    width: 100%;
    +    height: 100%;
    +    top: 0;
    +    left: 0;
    +    background: @brand-alt-darkest;
    +    nav {
    +        text-align: center;
    +        position: relative;
    +        top: 50%;
    +        height: 60%;
    +        -webkit-transform: translateY(-50%);
    +        transform: translateY(-50%);
    +    }
    +    ul {
    +        list-style: none;
    +        padding: 0;
    +        margin: 0 auto;
    +        display: inline-block;
    +        height: 340px;
    +        position: relative;
    +        li {
    +            display: block;
    +            height: 60px;
    +            line-height: 60px;
    +            margin: 0;
    +            padding: 0;
    +            -webkit-backface-visibility: hidden;
    +            backface-visibility: hidden;
    +            span {
    +                font-size: 30px;
    +                font-weight: 300;
    +            }
    +            &:last-child {
    +                height: 20px;
    +                line-height: 20px;
    +            }
    +            a {
    +                font-size: 16px;
    +                text-align: right;
    +                border: 0;
    +            }
    +        }
    +    }
    +}
    +
    +
    +#overlay-alphabet.overlay ul li {
    +    display: inline-block;
    +    width: 15%;
    +}
    +
    +
    +// 
    +
    +
    +
    +
    +#overlay-social,
    +#overlay-playsource {
    +    position: fixed;
    +    width: 100%;
    +    height: 100%;
    +    top: 0;
    +    left: 0;
    +    background: @brand-alt-darkest;
    +    nav {
    +        text-align: center;
    +        position: relative;
    +        top: 50%;
    +        height: 60%;
    +        -webkit-transform: translateY(-50%);
    +        transform: translateY(-50%);
    +    }
    +    ul {
    +        list-style: none;
    +        padding: 0;
    +        margin: 0 auto;
    +        display: inline-block;
    +        height: 340px;
    +        position: relative;
    +        li {
    +            display: block;
    +            height: 60px;
    +            line-height: 60px;
    +            margin: 0;
    +            padding: 0;
    +            -webkit-backface-visibility: hidden;
    +            backface-visibility: hidden;
    +            span {
    +                font-size: 30px;
    +                font-weight: 300;
    +            }
    +            &:last-child {
    +                height: 20px;
    +                line-height: 20px;
    +            }
    +            a {
    +                font-size: 16px;
    +                text-align: right;
    +                border: 0;
    +                &.share-twitter {
    +                    background: #55ACEE;
    +                    &:hover, &:focus {
    +                        background: darken(#55ACEE, 10%);
    +                    }
    +                }
    +                &.share-facebook {
    +                    background: #3B5998;
    +                    &:hover, &:focus {
    +                        background: darken(#3B5998, 10%);
    +                    }
    +                }
    +                &.share-google-plus {
    +                    background: #DD4B39;
    +                    &:hover, &:focus {
    +                        background: darken(#DD4B39, 10%);
    +                    }
    +                }
    +                i {
    +                    display: block;
    +                    float: left;
    +                    margin: -1px 20px 0 -5px;
    +                    font-size: 26px;
    +                    text-align: center;
    +                }
    +                span {
    +                    padding-right: 5px;
    +                    font-size: 12px;
    +                }
    +                &.inactive span {
    +                    color: @brand-alt-lightest;
    +                }
    +            }
    +        }
    +    }
    +}
    +.overlay-scale {
    +    visibility: hidden;
    +    opacity: 0;
    +    -webkit-transform: scale(0.9);
    +    transform: scale(0.9);
    +    -webkit-transition: -webkit-transform 0.2s, opacity 0.2s, visibility 0s 0.2s;
    +    transition: transform 0.2s, opacity 0.2s, visibility 0s 0.2s;
    +    &.open {
    +        visibility: visible;
    +        opacity: 1;
    +        -webkit-transform: scale(1);
    +        transform: scale(1);    
    +        -webkit-transition: -webkit-transform 0.4s, opacity 0.4s;
    +        transition: transform 0.4s, opacity 0.4s;
    +    }
    +    .btn-link:hover,
    +    .btn-link:focus {
    +        text-decoration: none;
    +    }
    +}
    +
    +#playsource-airplay {
    +    background: #666;
    +    outline: none;
    +    &:hover, &:focus {
    +        background: darken(#666, 10%);
    +        cursor: default;
    +    }
    +}
    +#playsource-dlna {
    +    background: #4CA943;
    +    outline: none;
    +    &:hover, &:focus {
    +        background: darken(#4CA943, 10%);
    +        cursor: default;
    +    }
    +}
    +#playsource-mpd {
    +    background: #003D88;
    +    &:hover, &.inactive:hover, &:focus {
    +        background: darken(#003D88, 10%);
    +    }
    +}
    +#playsource-spotify {
    +    background: #81b900;
    +    &:hover, &.inactive:hover, &:focus {
    +        background: darken(#81b900, 10%);
    +    }
    +}
    +#playsource-airplay,
    +#playsource-dlna,
    +#playsource-mpd,
    +#playsource-spotify {
    +    &.inactive {
    +        opacity: 0.5;
    +        background: @brand-alt-dark;
    +    }
    +}
    +#playsource-mpd,
    +#playsource-spotify {
    +    &.inactive {
    +        opacity: 0.5;
    +        background: @brand-alt-dark;
    +    }
    +    &.inactive:hover {
    +        opacity: 1;
    +    }
    +    &.inactive:hover span {
    +        color: @text-color;
    +    }
    +}
    +#player-airplay,
    +#player-dlna {
    +    &.inactive:hover {
    +        cursor: default;
    +    }
    +}
    +
    +
    +// Loading spinner
    +// --------------------------------------------------------------
    +
    +#spinner-db,
    +#spinner-pl {
    +    position: fixed;
    +    top: 50%;
    +    left: 50%;
    +    width: 50px;
    +    height: 50px;
    +    margin: -20px 0 0 -25px;
    +    z-index: 1001;
    +}
    +
    +
    +
    +// Plugins
    +// --------------------------------------------------------------
    +
    +.jamendo-cover {
    +    float: left;
    +    height: 33px;
    +    margin: 8px;
    +}
    +
    +
    +
    +// Fix for toggle switch animation on mobile devices
    +// --------------------------------------------------------------
    +
    +#section-sources, #section-network, #section-settings {
    +    animation: bugfix infinite 1s;
    +    -moz-animation: bugfix infinite 1s;
    +    -webkit-animation: bugfix infinite 1s;
    +    -o-animation: bugfix infinite 1s;
    +    -ms-animation: bugfix infinite 1s;
    +}
    +@keyframes bugfix { from { padding: 0; } to { padding: 0; } }
    +@-moz-keyframes bugfix { from { padding: 0; } to { padding: 0; } }
    +@-webkit-keyframes bugfix { from { padding: 0; } to { padding: 0; } }
    +@-o-keyframes bugfix { from { padding: 0; } to { padding: 0; } }
    +@-ms-keyframes bugfix { from { padding: 0; } to { padding: 0; } }
    +
    +
    +
    +// Guru-meditation
    +// --------------------------------------------------------------
    +
    +.guru {
    +    border: 2px solid #c00;
    +    padding: 1em;
    +    color: #c00;
    +    text-align: center;
    +    font-family: courier, monospace;
    +    font-size: 18px;
    +    overflow: hidden;
    +    margin: 50px 0;
    +    animation: guru 2s steps(1, end) infinite;
    +    -webkit-animation: guru 2s steps(2, end) infinite;
    +}
    +@keyframes guru {
    +    0% {
    +        border: 2px solid transparent;
    +    }
    +    50% {
    +        border: 2px solid #c00;
    +    }
    +    100% {
    +        border: 2px solid transparent;
    +    }
    +}
    +@-webkit-keyframes guru {
    +    0% {
    +        border: 2px solid transparent;
    +    }
    +    50% {
    +        border: 2px solid #c00;
    +    }
    +    100% {
    +        border: 2px solid transparent;
    +    }
    +}
    +
    +
    +
    +// Custom mixins
    +// --------------------------------------------------------------
    +
    +.no-selectable {
    +    -webkit-user-select: none;  /* Chrome all / Safari all */
    +    -moz-user-select: none;     /* Firefox all */
    +    -ms-user-select: none;      /* IE 10+ */
    +
    +    /* No support for these yet, use at own risk */
    +    -o-user-select: none;
    +    user-select: none;          
    +}
    +
    +
    +
    +// Bootstrap addons/overwrites
    +// --------------------------------------------------------------
    +
    +h1, h2 {
    +    font-weight: 300;
    +    text-transform: uppercase;
    +}
    +
    +.fa.sx {
    +    width: 16px;
    +    margin-right: 10px;
    +}
    +.fa.dx {
    +    width: 16px;
    +    margin-left: 10px;
    +}
    +.btn {
    +    border: medium none;
    +}
    +.btn-primary {
    +    text-transform: uppercase;
    +}
    +.btn-lg {
    +    .button-size(@padding-btn-vertical; @padding-btn-horizontal; @font-size-large; @line-height-large; @border-radius-large);
    +}
    +.btn.disabled, .btn[disabled], fieldset[disabled] .btn {
    +    background: darken(@btn-default-bg, 10%);
    +}
    +label {
    +    font-weight: normal;
    +}
    +.form-control {
    +    border: 2px solid @input-border;
    +    border-radius: @input-border-radius;
    +    .box-shadow(none);
    +}
    +.form-control {
    +    &[disabled],
    +    &[readonly],
    +    fieldset[disabled] & {
    +        cursor: not-allowed;
    +        background-color: @input-bg-disabled;
    +    }
    +}
    +.help-block, .help-inline {
    +    color: @brand-alt-lightest;
    +    font-size: 13px;
    +    strong {
    +        color: lighten(@brand-alt, 30%);
    +    }
    +}
    +.modal-content {
    +    border: 5px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)
    +    border: 5px solid @modal-content-border-color;
    +    .box-shadow(none);
    +}
    +.modal-backdrop {
    +    &.in { .opacity(.8); }
    +}
    +.modal-header .close {
    +    font-size: (@font-size-base * 2.5);
    +    color: @brand-alt;
    +    text-shadow: none;
    +    .opacity(1);
    +    &:hover,
    +    &:focus {
    +        color: @text-color;
    +        .opacity(1);
    +    }
    +}
    +.dropdown-menu {
    +    font-size: 15px;
    +}
    +
    +select {
    +    visibility: hidden;
    +}
    +fieldset {
    +    margin-bottom: 20px;
    +}
    +/*
    +input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly] {
    +    cursor: not-allowed;
    +    background-color: #f5f5f5;
    +    border: 2px solid #DCE4EC;
    +    color: @brand-alt;
    +}
    +hr {
    +    clear: both;
    +    margin: 40px 0;
    +    border-color: @brand-alt;
    +    border-width: 1px 0 0;
    +    color: @brand-alt;
    +}
    +*/
    +
    +// On Off Buttons
    +label span {
    +    color: @lightest;
    +}
    +
    +.boxed p {
    +    color: @lightest;
    +}
    +
    +
    +
    +// Parsley.js (form validation)
    +// --------------------------------------------------------------
    +
    +input.parsley-success, textarea.parsley-success, select.parsley-success {
    +    color: #468847 !important;
    +    border-color: #468847 !important;
    +}
    +input.parsley-error, textarea.parsley-error, select.parsley-error {
    +    color: #B94A48 !important;
    +    border-color: #B94A48 !important;
    +}
    +ul.parsley-error-list {
    +    font-size: 11px;
    +    margin: 2px;
    +    list-style-type: none;
    +    li {
    +        line-height: 16px;
    +    }
    +}
    +
    +
    +
    +// Media Queries
    +// --------------------------------------------------------------
    +
    +@media (min-width: 480px) { // everything above 480px
    +    .logo {
    +        display: inline;
    +        margin: -2px 5px 0 -5px;
    +    }
    +    #menu-top .playback-controls {
    +        left: 50%;
    +        width: 180px;
    +        margin: 0 0 0 -90px;
    +    }
    +    #menu-bottom li a {
    +        font-size: 16px;
    +        i {
    +            margin-right: 10px;
    +        }
    +    }
    +    #database-entries {
    +        li {
    +            font-size: 18px;
    +        }
    +        li:hover {
    +            cursor: pointer;
    +            background: @brand-alt-darkest;
    +        }    
    +        .db-icon {
    +            display: block;
    +            float: left;
    +            width: 24px;
    +            height: 44px;
    +            margin-left: 8px;
    +            padding-top: 9px;
    +            color: @brand-alt;
    +            text-align: center;
    +        }
    +        .fa-folder-open, .fa-hdd-o, .fa-dot-circle-o, .fa-user, .fa-tags {
    +            display: inline-block;
    +            width: 24px;
    +            margin-left: 12px;
    +            margin-right: 8px;
    +            color: @brand-alt;
    +            text-align: center;
    +        }
    +        .icon-root {
    +            color: @text-color;
    +        }
    +        .db-folder, .db-album, .db-artist {
    +            span {
    +                padding: 0;
    +            }
    +        }
    +        .db-action {
    +            &:hover {
    +                cursor: pointer;
    +                font-size: 24px;
    +                color: @brand-primary;
    +            }
    +        }
    +        .active .db-action:hover {
    +            color: @text-color;
    +        }
    +    }
    +    #playlist-entries {
    +        li {
    +            font-size: 18px;
    +        }
    +        li:hover {
    +            cursor: pointer;
    +            background: @brand-alt-darkest;
    +        }
    +        // li:before {
    +            // display: block;
    +            // float: left;
    +            // width: 35px;
    +            // height: 46px;
    +            // margin: 5px 0 0 5px;
    +            // text-align: right;
    +            // counter-increment: item;
    +            // content: counter(item) '. ';
    +            // color: @brand-alt;
    +            // font-size: 15px;
    +        // }
    +        li:before {
    +            display: block;
    +            float: left;
    +            width: 24px;
    +            height: 42px;
    +            margin: 9px 0 0 8px;
    +            text-align: center;
    +            content: "\f001";
    +            font-family: "FontAwesome";
    +            color: @brand-alt;
    +            font-size: 18px;
    +        }
    +        &.playlist-spotify li:before {
    +            content: "\f1bc";
    +        }
    +        li.active:before {
    +            // content: "\f144";
    +            color: @text-color;
    +        }
    +        li.sortable-ghost:before {
    +            line-height: 46px;
    +            margin-top: 0;
    +            content: "\f07d";
    +            font-family: "FontAwesome";
    +            color: @text-color;
    +            text-align: left;
    +        }
    +        .pl-action {
    +            &:hover {
    +                cursor: pointer;
    +                font-size: 24px;
    +                color: @brand-primary;
    +            }
    +        }
    +        .active .pl-action:hover {
    +            color: @text-color;
    +        }
    +    }
    +    #pl-editor {
    +        li {
    +            font-size: 18px;
    +        }
    +        li:hover {
    +            cursor: default;
    +            background: @brand-alt-darkest;
    +        }
    +        .fa-list-ol {
    +            display: inline-block;
    +            width: 24px;
    +            margin-left: 12px;
    +            margin-right: 8px;
    +            color: @brand-alt;
    +            text-align: center;
    +        }
    +        .pl-folder {
    +            span {
    +                padding: 0;
    +            }
    +        }
    +        .pl-action {
    +            &:hover {
    +                cursor: pointer;
    +                font-size: 24px;
    +                color: @brand-primary;
    +            }
    +        }
    +    }
    +    .form-control {
    +        max-width: 400px;
    +    }
    +}
    +@media (max-width: @screen-sm-min) { // everything under 768px
    +    .form-group {
    +        margin-bottom: 0;
    +    }
    +}
    +@media (min-width: @screen-sm-min) { // everything above 768px
    +    .boxed-group {
    +        margin: 0 0 15px;
    +        padding: 10px 0 0;
    +        border-radius: @border-radius-base;
    +    }
    +    .form-actions {
    +        padding: 0;
    +    }
    +    #time-knob {
    +        margin-bottom: 0;
    +    }
    +    #volume-knob {
    +        margin-top: 0;
    +    }
    +    #countdown-display,
    +    #total {
    +        top: 125px;
    +    }
    +}
    +@media (min-width: @screen-md-min) { // everything above 992px
    +    #playback .container {
    +        padding-top: 60px;
    +        padding-bottom: 40px;
    +    }
    +    .info-table {
    +        th {
    +            width: 176px;
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/assets/less/musicapristina-fonts.less b/assets/less/musicapristina-fonts.less
    new file mode 100644
    index 00000000..a07e93e5
    --- /dev/null
    +++ b/assets/less/musicapristina-fonts.less
    @@ -0,0 +1,43 @@
    +@font-face {
    +  font-family: 'Lato';
    +  src: url('../fonts/lato/lato-bold-webfont.eot');
    +  src: url('../fonts/lato/lato-bold-webfont.eot?#iefix') format('embedded-opentype'),
    +       url('../fonts/lato/lato-bold-webfont.woff') format('woff'),
    +       url('../fonts/lato/lato-bold-webfont.ttf') format('truetype'),
    +       url('../fonts/lato/lato-bold-webfont.svg#latobold') format('svg');
    +  font-weight: bold;
    +  font-style: normal;
    +}
    +
    +@font-face {
    +  font-family: 'Lato';
    +  src: url('../fonts/lato/lato-italic-webfont.eot');
    +  src: url('../fonts/lato/lato-italic-webfont.eot?#iefix') format('embedded-opentype'),
    +       url('../fonts/lato/lato-italic-webfont.woff') format('woff'),
    +       url('../fonts/lato/lato-italic-webfont.ttf') format('truetype'),
    +       url('../fonts/lato/lato-italic-webfont.svg#latoitalic') format('svg');
    +  font-weight: normal;
    +  font-style: italic;
    +}
    +
    +@font-face {
    +  font-family: 'Lato';
    +  src: url('../fonts/lato/lato-light-webfont.eot');
    +  src: url('../fonts/lato/lato-light-webfont.eot?#iefix') format('embedded-opentype'),
    +       url('../fonts/lato/lato-light-webfont.woff') format('woff'),
    +       url('../fonts/lato/lato-light-webfont.ttf') format('truetype'),
    +       url('../fonts/lato/lato-light-webfont.svg#latolight') format('svg');
    +  font-weight: 300;
    +  font-style: normal;
    +}
    +
    +@font-face {
    +  font-family: 'Lato';
    +  src: url('../fonts/lato/lato-regular-webfont.eot');
    +  src: url('../fonts/lato/lato-regular-webfont.eot?#iefix') format('embedded-opentype'),
    +       url('../fonts/lato/lato-regular-webfont.woff') format('woff'),
    +       url('../fonts/lato/lato-regular-webfont.ttf') format('truetype'),
    +       url('../fonts/lato/lato-regular-webfont.svg#latoregular') format('svg');
    +  font-weight: normal;
    +  font-style: normal;
    +}
    \ No newline at end of file
    diff --git a/assets/less/musicapristina-variables.less b/assets/less/musicapristina-variables.less
    new file mode 100644
    index 00000000..f011cb88
    --- /dev/null
    +++ b/assets/less/musicapristina-variables.less
    @@ -0,0 +1,861 @@
    +//
    +// Variables
    +// --------------------------------------------------
    +
    +
    +//== Colors
    +//
    +//## Gray and brand colors for use across Bootstrap.
    +
    +@gray-darker:            lighten(#000, 13.5%); // #222
    +@gray-dark:              lighten(#000, 20%);   // #333
    +@gray:                   lighten(#000, 33.5%); // #555
    +@gray-light:             lighten(#000, 46.7%); // #777
    +@gray-lighter:           lighten(#000, 93.5%); // #eee
    +
    +@brand-primary:         #c05c14;     // #428bca ;
    +@brand-success:         #5cb85c;
    +@brand-info:            #5bc0de;
    +@brand-warning:         #f0ad4e;
    +@brand-danger:          #d9534f;
    +
    +@brand-alt:				#30485c;
    +@lightest:              #fff;
    +
    +
    +//== Scaffolding
    +//
    +//## Settings for some of the most global styles.
    +
    +//** Background color for ``.
    +@body-bg:               @lightest;
    +//** Global text color on ``.
    +@text-color:            @gray-dark;
    +@text-color-alt:        @gray-lighter;
    +
    +//** Global textual link color.
    +@link-color:            @brand-primary;
    +//** Link hover color set via `darken()` function.
    +@link-hover-color:      darken(@link-color, 15%);
    +
    +
    +//== Typography
    +//
    +//## Font, line-height, and color for body text, headings, and more.
    +
    +@font-family-sans-serif:  "Helvetica Neue", Helvetica, Arial, sans-serif;
    +@font-family-serif:       Georgia, "Times New Roman", Times, serif;
    +//** Default monospace fonts for ``, ``, and `
    `.
    +@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
    +@font-family-base:        @font-family-sans-serif;
    +
    +@font-size-base:          14px;
    +@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
    +@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
    +
    +@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px
    +@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px
    +@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px
    +@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px
    +@font-size-h5:            @font-size-base;
    +@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
    +
    +//** Unit-less `line-height` for use in components like buttons.
    +@line-height-base:        1.428571429; // 20/14
    +//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
    +@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
    +
    +//** By default, this inherits from the ``.
    +@headings-font-family:    inherit;
    +@headings-font-weight:    500;
    +@headings-line-height:    1.1;
    +@headings-color:          inherit;
    +
    +
    +//== Iconography
    +//
    +//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
    +
    +//** Load fonts from this directory.
    +@icon-font-path:          "../fonts/";
    +//** File name for all font files.
    +@icon-font-name:          "glyphicons-halflings-regular";
    +//** Element ID within SVG icon file.
    +@icon-font-svg-id:        "glyphicons_halflingsregular";
    +
    +
    +//== Components
    +//
    +//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
    +
    +@padding-base-vertical:     6px;
    +@padding-base-horizontal:   12px;
    +
    +@padding-large-vertical:    10px;
    +@padding-large-horizontal:  16px;
    +
    +@padding-small-vertical:    5px;
    +@padding-small-horizontal:  10px;
    +
    +@padding-xs-vertical:       1px;
    +@padding-xs-horizontal:     5px;
    +
    +@line-height-large:         1.33;
    +@line-height-small:         1.5;
    +
    +@border-radius-base:        4px;
    +@border-radius-large:       6px;
    +@border-radius-small:       3px;
    +
    +//** Global color for active items (e.g., navs or dropdowns).
    +@component-active-color:    #fff;
    +//** Global background color for active items (e.g., navs or dropdowns).
    +@component-active-bg:       @brand-primary;
    +
    +//** Width of the `border` for generating carets that indicator dropdowns.
    +@caret-width-base:          4px;
    +//** Carets increase slightly in size for larger components.
    +@caret-width-large:         5px;
    +
    +
    +//== Tables
    +//
    +//## Customizes the `.table` component with basic values, each used across all table variations.
    +
    +//** Padding for ``s and ``s.
    +@table-cell-padding:            8px;
    +//** Padding for cells in `.table-condensed`.
    +@table-condensed-cell-padding:  5px;
    +
    +//** Default background color used for all tables.
    +@table-bg:                      transparent;
    +//** Background color used for `.table-striped`.
    +@table-bg-accent:               #f9f9f9;
    +//** Background color used for `.table-hover`.
    +@table-bg-hover:                #f5f5f5;
    +@table-bg-active:               @table-bg-hover;
    +
    +//** Border color for table and cell borders.
    +@table-border-color:            #ddd;
    +
    +
    +//== Buttons
    +//
    +//## For each of Bootstrap's buttons, define text, background and border color.
    +
    +@btn-font-weight:                normal;
    +
    +@btn-default-color:              @gray-lighter;
    +@btn-default-bg:                 @brand-alt;
    +@btn-default-border:             #ccc;
    +
    +@btn-primary-color:              #fff;
    +@btn-primary-bg:                 @brand-primary;
    +@btn-primary-border:             darken(@btn-primary-bg, 5%);
    +
    +@btn-success-color:              #fff;
    +@btn-success-bg:                 @brand-success;
    +@btn-success-border:             darken(@btn-success-bg, 5%);
    +
    +@btn-info-color:                 #fff;
    +@btn-info-bg:                    @brand-info;
    +@btn-info-border:                darken(@btn-info-bg, 5%);
    +
    +@btn-warning-color:              #fff;
    +@btn-warning-bg:                 @brand-warning;
    +@btn-warning-border:             darken(@btn-warning-bg, 5%);
    +
    +@btn-danger-color:               #fff;
    +@btn-danger-bg:                  @brand-danger;
    +@btn-danger-border:              darken(@btn-danger-bg, 5%);
    +
    +@btn-link-disabled-color:        @gray-light;
    +
    +
    +//== Forms
    +//
    +//##
    +
    +//** `` background color
    +@input-bg:                       #fff;
    +//** `` background color
    +@input-bg-disabled:              @gray-lighter;
    +
    +//** Text color for ``s
    +@input-color:                    @gray;
    +//** `` border color
    +@input-border:                   #ccc;
    +//** `` border radius
    +@input-border-radius:            @border-radius-base;
    +//** Border color for inputs on focus
    +@input-border-focus:             #66afe9;
    +
    +//** Placeholder text color
    +@input-color-placeholder:        @gray-light;
    +
    +//** Default `.form-control` height
    +@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
    +//** Large `.form-control` height
    +@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
    +//** Small `.form-control` height
    +@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
    +
    +@legend-color:                   @gray-dark;
    +@legend-border-color:            #e5e5e5;
    +
    +//** Background color for textual input addons
    +@input-group-addon-bg:           @gray-lighter;
    +//** Border color for textual input addons
    +@input-group-addon-border-color: @input-border;
    +
    +
    +//== Dropdowns
    +//
    +//## Dropdown menu container and contents.
    +
    +//** Background for the dropdown menu.
    +@dropdown-bg:                    #fff;
    +//** Dropdown menu `border-color`.
    +@dropdown-border:                rgba(0,0,0,.15);
    +//** Dropdown menu `border-color` **for IE8**.
    +@dropdown-fallback-border:       #ccc;
    +//** Divider color for between dropdown items.
    +@dropdown-divider-bg:            #e5e5e5;
    +
    +//** Dropdown link text color.
    +@dropdown-link-color:            @gray-dark;
    +//** Hover color for dropdown links.
    +@dropdown-link-hover-color:      darken(@gray-dark, 5%);
    +//** Hover background for dropdown links.
    +@dropdown-link-hover-bg:         #f5f5f5;
    +
    +//** Active dropdown menu item text color.
    +@dropdown-link-active-color:     @component-active-color;
    +//** Active dropdown menu item background color.
    +@dropdown-link-active-bg:        @component-active-bg;
    +
    +//** Disabled dropdown menu item background color.
    +@dropdown-link-disabled-color:   @gray-light;
    +
    +//** Text color for headers within dropdown menus.
    +@dropdown-header-color:          @gray-light;
    +
    +//** Deprecated `@dropdown-caret-color` as of v3.1.0
    +@dropdown-caret-color:           #000;
    +
    +
    +//-- Z-index master list
    +//
    +// Warning: Avoid customizing these values. They're used for a bird's eye view
    +// of components dependent on the z-axis and are designed to all work together.
    +//
    +// Note: These variables are not generated into the Customizer.
    +
    +@zindex-navbar:            1000;
    +@zindex-dropdown:          1000;
    +@zindex-popover:           1060;
    +@zindex-tooltip:           1070;
    +@zindex-navbar-fixed:      1030;
    +@zindex-modal-background:  1040;
    +@zindex-modal:             1050;
    +
    +
    +//== Media queries breakpoints
    +//
    +//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
    +
    +// Extra small screen / phone
    +//** Deprecated `@screen-xs` as of v3.0.1
    +@screen-xs:                  480px;
    +//** Deprecated `@screen-xs-min` as of v3.2.0
    +@screen-xs-min:              @screen-xs;
    +//** Deprecated `@screen-phone` as of v3.0.1
    +@screen-phone:               @screen-xs-min;
    +
    +// Small screen / tablet
    +//** Deprecated `@screen-sm` as of v3.0.1
    +@screen-sm:                  768px;
    +@screen-sm-min:              @screen-sm;
    +//** Deprecated `@screen-tablet` as of v3.0.1
    +@screen-tablet:              @screen-sm-min;
    +
    +// Medium screen / desktop
    +//** Deprecated `@screen-md` as of v3.0.1
    +@screen-md:                  992px;
    +@screen-md-min:              @screen-md;
    +//** Deprecated `@screen-desktop` as of v3.0.1
    +@screen-desktop:             @screen-md-min;
    +
    +// Large screen / wide desktop
    +//** Deprecated `@screen-lg` as of v3.0.1
    +@screen-lg:                  1200px;
    +@screen-lg-min:              @screen-lg;
    +//** Deprecated `@screen-lg-desktop` as of v3.0.1
    +@screen-lg-desktop:          @screen-lg-min;
    +
    +// So media queries don't overlap when required, provide a maximum
    +@screen-xs-max:              (@screen-sm-min - 1);
    +@screen-sm-max:              (@screen-md-min - 1);
    +@screen-md-max:              (@screen-lg-min - 1);
    +
    +
    +//== Grid system
    +//
    +//## Define your custom responsive grid.
    +
    +//** Number of columns in the grid.
    +@grid-columns:              12;
    +//** Padding between columns. Gets divided in half for the left and right.
    +@grid-gutter-width:         30px;
    +// Navbar collapse
    +//** Point at which the navbar becomes uncollapsed.
    +@grid-float-breakpoint:     @screen-sm-min;
    +//** Point at which the navbar begins collapsing.
    +@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
    +
    +
    +//== Container sizes
    +//
    +//## Define the maximum width of `.container` for different screen sizes.
    +
    +// Small screen / tablet
    +@container-tablet:             ((720px + @grid-gutter-width));
    +//** For `@screen-sm-min` and up.
    +@container-sm:                 @container-tablet;
    +
    +// Medium screen / desktop
    +@container-desktop:            ((940px + @grid-gutter-width));
    +//** For `@screen-md-min` and up.
    +@container-md:                 @container-desktop;
    +
    +// Large screen / wide desktop
    +@container-large-desktop:      ((1140px + @grid-gutter-width));
    +//** For `@screen-lg-min` and up.
    +@container-lg:                 @container-large-desktop;
    +
    +
    +//== Navbar
    +//
    +//##
    +
    +// Basics of a navbar
    +@navbar-height:                    50px;
    +@navbar-margin-bottom:             @line-height-computed;
    +@navbar-border-radius:             @border-radius-base;
    +@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));
    +@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
    +@navbar-collapse-max-height:       340px;
    +
    +@navbar-default-color:             #777;
    +@navbar-default-bg:                #f8f8f8;
    +@navbar-default-border:            darken(@navbar-default-bg, 6.5%);
    +
    +// Navbar links
    +@navbar-default-link-color:                #777;
    +@navbar-default-link-hover-color:          #333;
    +@navbar-default-link-hover-bg:             transparent;
    +@navbar-default-link-active-color:         #555;
    +@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
    +@navbar-default-link-disabled-color:       #ccc;
    +@navbar-default-link-disabled-bg:          transparent;
    +
    +// Navbar brand label
    +@navbar-default-brand-color:               @navbar-default-link-color;
    +@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
    +@navbar-default-brand-hover-bg:            transparent;
    +
    +// Navbar toggle
    +@navbar-default-toggle-hover-bg:           #ddd;
    +@navbar-default-toggle-icon-bar-bg:        #888;
    +@navbar-default-toggle-border-color:       #ddd;
    +
    +
    +// Inverted navbar
    +// Reset inverted navbar basics
    +@navbar-inverse-color:                      @gray-light;
    +@navbar-inverse-bg:                         #222;
    +@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
    +
    +// Inverted navbar links
    +@navbar-inverse-link-color:                 @gray-light;
    +@navbar-inverse-link-hover-color:           #fff;
    +@navbar-inverse-link-hover-bg:              transparent;
    +@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
    +@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
    +@navbar-inverse-link-disabled-color:        #444;
    +@navbar-inverse-link-disabled-bg:           transparent;
    +
    +// Inverted navbar brand label
    +@navbar-inverse-brand-color:                @navbar-inverse-link-color;
    +@navbar-inverse-brand-hover-color:          #fff;
    +@navbar-inverse-brand-hover-bg:             transparent;
    +
    +// Inverted navbar toggle
    +@navbar-inverse-toggle-hover-bg:            #333;
    +@navbar-inverse-toggle-icon-bar-bg:         #fff;
    +@navbar-inverse-toggle-border-color:        #333;
    +
    +
    +//== Navs
    +//
    +//##
    +
    +//=== Shared nav styles
    +@nav-link-padding:                          10px 15px;
    +@nav-link-hover-bg:                         @gray-lighter;
    +
    +@nav-disabled-link-color:                   @gray-light;
    +@nav-disabled-link-hover-color:             @gray-light;
    +
    +@nav-open-link-hover-color:                 #fff;
    +
    +//== Tabs
    +@nav-tabs-border-color:                     #ddd;
    +
    +@nav-tabs-link-hover-border-color:          @gray-lighter;
    +
    +@nav-tabs-active-link-hover-bg:             @body-bg;
    +@nav-tabs-active-link-hover-color:          @gray;
    +@nav-tabs-active-link-hover-border-color:   #ddd;
    +
    +@nav-tabs-justified-link-border-color:            #ddd;
    +@nav-tabs-justified-active-link-border-color:     @body-bg;
    +
    +//== Pills
    +@nav-pills-border-radius:                   @border-radius-base;
    +@nav-pills-active-link-hover-bg:            @component-active-bg;
    +@nav-pills-active-link-hover-color:         @component-active-color;
    +
    +
    +//== Pagination
    +//
    +//##
    +
    +@pagination-color:                     @link-color;
    +@pagination-bg:                        #fff;
    +@pagination-border:                    #ddd;
    +
    +@pagination-hover-color:               @link-hover-color;
    +@pagination-hover-bg:                  @gray-lighter;
    +@pagination-hover-border:              #ddd;
    +
    +@pagination-active-color:              #fff;
    +@pagination-active-bg:                 @brand-primary;
    +@pagination-active-border:             @brand-primary;
    +
    +@pagination-disabled-color:            @gray-light;
    +@pagination-disabled-bg:               #fff;
    +@pagination-disabled-border:           #ddd;
    +
    +
    +//== Pager
    +//
    +//##
    +
    +@pager-bg:                             @pagination-bg;
    +@pager-border:                         @pagination-border;
    +@pager-border-radius:                  15px;
    +
    +@pager-hover-bg:                       @pagination-hover-bg;
    +
    +@pager-active-bg:                      @pagination-active-bg;
    +@pager-active-color:                   @pagination-active-color;
    +
    +@pager-disabled-color:                 @pagination-disabled-color;
    +
    +
    +//== Jumbotron
    +//
    +//##
    +
    +@jumbotron-padding:              30px;
    +@jumbotron-color:                inherit;
    +@jumbotron-bg:                   @gray-lighter;
    +@jumbotron-heading-color:        inherit;
    +@jumbotron-font-size:            ceil((@font-size-base * 1.5));
    +
    +
    +//== Form states and alerts
    +//
    +//## Define colors for form feedback states and, by default, alerts.
    +
    +@state-success-text:             #3c763d;
    +@state-success-bg:               #dff0d8;
    +@state-success-border:           darken(spin(@state-success-bg, -10), 5%);
    +
    +@state-info-text:                #31708f;
    +@state-info-bg:                  #d9edf7;
    +@state-info-border:              darken(spin(@state-info-bg, -10), 7%);
    +
    +@state-warning-text:             #8a6d3b;
    +@state-warning-bg:               #fcf8e3;
    +@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
    +
    +@state-danger-text:              #a94442;
    +@state-danger-bg:                #f2dede;
    +@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
    +
    +
    +//== Tooltips
    +//
    +//##
    +
    +//** Tooltip max width
    +@tooltip-max-width:           200px;
    +//** Tooltip text color
    +@tooltip-color:               #fff;
    +//** Tooltip background color
    +@tooltip-bg:                  #000;
    +@tooltip-opacity:             .9;
    +
    +//** Tooltip arrow width
    +@tooltip-arrow-width:         5px;
    +//** Tooltip arrow color
    +@tooltip-arrow-color:         @tooltip-bg;
    +
    +
    +//== Popovers
    +//
    +//##
    +
    +//** Popover body background color
    +@popover-bg:                          #fff;
    +//** Popover maximum width
    +@popover-max-width:                   276px;
    +//** Popover border color
    +@popover-border-color:                rgba(0,0,0,.2);
    +//** Popover fallback border color
    +@popover-fallback-border-color:       #ccc;
    +
    +//** Popover title background color
    +@popover-title-bg:                    darken(@popover-bg, 3%);
    +
    +//** Popover arrow width
    +@popover-arrow-width:                 10px;
    +//** Popover arrow color
    +@popover-arrow-color:                 #fff;
    +
    +//** Popover outer arrow width
    +@popover-arrow-outer-width:           (@popover-arrow-width + 1);
    +//** Popover outer arrow color
    +@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
    +//** Popover outer arrow fallback color
    +@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
    +
    +
    +//== Labels
    +//
    +//##
    +
    +//** Default label background color
    +@label-default-bg:            @gray-light;
    +//** Primary label background color
    +@label-primary-bg:            @brand-primary;
    +//** Success label background color
    +@label-success-bg:            @brand-success;
    +//** Info label background color
    +@label-info-bg:               @brand-info;
    +//** Warning label background color
    +@label-warning-bg:            @brand-warning;
    +//** Danger label background color
    +@label-danger-bg:             @brand-danger;
    +
    +//** Default label text color
    +@label-color:                 #fff;
    +//** Default text color of a linked label
    +@label-link-hover-color:      #fff;
    +
    +
    +//== Modals
    +//
    +//##
    +
    +//** Padding applied to the modal body
    +@modal-inner-padding:         15px;
    +
    +//** Padding applied to the modal title
    +@modal-title-padding:         15px;
    +//** Modal title line-height
    +@modal-title-line-height:     @line-height-base;
    +
    +//** Background color of modal content area
    +@modal-content-bg:                             #fff;
    +//** Modal content border color
    +@modal-content-border-color:                   rgba(0,0,0,.2);
    +//** Modal content border color **for IE8**
    +@modal-content-fallback-border-color:          #999;
    +
    +//** Modal backdrop background color
    +@modal-backdrop-bg:           #000;
    +//** Modal backdrop opacity
    +@modal-backdrop-opacity:      .5;
    +//** Modal header border color
    +@modal-header-border-color:   #e5e5e5;
    +//** Modal footer border color
    +@modal-footer-border-color:   @modal-header-border-color;
    +
    +@modal-lg:                    900px;
    +@modal-md:                    600px;
    +@modal-sm:                    300px;
    +
    +
    +//== Alerts
    +//
    +//## Define alert colors, border radius, and padding.
    +
    +@alert-padding:               15px;
    +@alert-border-radius:         @border-radius-base;
    +@alert-link-font-weight:      bold;
    +
    +@alert-success-bg:            @state-success-bg;
    +@alert-success-text:          @state-success-text;
    +@alert-success-border:        @state-success-border;
    +
    +@alert-info-bg:               @state-info-bg;
    +@alert-info-text:             @state-info-text;
    +@alert-info-border:           @state-info-border;
    +
    +@alert-warning-bg:            @state-warning-bg;
    +@alert-warning-text:          @state-warning-text;
    +@alert-warning-border:        @state-warning-border;
    +
    +@alert-danger-bg:             @state-danger-bg;
    +@alert-danger-text:           @state-danger-text;
    +@alert-danger-border:         @state-danger-border;
    +
    +
    +//== Progress bars
    +//
    +//##
    +
    +//** Background color of the whole progress component
    +@progress-bg:                 #f5f5f5;
    +//** Progress bar text color
    +@progress-bar-color:          #fff;
    +
    +//** Default progress bar color
    +@progress-bar-bg:             @brand-primary;
    +//** Success progress bar color
    +@progress-bar-success-bg:     @brand-success;
    +//** Warning progress bar color
    +@progress-bar-warning-bg:     @brand-warning;
    +//** Danger progress bar color
    +@progress-bar-danger-bg:      @brand-danger;
    +//** Info progress bar color
    +@progress-bar-info-bg:        @brand-info;
    +
    +
    +//== List group
    +//
    +//##
    +
    +//** Background color on `.list-group-item`
    +@list-group-bg:                 #fff;
    +//** `.list-group-item` border color
    +@list-group-border:             #ddd;
    +//** List group border radius
    +@list-group-border-radius:      @border-radius-base;
    +
    +//** Background color of single list items on hover
    +@list-group-hover-bg:           #f5f5f5;
    +//** Text color of active list items
    +@list-group-active-color:       @component-active-color;
    +//** Background color of active list items
    +@list-group-active-bg:          @component-active-bg;
    +//** Border color of active list elements
    +@list-group-active-border:      @list-group-active-bg;
    +//** Text color for content within active list items
    +@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
    +
    +//** Text color of disabled list items
    +@list-group-disabled-color:      @gray-light;
    +//** Background color of disabled list items
    +@list-group-disabled-bg:         @gray-lighter;
    +//** Text color for content within disabled list items
    +@list-group-disabled-text-color: @list-group-disabled-color;
    +
    +@list-group-link-color:         #555;
    +@list-group-link-hover-color:   @list-group-link-color;
    +@list-group-link-heading-color: #333;
    +
    +
    +//== Panels
    +//
    +//##
    +
    +@panel-bg:                    #fff;
    +@panel-body-padding:          15px;
    +@panel-heading-padding:       10px 15px;
    +@panel-footer-padding:        @panel-heading-padding;
    +@panel-border-radius:         @border-radius-base;
    +
    +//** Border color for elements within panels
    +@panel-inner-border:          #ddd;
    +@panel-footer-bg:             #f5f5f5;
    +
    +@panel-default-text:          @gray-dark;
    +@panel-default-border:        #ddd;
    +@panel-default-heading-bg:    #f5f5f5;
    +
    +@panel-primary-text:          #fff;
    +@panel-primary-border:        @brand-primary;
    +@panel-primary-heading-bg:    @brand-primary;
    +
    +@panel-success-text:          @state-success-text;
    +@panel-success-border:        @state-success-border;
    +@panel-success-heading-bg:    @state-success-bg;
    +
    +@panel-info-text:             @state-info-text;
    +@panel-info-border:           @state-info-border;
    +@panel-info-heading-bg:       @state-info-bg;
    +
    +@panel-warning-text:          @state-warning-text;
    +@panel-warning-border:        @state-warning-border;
    +@panel-warning-heading-bg:    @state-warning-bg;
    +
    +@panel-danger-text:           @state-danger-text;
    +@panel-danger-border:         @state-danger-border;
    +@panel-danger-heading-bg:     @state-danger-bg;
    +
    +
    +//== Thumbnails
    +//
    +//##
    +
    +//** Padding around the thumbnail image
    +@thumbnail-padding:           4px;
    +//** Thumbnail background color
    +@thumbnail-bg:                @body-bg;
    +//** Thumbnail border color
    +@thumbnail-border:            #ddd;
    +//** Thumbnail border radius
    +@thumbnail-border-radius:     @border-radius-base;
    +
    +//** Custom text color for thumbnail captions
    +@thumbnail-caption-color:     @text-color;
    +//** Padding around the thumbnail caption
    +@thumbnail-caption-padding:   9px;
    +
    +
    +//== Wells
    +//
    +//##
    +
    +@well-bg:                     #f5f5f5;
    +@well-border:                 darken(@well-bg, 7%);
    +
    +
    +//== Badges
    +//
    +//##
    +
    +@badge-color:                 #fff;
    +//** Linked badge text color on hover
    +@badge-link-hover-color:      #fff;
    +@badge-bg:                    @gray-light;
    +
    +//** Badge text color in active nav link
    +@badge-active-color:          @link-color;
    +//** Badge background color in active nav link
    +@badge-active-bg:             #fff;
    +
    +@badge-font-weight:           bold;
    +@badge-line-height:           1;
    +@badge-border-radius:         10px;
    +
    +
    +//== Breadcrumbs
    +//
    +//##
    +
    +@breadcrumb-padding-vertical:   8px;
    +@breadcrumb-padding-horizontal: 15px;
    +//** Breadcrumb background color
    +@breadcrumb-bg:                 #f5f5f5;
    +//** Breadcrumb text color
    +@breadcrumb-color:              #ccc;
    +//** Text color of current page in the breadcrumb
    +@breadcrumb-active-color:       @gray-light;
    +//** Textual separator for between breadcrumb elements
    +@breadcrumb-separator:          "/";
    +
    +
    +//== Carousel
    +//
    +//##
    +
    +@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
    +
    +@carousel-control-color:                      #fff;
    +@carousel-control-width:                      15%;
    +@carousel-control-opacity:                    .5;
    +@carousel-control-font-size:                  20px;
    +
    +@carousel-indicator-active-bg:                #fff;
    +@carousel-indicator-border-color:             #fff;
    +
    +@carousel-caption-color:                      #fff;
    +
    +
    +//== Close
    +//
    +//##
    +
    +@close-font-weight:           bold;
    +@close-color:                 #000;
    +@close-text-shadow:           0 1px 0 #fff;
    +
    +
    +//== Code
    +//
    +//##
    +
    +@code-color:                  #c7254e;
    +@code-bg:                     #f9f2f4;
    +
    +@kbd-color:                   #fff;
    +@kbd-bg:                      #333;
    +
    +@pre-bg:                      #f5f5f5;
    +@pre-color:                   @gray-dark;
    +@pre-border-color:            #ccc;
    +@pre-scrollable-max-height:   340px;
    +
    +
    +//== Type
    +//
    +//##
    +
    +//** Horizontal offset for forms and lists.
    +@component-offset-horizontal: 180px;
    +//** Text muted color
    +@text-muted:                  @gray-light;
    +//** Abbreviations and acronyms border color
    +@abbr-border-color:           @gray-light;
    +//** Headings small color
    +@headings-small-color:        @gray-light;
    +//** Blockquote small color
    +@blockquote-small-color:      @gray-light;
    +//** Blockquote font size
    +@blockquote-font-size:        (@font-size-base * 1.25);
    +//** Blockquote border color
    +@blockquote-border-color:     @gray-lighter;
    +//** Page header border color
    +@page-header-border-color:    @gray-lighter;
    +//** Width of horizontal description list titles
    +@dl-horizontal-offset:        @component-offset-horizontal;
    +//** Horizontal line color.
    +@hr-border:                   @gray-lighter;
    +
    +
    +// EXTRA
    +@padding-btn-vertical: 10px;
    +@padding-btn-horizontal: 18px;
    +
    +@brand-alt-dark: darken(@brand-alt, 10%);
    +@brand-alt-darker: darken(@brand-alt, 16%);
    +@brand-alt-darkest: darken(@brand-alt, 20%);
    +
    +@brand-alt-light: lighten(@brand-alt, 10%);
    +@brand-alt-lighter: lighten(@brand-alt, 16%);
    +@brand-alt-lightest: lighten(@brand-alt, 20%);
    \ No newline at end of file
    diff --git a/assets/less/musicapristina.less b/assets/less/musicapristina.less
    new file mode 100644
    index 00000000..f4fb2feb
    --- /dev/null
    +++ b/assets/less/musicapristina.less
    @@ -0,0 +1,61 @@
    +@import "musicapristina-variables.less";
    +
    +// BOOTSTRAP
    +// Core variables and mixins
    +//@import "bootstrap/variables.less";
    +@import "bootstrap/mixins.less";
    +
    +// Reset and dependencies
    +@import "bootstrap/normalize.less";
    +// @import "bootstrap/print.less";
    +// @import "bootstrap/glyphicons.less";
    +
    +// Core CSS
    +@import "bootstrap/scaffolding.less";
    +@import "bootstrap/type.less";
    +@import "bootstrap/code.less";
    +@import "bootstrap/grid.less";
    +// @import "bootstrap/tables.less";
    +@import "bootstrap/forms.less";
    +@import "bootstrap/buttons.less";
    +
    +// Components
    +@import "bootstrap/component-animations.less";
    +@import "bootstrap/dropdowns.less";
    +@import "bootstrap/button-groups.less";
    +@import "bootstrap/input-groups.less";
    +@import "bootstrap/navs.less";
    +// @import "bootstrap/navbar.less";
    +// @import "bootstrap/breadcrumbs.less";
    +// @import "bootstrap/pagination.less";
    +// @import "bootstrap/pager.less";
    +// @import "bootstrap/labels.less";
    +// @import "bootstrap/badges.less";
    +// @import "bootstrap/jumbotron.less";
    +// @import "bootstrap/thumbnails.less";
    +@import "bootstrap/alerts.less";
    +@import "bootstrap/progress-bars.less";
    +// @import "bootstrap/media.less";
    +// @import "bootstrap/list-group.less";
    +// @import "bootstrap/panels.less";
    +// @import "bootstrap/responsive-embed.less";
    +// @import "bootstrap/wells.less";
    +@import "bootstrap/close.less";
    +
    +// Components w/ JavaScript
    +@import "bootstrap/modals.less";
    +@import "bootstrap/tooltip.less";
    +// @import "bootstrap/popovers.less";
    +// @import "bootstrap/carousel.less";
    +
    +// Utility classes
    +@import "bootstrap/utilities.less";
    +@import "bootstrap/responsive-utilities.less";
    +
    +@import "font-awesome/font-awesome.less";
    +@import "musicapristina-fonts.less";
    +
    +@import "bootstrap-select/bootstrap-select.less";
    +@import "toggle-switch/toggle-switch.less";
    +@import "csspinner/csspinner.less";
    +@import "musicapristina-custom.less";
    \ No newline at end of file
    diff --git a/assets/less/runeui-custom.less b/assets/less/runeui-custom.less
    index 59f5e686..eb954764 100644
    --- a/assets/less/runeui-custom.less
    +++ b/assets/less/runeui-custom.less
    @@ -558,7 +558,7 @@ html, body {
     .list-li {
         display: block;
         // position: relative;
    -    height: 49px;
    +    height: 50px;
         line-height: 19px;
         // margin: 0;
         // background: @body-bg;
    @@ -720,6 +720,9 @@ html, body {
             font-size: 12px;
             color: lighten(@brand-alt, 20%);
         }
    +}
    +#playlist-entries-container {
    +    position: relative;
     }
     #pl-editor {
         .list-main;
    @@ -834,8 +837,8 @@ html, body {
     
     #playlist, #database {
         padding: 80px 0;
    -    // background: @body-bg;
    -}
    +}
    +
     .sortable-ghost {
         background: darken(@brand-alt, 10%);
     }
    @@ -1008,7 +1011,7 @@ html, body {
     #wifi-signal-strength {
         margin: 6px 0;
     }
    -#modal-sysinfo p {
    +#sysinfo-modal-body p {
         color: lighten(@brand-alt, 20%);
     }
     
    @@ -1017,7 +1020,7 @@ html, body {
     // Credits
     // --------------------------------------------------------------
     
    -.container.help, .container.credits {
    +.help, .credits {
         font-size: 16px;
         line-height: 24px;
         .help-block {
    @@ -1229,6 +1232,68 @@ select.selectpicker {
     // Social share and Renderer display/switch overlays
     // --------------------------------------------------------------
     
    +// 
    +// Overlay as a class... check this. I just added it to the minified CSS
    +
    +.overlay {
    +    position: fixed;
    +    width: 100%;
    +    height: 100%;
    +    top: 0;
    +    left: 0;
    +    background: darken(@brand-alt, 20%);
    +    nav {
    +        text-align: center;
    +        position: relative;
    +        top: 50%;
    +        height: 60%;
    +        -webkit-transform: translateY(-50%);
    +        transform: translateY(-50%);
    +    }
    +    ul {
    +        list-style: none;
    +        padding: 0;
    +        margin: 0 auto;
    +        display: inline-block;
    +        height: 340px;
    +        position: relative;
    +        li {
    +            display: block;
    +            height: 60px;
    +            line-height: 60px;
    +            margin: 0;
    +            padding: 0;
    +            -webkit-backface-visibility: hidden;
    +            backface-visibility: hidden;
    +            span {
    +                font-size: 30px;
    +                font-weight: 300;
    +            }
    +            &:last-child {
    +                height: 20px;
    +                line-height: 20px;
    +            }
    +            a {
    +                font-size: 16px;
    +                text-align: right;
    +                border: 0;
    +            }
    +        }
    +    }
    +}
    +
    +
    +#overlay-alphabet.overlay ul li {
    +    display: inline-block;
    +    width: 15%;
    +}
    +
    +
    +// 
    +
    +
    +
    +
     #overlay-social,
     #overlay-playsource {
         position: fixed;
    diff --git a/assets/less/runeui-fonts.less b/assets/less/runeui-fonts.less
    index 978a96f7..46df0134 100644
    --- a/assets/less/runeui-fonts.less
    +++ b/assets/less/runeui-fonts.less
    @@ -1,54 +1,43 @@
    -@font-face {
    -  font-family: 'Lato';
    -  src: url('../fonts/lato/lato-bold-webfont.eot');
    -  src: url('../fonts/lato/lato-bold-webfont.eot?#iefix') format('embedded-opentype'),
    -       url('../fonts/lato/lato-bold-webfont.woff') format('woff'),
    -       url('../fonts/lato/lato-bold-webfont.ttf') format('truetype'),
    -       url('../fonts/lato/lato-bold-webfont.svg#latobold') format('svg');
    -  font-weight: bold;
    -  font-style: normal;
    -}
    -
    -@font-face {
    -  font-family: 'Lato';
    -  src: url('../fonts/lato/lato-bolditalic-webfont.eot');
    -  src: url('../fonts/lato/lato-bolditalic-webfont.eot?#iefix') format('embedded-opentype'),
    -       url('../fonts/lato/lato-bolditalic-webfont.woff') format('woff'),
    -       url('../fonts/lato/lato-bolditalic-webfont.ttf') format('truetype'),
    -       url('../fonts/lato/lato-bolditalic-webfont.svg#latobold_italic') format('svg');
    -  font-weight: bold;
    -  font-style: italic;
    -}
    -
    -@font-face {
    -  font-family: 'Lato';
    -  src: url('../fonts/lato/lato-italic-webfont.eot');
    -  src: url('../fonts/lato/lato-italic-webfont.eot?#iefix') format('embedded-opentype'),
    -       url('../fonts/lato/lato-italic-webfont.woff') format('woff'),
    -       url('../fonts/lato/lato-italic-webfont.ttf') format('truetype'),
    -       url('../fonts/lato/lato-italic-webfont.svg#latoitalic') format('svg');
    -  font-weight: normal;
    -  font-style: italic;
    -}
    -
    -@font-face {
    -  font-family: 'Lato';
    -  src: url('../fonts/lato/lato-light-webfont.eot');
    -  src: url('../fonts/lato/lato-light-webfont.eot?#iefix') format('embedded-opentype'),
    -       url('../fonts/lato/lato-light-webfont.woff') format('woff'),
    -       url('../fonts/lato/lato-light-webfont.ttf') format('truetype'),
    -       url('../fonts/lato/lato-light-webfont.svg#latolight') format('svg');
    -  font-weight: 300;
    -  font-style: normal;
    -}
    -
    -@font-face {
    -  font-family: 'Lato';
    -  src: url('../fonts/lato/lato-regular-webfont.eot');
    -  src: url('../fonts/lato/lato-regular-webfont.eot?#iefix') format('embedded-opentype'),
    -       url('../fonts/lato/lato-regular-webfont.woff') format('woff'),
    -       url('../fonts/lato/lato-regular-webfont.ttf') format('truetype'),
    -       url('../fonts/lato/lato-regular-webfont.svg#latoregular') format('svg');
    -  font-weight: normal;
    -  font-style: normal;
    +@font-face {
    +  font-family: 'Lato';
    +  src: url('../fonts/lato/lato-bold-webfont.eot');
    +  src: url('../fonts/lato/lato-bold-webfont.eot?#iefix') format('embedded-opentype'),
    +       url('../fonts/lato/lato-bold-webfont.woff') format('woff'),
    +       url('../fonts/lato/lato-bold-webfont.ttf') format('truetype'),
    +       url('../fonts/lato/lato-bold-webfont.svg#latobold') format('svg');
    +  font-weight: bold;
    +  font-style: normal;
    +}
    +
    +@font-face {
    +  font-family: 'Lato';
    +  src: url('../fonts/lato/lato-italic-webfont.eot');
    +  src: url('../fonts/lato/lato-italic-webfont.eot?#iefix') format('embedded-opentype'),
    +       url('../fonts/lato/lato-italic-webfont.woff') format('woff'),
    +       url('../fonts/lato/lato-italic-webfont.ttf') format('truetype'),
    +       url('../fonts/lato/lato-italic-webfont.svg#latoitalic') format('svg');
    +  font-weight: normal;
    +  font-style: italic;
    +}
    +
    +@font-face {
    +  font-family: 'Lato';
    +  src: url('../fonts/lato/lato-light-webfont.eot');
    +  src: url('../fonts/lato/lato-light-webfont.eot?#iefix') format('embedded-opentype'),
    +       url('../fonts/lato/lato-light-webfont.woff') format('woff'),
    +       url('../fonts/lato/lato-light-webfont.ttf') format('truetype'),
    +       url('../fonts/lato/lato-light-webfont.svg#latolight') format('svg');
    +  font-weight: 300;
    +  font-style: normal;
    +}
    +
    +@font-face {
    +  font-family: 'Lato';
    +  src: url('../fonts/lato/lato-regular-webfont.eot');
    +  src: url('../fonts/lato/lato-regular-webfont.eot?#iefix') format('embedded-opentype'),
    +       url('../fonts/lato/lato-regular-webfont.woff') format('woff'),
    +       url('../fonts/lato/lato-regular-webfont.ttf') format('truetype'),
    +       url('../fonts/lato/lato-regular-webfont.svg#latoregular') format('svg');
    +  font-weight: normal;
    +  font-style: normal;
     }
    \ No newline at end of file
    diff --git a/assets/less/runeui-variables.less b/assets/less/runeui-variables.less
    index 899ef2cd..8f12c85d 100644
    --- a/assets/less/runeui-variables.less
    +++ b/assets/less/runeui-variables.less
    @@ -1,133 +1,133 @@
    -// Brand colors
    -// -------------------------
    -@brand-primary:         #0095D8;
    -@brand-alt:				#34495E;
    -
    -// @brand-primary:         #16a085;
    -// @brand-alt:				#2c3e50;
    -
    -
    -// Scaffolding
    -// -------------------------
    -@body-bg:               #000;
    -@text-color:            lighten(@brand-alt, 62%);
    -
    -
    -// Typography
    -// -------------------------
    -@font-family-sans-serif:  "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
    -
    -@font-size-large:         ceil((@font-size-base * 1.10)); // ~16px
    -
    -// Components
    -// -------------------------
    -// Based on 14px font-size and 1.428 line-height (~20px to start)
    -
    -@padding-btn-vertical:         11px;
    -@padding-btn-horizontal:       19px;
    -
    -@border-radius-base:        6px;
    -
    -
    -// Buttons
    -// -------------------------
    -@btn-default-color:              @text-color;
    -@btn-default-bg:                 @brand-alt;
    -@btn-default-border:             #ccc;
    -
    -@btn-primary-color:              @text-color;
    -@btn-primary-bg:                 @brand-primary;
    -@btn-primary-border:             darken(@btn-primary-bg, 5%);
    -
    -@btn-success-color:              @text-color;
    -@btn-success-bg:                 @brand-success;
    -@btn-success-border:             darken(@btn-success-bg, 5%);
    -
    -@btn-warning-color:              @text-color;
    -@btn-warning-bg:                 @brand-warning;
    -@btn-warning-border:             darken(@btn-warning-bg, 5%);
    -
    -@btn-danger-color:               @text-color;
    -@btn-danger-bg:                  @brand-danger;
    -@btn-danger-border:              darken(@btn-danger-bg, 5%);
    -
    -@btn-info-color:                 @text-color;
    -@btn-info-bg:                    @brand-info;
    -@btn-info-border:                darken(@btn-info-bg, 5%);
    -
    -@btn-link-disabled-color:        @gray-light;
    -
    -
    -// Forms
    -// -------------------------
    -@input-bg:                       darken(@brand-alt, 20%); // #11181F;
    -@input-bg-disabled:              darken(@input-bg, 10%);
    -
    -@input-color:                    @text-color;
    -@input-border:                   @brand-alt;
    -@input-border-radius:            @border-radius-base;
    -@input-border-focus:             #66afe9;
    -
    -//** Large `.form-control` height
    -@input-height-large:             (ceil(@font-size-base * @line-height-large) + (@padding-large-vertical * 2) + 2);
    -
    -@legend-color:                   @text-color;
    -@legend-border-color:            @brand-alt;
    -
    -
    -// Dropdowns
    -// -------------------------
    -@dropdown-bg:                    #fff;
    -@dropdown-border:                rgba(0,0,0,.15);
    -@dropdown-fallback-border:       #ccc;
    -@dropdown-divider-bg:            #e5e5e5;
    -
    -@dropdown-link-color:            @gray-dark;
    -@dropdown-link-hover-color:      darken(@gray-dark, 5%);
    -@dropdown-link-hover-bg:         #f5f5f5;
    -
    -@dropdown-link-active-color:     @component-active-color;
    -@dropdown-link-active-bg:        @component-active-bg;
    -
    -@dropdown-link-disabled-color:   @gray-light;
    -
    -@dropdown-header-color:          @gray-light;
    -
    -// Tooltips
    -// -------------------------
    -@tooltip-bg:                     @brand-alt;
    -
    -// Modals
    -// -------------------------
    -@modal-content-bg:                             darken(@brand-alt, 20%);
    -@modal-content-border-color:                   @brand-alt;
    -@modal-content-fallback-border-color:          darken(@brand-alt, 5%);
    -
    -@modal-backdrop-bg:           #000;
    -@modal-header-border-color:   darken(@brand-alt, 20%);
    -@modal-footer-border-color:   darken(@brand-alt, 20%);
    -
    -
    -// Code
    -// ------------------------
    -@code-bg:                     @brand-alt;
    -@code-color:                  #c7254e;
    -
    -@pre-bg:                      lighten(@body-bg, 10%);
    -@pre-color:                   lighten(@brand-alt, 30%);
    -@pre-border-color:            darken(@brand-alt, 20%);
    -@pre-scrollable-max-height:   340px;
    -
    -
    -// Form states and alerts
    -// -------------------------
    -
    -@state-info-text:				@text-color;
    -@state-info-bg:					darken(@brand-alt, 20%);
    -@state-info-border:				darken(spin(@state-info-bg, -10), 7%);
    -
    -
    -// Progress bars
    -// -------------------------
    -
    +// Brand colors
    +// -------------------------
    +@brand-primary:         #0095D8;
    +@brand-alt:				#34495E;
    +
    +// @brand-primary:         #16a085;
    +// @brand-alt:				#2c3e50;
    +
    +
    +// Scaffolding
    +// -------------------------
    +@body-bg:               #000;
    +@text-color:            lighten(@brand-alt, 62%);
    +
    +
    +// Typography
    +// -------------------------
    +@font-family-sans-serif:  "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
    +
    +@font-size-large:         ceil((@font-size-base * 1.10)); // ~16px
    +
    +// Components
    +// -------------------------
    +// Based on 14px font-size and 1.428 line-height (~20px to start)
    +
    +@padding-btn-vertical:         11px;
    +@padding-btn-horizontal:       19px;
    +
    +@border-radius-base:        6px;
    +
    +
    +// Buttons
    +// -------------------------
    +@btn-default-color:              @text-color;
    +@btn-default-bg:                 @brand-alt;
    +@btn-default-border:             #ccc;
    +
    +@btn-primary-color:              @text-color;
    +@btn-primary-bg:                 @brand-primary;
    +@btn-primary-border:             darken(@btn-primary-bg, 5%);
    +
    +@btn-success-color:              @text-color;
    +@btn-success-bg:                 @brand-success;
    +@btn-success-border:             darken(@btn-success-bg, 5%);
    +
    +@btn-warning-color:              @text-color;
    +@btn-warning-bg:                 @brand-warning;
    +@btn-warning-border:             darken(@btn-warning-bg, 5%);
    +
    +@btn-danger-color:               @text-color;
    +@btn-danger-bg:                  @brand-danger;
    +@btn-danger-border:              darken(@btn-danger-bg, 5%);
    +
    +@btn-info-color:                 @text-color;
    +@btn-info-bg:                    @brand-info;
    +@btn-info-border:                darken(@btn-info-bg, 5%);
    +
    +@btn-link-disabled-color:        @gray-light;
    +
    +
    +// Forms
    +// -------------------------
    +@input-bg:                       darken(@brand-alt, 20%); // #11181F;
    +@input-bg-disabled:              darken(@input-bg, 10%);
    +
    +@input-color:                    @text-color;
    +@input-border:                   @brand-alt;
    +@input-border-radius:            @border-radius-base;
    +@input-border-focus:             #66afe9;
    +
    +//** Large `.form-control` height
    +@input-height-large:             (ceil(@font-size-base * @line-height-large) + (@padding-large-vertical * 2) + 2);
    +
    +@legend-color:                   @text-color;
    +@legend-border-color:            @brand-alt;
    +
    +
    +// Dropdowns
    +// -------------------------
    +@dropdown-bg:                    #fff;
    +@dropdown-border:                rgba(0,0,0,.15);
    +@dropdown-fallback-border:       #ccc;
    +@dropdown-divider-bg:            #e5e5e5;
    +
    +@dropdown-link-color:            @gray-dark;
    +@dropdown-link-hover-color:      darken(@gray-dark, 5%);
    +@dropdown-link-hover-bg:         #f5f5f5;
    +
    +@dropdown-link-active-color:     @component-active-color;
    +@dropdown-link-active-bg:        @component-active-bg;
    +
    +@dropdown-link-disabled-color:   @gray-light;
    +
    +@dropdown-header-color:          @gray-light;
    +
    +// Tooltips
    +// -------------------------
    +@tooltip-bg:                     @brand-alt;
    +
    +// Modals
    +// -------------------------
    +@modal-content-bg:                             darken(@brand-alt, 20%);
    +@modal-content-border-color:                   @brand-alt;
    +@modal-content-fallback-border-color:          darken(@brand-alt, 5%);
    +
    +@modal-backdrop-bg:           #000;
    +@modal-header-border-color:   darken(@brand-alt, 20%);
    +@modal-footer-border-color:   darken(@brand-alt, 20%);
    +
    +
    +// Code
    +// ------------------------
    +@code-bg:                     @brand-alt;
    +@code-color:                  #c7254e;
    +
    +@pre-bg:                      lighten(@body-bg, 10%);
    +@pre-color:                   lighten(@brand-alt, 30%);
    +@pre-border-color:            darken(@brand-alt, 20%);
    +@pre-scrollable-max-height:   340px;
    +
    +
    +// Form states and alerts
    +// -------------------------
    +
    +@state-info-text:				@text-color;
    +@state-info-bg:					darken(@brand-alt, 20%);
    +@state-info-border:				darken(spin(@state-info-bg, -10), 7%);
    +
    +
    +// Progress bars
    +// -------------------------
    +
     @progress-bg:					@brand-alt;
    \ No newline at end of file
    diff --git a/assets/less/toggle-switch/toggle-switch.less b/assets/less/toggle-switch/toggle-switch.less
    index 8f0b939b..d6f55dbb 100644
    --- a/assets/less/toggle-switch/toggle-switch.less
    +++ b/assets/less/toggle-switch/toggle-switch.less
    @@ -1,137 +1,137 @@
    -/*
    - * CSS TOGGLE SWITCHES
    - * Unlicense
    - *
    - * Ionuț Colceriu - ghinda.net
    - * https://github.com/ghinda/css-toggle-switch
    - *
    - */
    - 
    -@white: #fff;
    -
    -/* Mixins
    -*/
    -.layout (@position; @top; @right; @left; @display; @width; @zindex) {
    -  position: @position;
    -  top: @top;
    -  right: @right;
    -  left: @left;
    -  display: @display;
    -  width: @width;
    -  z-index: @zindex;
    -}
    -
    -.container (@bg-color; @border; @border-radius; @shadow1; @shadow2; @shadow3) {
    -  background-color: @bg-color;
    -  border: @border;
    -  border-radius: @border-radius;
    -  .box-shadow (@shadow1; @shadow2; @shadow3);
    -}
    -
    -.box-shadow (@shadow1: 0 0 0 rgba(0, 0, 0, 0); @shadow2: 0 0 0 rgba(0, 0, 0, 0); @shadow3: 0 0 0 rgba(0, 0, 0, 0)) {
    -  -webkit-box-shadow: @shadow1, @shadow2, @shadow3;
    -  -moz-box-shadow: @shadow1, @shadow2, @shadow3;
    -  box-shadow: @shadow1, @shadow2, @shadow3;
    -}
    -
    -.bg-image (@start-color; @end-color) {
    -  background-image: -webkit-linear-gradient(top, @start-color, @end-color);
    -  background-image: linear-gradient(to bottom, @start-color, @end-color);
    -}
    -
    -.slide-button-transition (@duration: .2s) {
    -  -webkit-transition: all @duration ease-out;
    -  -moz-transition: all @duration ease-out;
    -  transition: all @duration ease-out;
    -}
    -
    -.switch-toggle a, .switch-light span span {
    -  display: none; }
    -
    -/* We can't test for a specific feature,
    - * so we only target browsers with support for media queries.
    - */
    -@media only screen {
    -  /* Checkbox switch
    -   */
    -  /* Radio switch
    -   */
    -  /* Standalone Themes */
    -  /* Candy Theme
    -   * Based on the "Sort Switches / Toggles (PSD)" by Ormal Clarck
    -   * http://www.premiumpixels.com/freebies/sort-switches-toggles-psd/
    -   */
    -  /* Android Theme
    -   */
    -  /* iOS Theme
    -   */
    -     
    -  .switch-light {
    -    display: block;
    -    max-width: 120px;
    -	height: 40px;
    -    /* Outline the toggles when the inputs are focused
    -   */
    -    position: relative;
    -    overflow: visible;
    -    padding: 0;
    -	background: @brand-alt;
    -    // margin-left: 100px;
    -	border-radius: 6px;
    -	&:hover {
    -      cursor: pointer;
    -    }
    -    /* Position the label over all the elements, except the slide-button ()
    -   * Clicking anywhere on the label will change the switch-state
    -   */
    -    /* Don't hide the input from screen-readers and keyboard access
    -   */ }
    -    .switch-light * {
    -      -webkit-box-sizing: border-box;
    -      -moz-box-sizing: border-box;
    -      box-sizing: border-box; }
    -    .switch-light a {
    -      display: block;
    -      .slide-button-transition; }
    -    .switch-light label, .switch-light > span {
    -      line-height: 40px;
    -      vertical-align: middle; }
    -    .switch-light input:focus ~ a, .switch-light input:focus + label {
    -      outline: 1px dotted #888888; }
    -    .switch-light label {
    -      .layout (relative; static; static; static; block; 100%; 3); }
    -    .switch-light input {
    -      .layout (absolute; static; static; static; inherit; auto; 5);
    -      opacity: 0; }
    -      .switch-light input:checked ~ a {
    -        right: 0%; }
    -    .switch-light > span {
    -      .layout (absolute; static; static; -100px; inherit; 100%; auto);
    -      margin: 0;
    -      padding-right: 100px;
    -      text-align: left; }
    -      .switch-light > span span {
    -        .layout (absolute; 0; static; 0; block; 50%; 5);
    -        margin-left: 100px;
    -        text-align: center; }
    -        .switch-light > span span:last-child {
    -          left: 50%; }
    -    .switch-light a {
    -      .layout (absolute; 0; 50%; static; block; 50%; 4);
    -      height: 100%;
    -      padding: 0; }
    -}
    -
    -/* Bugfix for older Webkit, including mobile Webkit. Adapted from
    - * http://css-tricks.com/webkit-sibling-bug/
    - */
    -@media only screen and (-webkit-max-device-pixel-ratio: 2) and (max-device-width: 1280px) {
    -  .switch-light, .switch-toggle {
    -    -webkit-animation: webkitSiblingBugfix infinite 1s; } }
    -
    -@-webkit-keyframes webkitSiblingBugfix {
    -  from {
    -    -webkit-transform: translate3d(0, 0, 0); }
    -
    -  to {
    -    -webkit-transform: translate3d(0, 0, 0); } }
    +/*
    + * CSS TOGGLE SWITCHES
    + * Unlicense
    + *
    + * Ionuț Colceriu - ghinda.net
    + * https://github.com/ghinda/css-toggle-switch
    + *
    + */
    + 
    +@white: #fff;
    +
    +/* Mixins
    +*/
    +.layout (@position; @top; @right; @left; @display; @width; @zindex) {
    +  position: @position;
    +  top: @top;
    +  right: @right;
    +  left: @left;
    +  display: @display;
    +  width: @width;
    +  z-index: @zindex;
    +}
    +
    +.container (@bg-color; @border; @border-radius; @shadow1; @shadow2; @shadow3) {
    +  background-color: @bg-color;
    +  border: @border;
    +  border-radius: @border-radius;
    +  .box-shadow (@shadow1; @shadow2; @shadow3);
    +}
    +
    +.box-shadow (@shadow1: 0 0 0 rgba(0, 0, 0, 0); @shadow2: 0 0 0 rgba(0, 0, 0, 0); @shadow3: 0 0 0 rgba(0, 0, 0, 0)) {
    +  -webkit-box-shadow: @shadow1, @shadow2, @shadow3;
    +  -moz-box-shadow: @shadow1, @shadow2, @shadow3;
    +  box-shadow: @shadow1, @shadow2, @shadow3;
    +}
    +
    +.bg-image (@start-color; @end-color) {
    +  background-image: -webkit-linear-gradient(top, @start-color, @end-color);
    +  background-image: linear-gradient(to bottom, @start-color, @end-color);
    +}
    +
    +.slide-button-transition (@duration: .2s) {
    +  -webkit-transition: all @duration ease-out;
    +  -moz-transition: all @duration ease-out;
    +  transition: all @duration ease-out;
    +}
    +
    +.switch-toggle a, .switch-light span span {
    +  display: none; }
    +
    +/* We can't test for a specific feature,
    + * so we only target browsers with support for media queries.
    + */
    +@media only screen {
    +  /* Checkbox switch
    +   */
    +  /* Radio switch
    +   */
    +  /* Standalone Themes */
    +  /* Candy Theme
    +   * Based on the "Sort Switches / Toggles (PSD)" by Ormal Clarck
    +   * http://www.premiumpixels.com/freebies/sort-switches-toggles-psd/
    +   */
    +  /* Android Theme
    +   */
    +  /* iOS Theme
    +   */
    +     
    +  .switch-light {
    +    display: block;
    +    max-width: 120px;
    +	height: 40px;
    +    /* Outline the toggles when the inputs are focused
    +   */
    +    position: relative;
    +    overflow: visible;
    +    padding: 0;
    +	background: @brand-alt;
    +    // margin-left: 100px;
    +	border-radius: 6px;
    +	&:hover {
    +      cursor: pointer;
    +    }
    +    /* Position the label over all the elements, except the slide-button ()
    +   * Clicking anywhere on the label will change the switch-state
    +   */
    +    /* Don't hide the input from screen-readers and keyboard access
    +   */ }
    +    .switch-light * {
    +      -webkit-box-sizing: border-box;
    +      -moz-box-sizing: border-box;
    +      box-sizing: border-box; }
    +    .switch-light a {
    +      display: block;
    +      .slide-button-transition; }
    +    .switch-light label, .switch-light > span {
    +      line-height: 40px;
    +      vertical-align: middle; }
    +    .switch-light input:focus ~ a, .switch-light input:focus + label {
    +      outline: 1px dotted #888888; }
    +    .switch-light label {
    +      .layout (relative; static; static; static; block; 100%; 3); }
    +    .switch-light input {
    +      .layout (absolute; static; static; static; inherit; auto; 5);
    +      opacity: 0; }
    +      .switch-light input:checked ~ a {
    +        right: 0%; }
    +    .switch-light > span {
    +      .layout (absolute; static; static; -100px; inherit; 100%; auto);
    +      margin: 0;
    +      padding-right: 100px;
    +      text-align: left; }
    +      .switch-light > span span {
    +        .layout (absolute; 0; static; 0; block; 50%; 5);
    +        margin-left: 100px;
    +        text-align: center; }
    +        .switch-light > span span:last-child {
    +          left: 50%; }
    +    .switch-light a {
    +      .layout (absolute; 0; 50%; static; block; 50%; 4);
    +      height: 100%;
    +      padding: 0; }
    +}
    +
    +/* Bugfix for older Webkit, including mobile Webkit. Adapted from
    + * http://css-tricks.com/webkit-sibling-bug/
    + */
    +@media only screen and (-webkit-max-device-pixel-ratio: 2) and (max-device-width: 1280px) {
    +  .switch-light, .switch-toggle {
    +    -webkit-animation: webkitSiblingBugfix infinite 1s; } }
    +
    +@-webkit-keyframes webkitSiblingBugfix {
    +  from {
    +    -webkit-transform: translate3d(0, 0, 0); }
    +
    +  to {
    +    -webkit-transform: translate3d(0, 0, 0); } }
    diff --git a/command/HtmlPage.html b/command/HtmlPage.html
    new file mode 100644
    index 00000000..3407f6ba
    --- /dev/null
    +++ b/command/HtmlPage.html
    @@ -0,0 +1,9 @@
    +
    +
    +
    +    
    +
    +
    +
    +
    +
    diff --git a/command/airplay_toggle b/command/airplay_toggle
    index 7fa2fb83..80b0918c 100755
    --- a/command/airplay_toggle
    +++ b/command/airplay_toggle
    @@ -1,57 +1,57 @@
    -#!/usr/bin/php
    -.
    - *
    - *  file: command/airplay_toggle
    - *  version: 1.3
    - *  coder: Simone De Gregori
    - *
    - */
    -// common include
    -ini_set('display_errors', '1');
    -error_reporting(E_ALL);
    -ini_set('error_log', '/var/log/runeaudio/airplay_toggle.log');
    -define('APP', '/srv/http/app/');
    -// Connect to Redis backend
    -$redis = new Redis();
    -$redis->connect('/tmp/redis.sock');
    -$activePlayer = $redis->get('activePlayer');
    -include('/srv/http/app/libs/runeaudio.php');
    -// toggle playback
    -wrk_togglePlayback($redis, $activePlayer);
    -if ($activePlayer !== 'Airplay') {
    -    // set activePlayer
    -    $redis->set('activePlayer', 'Airplay');
    -    // send notify
    -    ui_notify('AirPlay', 'The player is locket by an incoming Airplay stream.');
    -} else {
    -    // send notify
    -    ui_notify('AirPlay', 'Airplay stream finished. Player unlocked.');
    -}
    -// update player view
    -ui_libraryHome($redis);
    +#!/usr/bin/php
    +.
    + *
    + *  file: command/airplay_toggle
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +ini_set('display_errors', '1');
    +error_reporting(E_ALL);
    +ini_set('error_log', '/var/log/runeaudio/airplay_toggle.log');
    +define('APP', '/srv/http/app/');
    +// Connect to Redis backend
    +$redis = new Redis();
    +$redis->connect('/tmp/redis.sock');
    +$activePlayer = $redis->get('activePlayer');
    +include('/srv/http/app/libs/runeaudio.php');
    +// toggle playback
    +wrk_togglePlayback($redis, $activePlayer);
    +if ($activePlayer !== 'Airplay') {
    +    // set activePlayer
    +    $redis->set('activePlayer', 'Airplay');
    +    // send notify
    +    ui_notify('AirPlay', 'The player is locket by an incoming Airplay stream.');
    +} else {
    +    // send notify
    +    ui_notify('AirPlay', 'Airplay stream finished. Player unlocked.');
    +}
    +// update player view
    +ui_libraryHome($redis);
    diff --git a/command/cachectl.php b/command/cachectl.php
    old mode 100755
    new mode 100644
    index 0cb6ae97..e97f3a5a
    --- a/command/cachectl.php
    +++ b/command/cachectl.php
    @@ -47,7 +47,7 @@
                 OpCacheCtl('reset', '/srv/http/');
                 opcache_reset();
                 runelog('cacheCTL RESET');
    -            echo "PHP OPCACHE CLEARED";
    +            echo "PHP OPCACHE CLEARED " . (new \DateTime())->format('Y-m-d H:i:s');
                 break;
             case 'debug':
                 // opcache_reset();
    diff --git a/command/cmd_async b/command/cmd_async
    new file mode 100755
    index 00000000..a7dfc1bf
    --- /dev/null
    +++ b/command/cmd_async
    @@ -0,0 +1,57 @@
    +#!/usr/bin/php
    +.
    + *
    + *  file: command/cmd_async {cmd} {usec wait} 
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +ini_set('display_errors', '1');
    +// ini_set('error_reporting', -1);
    +ini_set('error_log', '/var/log/runeaudio/cmd_async.log');
    +// Connect to Redis backend
    +$redis = new Redis();
    +$redis->connect('/tmp/redis.sock');
    +include('/var/www/app/libs/runeaudio.php');
    +
    +if (isset($argv[1])) {
    +$argv[1] = base64_decode($argv[1]);
    +    if (isset($argv[2])) {
    +        settype($argv[2], 'integer');
    +        echo "wait for ".$argv[2]." seconds\n";
    +        sleep($argv[2]);
    +    }
    +    echo "execute command: ".$argv[1]."\n";
    +    $output = sysCmd($argv[1]);
    +    echo "command response:\n";
    +    foreach ($output as $line) echo "\t".$line."\n";
    +} else {
    +    echo "cmd_async {cmd} {sec wait}\n";
    +}
    diff --git a/command/debug_collector b/command/debug_collector
    index db3dc3a8..65069812 100755
    --- a/command/debug_collector
    +++ b/command/debug_collector
    @@ -1,242 +1,242 @@
    -#!/usr/bin/php
    -.
    - *
    - *  file: command/debug_collector
    - *  version: 1.3
    - *  coder: Simone De Gregori
    - *
    - */
    -// common include
    -ini_set('display_errors', '1');
    -ini_set('error_reporting', -1);
    -ini_set('error_log', '/var/log/runeaudio/debug_collector.log');
    -include('/var/www/app/libs/runeaudio.php');
    -// Connect to Redis backend
    -$redis = new Redis();
    -$redis->connect('/tmp/redis.sock');
    -function debug_data($redis)
    -{
    -    $acards = sysCmd("cat /proc/asound/cards | grep : | cut -d ' ' -f 2");
    -    $output = "\n";
    -    $output .= "###### System info ######\n";
    -    $output .=  file_get_contents('/proc/version');
    -    $output .= "\n";
    -    $output .=  "system time:\t".implode('\n', sysCmd('date'));
    -    $output .= "\n";
    -    $output .= "\n";
    -    $output .=  "system load:\t".file_get_contents('/proc/loadavg');
    -    $output .= "\n";
    -    $output .= "HW platform:\t".$redis->get('hwplatform')." (".$redis->get('hwplatformid').")\n";
    -    $output .= "\n";
    -    $output .= "playerID:\t".$redis->get('playerid')."\n";
    -    $output .= "\n";
    -    $output .= "\n";
    -    $output .= "###### System load statistics (procinfo -H) ######\n";
    -    $command = sysCmd('procinfo -H');
    -    $output .= implode("\n", $command)."\n\n\n";
    -    unset($command);
    -    $output .= "###### Active processes (pstree) ######\n";
    -    $command = sysCmd('pstree');
    -    $output .= implode("\n", $command)."\n\n\n";
    -    unset($command);
    -    $output .= "###### Audio backend ######\n";
    -    $output .=  file_get_contents('/proc/asound/version');
    -    $output .= "\n";
    -    $output .= "Card list: (/proc/asound/cards)\n";
    -    $output .= "--------------------------------------------------\n";
    -    $output .=  file_get_contents('/proc/asound/cards');
    -    $output .= "\n";
    -    $output .= "\n";
    -    $output .= "PLAYBACK devices: (aplay -l)\n";
    -    $output .= "--------------------------------------------------\n";
    -    $output .= implode("\n", sysCmd('aplay -l'));
    -    $output .= "\n";
    -    $output .= "\n";
    -    $output .= "\n";
    -    $output .= "PCM devices: (aplay -L)\n";
    -    $output .= "--------------------------------------------------\n";
    -    $output .= implode("\n", sysCmd('aplay -L'));
    -    $output .= "\n";
    -    $output .= "\n";
    -    $output .= "\n";
    -    $output .= "Simple mixer controls: (amixer scontrols)\n";
    -    $output .= "--------------------------------------------------\n";
    -    foreach ($acards as $card) {
    -        $output .= "card ".$card.":\n";
    -        $command = implode("\n", sysCmd('amixer -c '.$card.' scontrols'));
    -        if (!empty($command)) { $output .= $command; } else { $output .= 'no mixer scontrols'; }
    -        unset($command);
    -        $output .= "\n\n";
    -    }
    -    $output .= "\n";
    -    $output .= "Mixer controls: (amixer controls)\n";
    -    $output .= "--------------------------------------------------\n";
    -    foreach ($acards as $card) {
    -        $output .= "card ".$card.":\n";
    -        $command = implode("\n", sysCmd('amixer -c '.$card.' controls'));
    -        if (!empty($command)) { $output .= $command; } else { $output .= 'no mixer controls'; }
    -        unset($command);
    -        $output .= "\n\n";
    -    }
    -    $output .= "\n";
    -    $output .= "MPD audio outputs status: (mpc outputs)\n";
    -    $output .= "--------------------------------------------------\n";
    -    $command = implode("\n", sysCmd('mpc outputs'));
    -    $output .= $command;
    -    unset($command);
    -    $output .= "\n\n";
    -    // TODO: better collect stream info
    -    // $output .= "ALSA interface #0: (/proc/asound/card0/pcm0p/info)\n";
    -    // $output .= "--------------------------------------------------\n";
    -    // $output .=  file_get_contents('/proc/asound/card0/pcm0p/info');
    -    // $output .= "\n";
    -    // $output .= "ALSA interface #1: (/proc/asound/card1/pcm0p/info)\n";
    -    // $output .= "--------------------------------------------------\n";
    -    // $output .=  file_get_contents('/proc/asound/card1/pcm0p/info');
    -    // $output .= "\n";
    -    // $output .= "interface #0 stream status: (/proc/asound/card0/stream0)\n";
    -    // $output .= "--------------------------------------------------------\n";
    -    // $streaminfo = file_get_contents('/proc/asound/card0/stream0');
    -    // if (empty($streaminfo)) {
    -    // $output .= "no stream present\n";
    -    // } else {
    -    // $output .= $streaminfo;
    -    // }
    -    // $output .= "\n";
    -    // $output .= "interface #1 stream status: (/proc/asound/card1/stream0)\n";
    -    // $output .= "--------------------------------------------------------\n";
    -    // $streaminfo = file_get_contents('/proc/asound/card1/stream0');
    -    // if (empty($streaminfo)) {
    -    // $output .= "no stream present\n";
    -    // } else {
    -    // $output .= $streaminfo;
    -    // }
    -    $output .= "\n";
    -    $output .= "###### mpd.conf ######\n";
    -    $output .= file_get_contents('/etc/mpd.conf');
    -    $output .= "\n";
    -    $output .= "###### Kernel optimization parameters ######\n";
    -    $output .= "hardware platform:\t".$redis->get('hwplatform')."\n";
    -    $output .= "current orionprofile:\t".$redis->get('orionprofile')."\n";
    -    $output .= "\n\n";
    -    //         $output .=  "kernel scheduler for mmcblk0:\t\t".((empty(file_get_contents('/sys/block/mmcblk0/queue/scheduler'))) ? "\n" : file_get_contents('/sys/block/mmcblk0/queue/scheduler'));
    -    $output .=  "kernel scheduler for mmcblk0:\t\t\t".file_get_contents('/sys/block/mmcblk0/queue/scheduler');
    -    $output .=  "/proc/sys/vm/swappiness:\t\t\t".file_get_contents('/proc/sys/vm/swappiness');
    -    $output .=  "/proc/sys/kernel/sched_latency_ns:\t\t".file_get_contents('/proc/sys/kernel/sched_latency_ns');
    -    $output .=  "/proc/sys/kernel/sched_rt_period_us:\t\t".file_get_contents('/proc/sys/kernel/sched_rt_period_us');
    -    $output .=  "/proc/sys/kernel/sched_rt_runtime_us:\t\t".file_get_contents('/proc/sys/kernel/sched_rt_runtime_us');
    -    $output .=  "/proc/sys/kernel/sched_autogroup_enabled:\t".file_get_contents('/proc/sys/kernel/sched_autogroup_enabled');
    -    $output .=  "/proc/sys/kernel/sched_rr_timeslice_ms:\t\t".file_get_contents('/proc/sys/kernel/sched_rr_timeslice_ms');
    -    $output .=  "/proc/sys/kernel/sched_min_granularity_ns:\t".file_get_contents('/proc/sys/kernel/sched_min_granularity_ns');
    -    $output .=  "/proc/sys/kernel/sched_wakeup_granularity_ns:\t".file_get_contents('/proc/sys/kernel/sched_wakeup_granularity_ns');
    -    $output .= "\n";
    -    $output .= "\n";
    -    $output .= "###### Kernel module snd_usb_audio settings ######\n";
    -    $command = sysCmd('systool -v -m snd_usb_audio');
    -    $output .= implode("\n", $command)."\n\n";
    -    unset($command);
    -    $output .= "###### Systemd active startup scripts (ls -lah /etc/systemd/system/multi-user.target.wants/) ######\n";
    -    $command = sysCmd('ls -lah /etc/systemd/system/multi-user.target.wants/');
    -    $output .= implode("\n", $command)."\n\n\n";
    -    $output .= "###### Filesystem mounts ######\n";
    -    $output .=  file_get_contents('/proc/mounts')."\n\n";
    -    $output .= "###### Filesystem mounts - free space (df -h) ######\n";
    -    $command = sysCmd('df -h');
    -    $output .= implode("\n", $command)."\n\n\n";
    -    unset($command);
    -    $output .= "###### Loaded kernel modules ######\n";
    -    $command = sysCmd('lsmod');
    -    $output .= implode("\n", $command)."\n\n\n";
    -    unset($command);
    -    $output .= "###### Network listening sockets (netstat -lnp) ######\n";
    -    $command = sysCmd('netstat -lnp');
    -    $output .= implode("\n", $command)."\n\n\n";
    -    unset($command);
    -    $output .= "###### Network established sockets (netstat -np) ######\n";
    -    $command = sysCmd('netstat -np');
    -    $output .= implode("\n", $command)."\n\n\n";
    -    unset($command);
    -    $output .= "###### Network interfaces (ip addr) ######\n";
    -    $command = sysCmd('ip addr');
    -    $output .= implode("\n", $command)."\n\n\n";
    -    unset($command);
    -    $output .= "###### Network interfaces (ifconfig) ######\n";
    -    $command = sysCmd('ifconfig');
    -    $output .= implode("\n", $command)."\n\n";
    -    unset($command);
    -    $output .= "###### Network netctl profiles (netctl list) ######\n";
    -    $command = sysCmd('netctl list');
    -    $output .= implode("\n", $command)."\n\n";
    -    unset($command);
    -    $command = sysCmd("netctl list | cut -d ' ' -f 2,3");
    -    foreach ($command as $interface) {
    -        $command = sysCmd('netctl status '.trim($interface));
    -        $output .= $interface." netctl status\n";
    -        $output .= "--------------------------------------------------------\n";
    -        $output .= implode("\n",$command)."\n\n";
    -        $output .= $interface." netctl profile\n";
    -        $output .= "--------------------------------------------------------\n";
    -        $command = sysCmd('cat /etc/netctl/'.trim($interface));
    -        $output .= implode("\n", $command)."\n\n";
    -    }
    -    $output .= "\n";
    -    $output .= "###### Network wifi status (iwconfig) ######\n";
    -    $command = sysCmd('iwconfig');
    -    $output .= implode("\n", $command)."\n\n";
    -    unset($command);
    -    // $output .= "###### Network wifi site-scan (iwlist scan) ######\n";
    -    // $command = sysCmd('iwlist scan');
    -    // $output .= implode("\n",$command)."\n\n";
    -    // unset($command);
    -    $output .= "###### Kernel status (dmesg) ######\n";
    -    $command = sysCmd('dmesg');
    -    $output .= implode("\n", $command)."\n\n";
    -    unset($command);
    -    $output .= "###### PHP backend ######\n";
    -    $output .= "php version:\t".phpversion()."\n";
    -    $output .= "debug level:\t".$redis->get('debug')."\n";
    -    $output .= "\n";
    -    $output .= "\n";
    -    // $output .= "###### SESSION ######\n";
    -    // $output .= "\n";
    -    // $output .= "STATUS:\t\t".session_status()."\n";
    -    // $output .= "ID:\t\t".session_id()."\n";
    -    // $output .= "SAVE PATH:\t".session_save_path()."\n";
    -    // $output .= "\n";
    -    // $output .= "\n";
    -    // $output .= "###### SESSION DATA ######\n";
    -    // $output .= "\n";
    -    // $output .= print_r($_SESSION);
    -    $output .= "Debug data collected in ".round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']),3)." seconds. ";
    -    // $output .= "\n";
    -    // $output .= "\n";
    -    return $output;
    -}
    -$redis->set('debugdata', debug_data($redis));
    -$redis->close();
    +#!/usr/bin/php
    +.
    + *
    + *  file: command/debug_collector
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +ini_set('display_errors', '1');
    +ini_set('error_reporting', -1);
    +ini_set('error_log', '/var/log/runeaudio/debug_collector.log');
    +include('/var/www/app/libs/runeaudio.php');
    +// Connect to Redis backend
    +$redis = new Redis();
    +$redis->connect('/tmp/redis.sock');
    +function debug_data($redis)
    +{
    +    $acards = sysCmd("cat /proc/asound/cards | grep : | cut -d ' ' -f 2");
    +    $output = "\n";
    +    $output .= "###### System info ######\n";
    +    $output .=  file_get_contents('/proc/version');
    +    $output .= "\n";
    +    $output .=  "system time:\t".implode('\n', sysCmd('date'));
    +    $output .= "\n";
    +    $output .= "\n";
    +    $output .=  "system load:\t".file_get_contents('/proc/loadavg');
    +    $output .= "\n";
    +    $output .= "HW platform:\t".$redis->get('hwplatform')." (".$redis->get('hwplatformid').")\n";
    +    $output .= "\n";
    +    $output .= "playerID:\t".$redis->get('playerid')."\n";
    +    $output .= "\n";
    +    $output .= "\n";
    +    $output .= "###### System load statistics (procinfo -H) ######\n";
    +    $command = sysCmd('procinfo -H');
    +    $output .= implode("\n", $command)."\n\n\n";
    +    unset($command);
    +    $output .= "###### Active processes (pstree) ######\n";
    +    $command = sysCmd('pstree');
    +    $output .= implode("\n", $command)."\n\n\n";
    +    unset($command);
    +    $output .= "###### Audio backend ######\n";
    +    $output .=  file_get_contents('/proc/asound/version');
    +    $output .= "\n";
    +    $output .= "Card list: (/proc/asound/cards)\n";
    +    $output .= "--------------------------------------------------\n";
    +    $output .=  file_get_contents('/proc/asound/cards');
    +    $output .= "\n";
    +    $output .= "\n";
    +    $output .= "PLAYBACK devices: (aplay -l)\n";
    +    $output .= "--------------------------------------------------\n";
    +    $output .= implode("\n", sysCmd('aplay -l'));
    +    $output .= "\n";
    +    $output .= "\n";
    +    $output .= "\n";
    +    $output .= "PCM devices: (aplay -L)\n";
    +    $output .= "--------------------------------------------------\n";
    +    $output .= implode("\n", sysCmd('aplay -L'));
    +    $output .= "\n";
    +    $output .= "\n";
    +    $output .= "\n";
    +    $output .= "Simple mixer controls: (amixer scontrols)\n";
    +    $output .= "--------------------------------------------------\n";
    +    foreach ($acards as $card) {
    +        $output .= "card ".$card.":\n";
    +        $command = implode("\n", sysCmd('amixer -c '.$card.' scontrols'));
    +        if (!empty($command)) { $output .= $command; } else { $output .= 'no mixer scontrols'; }
    +        unset($command);
    +        $output .= "\n\n";
    +    }
    +    $output .= "\n";
    +    $output .= "Mixer controls: (amixer controls)\n";
    +    $output .= "--------------------------------------------------\n";
    +    foreach ($acards as $card) {
    +        $output .= "card ".$card.":\n";
    +        $command = implode("\n", sysCmd('amixer -c '.$card.' controls'));
    +        if (!empty($command)) { $output .= $command; } else { $output .= 'no mixer controls'; }
    +        unset($command);
    +        $output .= "\n\n";
    +    }
    +    $output .= "\n";
    +    $output .= "MPD audio outputs status: (mpc outputs)\n";
    +    $output .= "--------------------------------------------------\n";
    +    $command = implode("\n", sysCmd('mpc outputs'));
    +    $output .= $command;
    +    unset($command);
    +    $output .= "\n\n";
    +    // TODO: better collect stream info
    +    // $output .= "ALSA interface #0: (/proc/asound/card0/pcm0p/info)\n";
    +    // $output .= "--------------------------------------------------\n";
    +    // $output .=  file_get_contents('/proc/asound/card0/pcm0p/info');
    +    // $output .= "\n";
    +    // $output .= "ALSA interface #1: (/proc/asound/card1/pcm0p/info)\n";
    +    // $output .= "--------------------------------------------------\n";
    +    // $output .=  file_get_contents('/proc/asound/card1/pcm0p/info');
    +    // $output .= "\n";
    +    // $output .= "interface #0 stream status: (/proc/asound/card0/stream0)\n";
    +    // $output .= "--------------------------------------------------------\n";
    +    // $streaminfo = file_get_contents('/proc/asound/card0/stream0');
    +    // if (empty($streaminfo)) {
    +    // $output .= "no stream present\n";
    +    // } else {
    +    // $output .= $streaminfo;
    +    // }
    +    // $output .= "\n";
    +    // $output .= "interface #1 stream status: (/proc/asound/card1/stream0)\n";
    +    // $output .= "--------------------------------------------------------\n";
    +    // $streaminfo = file_get_contents('/proc/asound/card1/stream0');
    +    // if (empty($streaminfo)) {
    +    // $output .= "no stream present\n";
    +    // } else {
    +    // $output .= $streaminfo;
    +    // }
    +    $output .= "\n";
    +    $output .= "###### mpd.conf ######\n";
    +    $output .= file_get_contents('/etc/mpd.conf');
    +    $output .= "\n";
    +    $output .= "###### Kernel optimization parameters ######\n";
    +    $output .= "hardware platform:\t".$redis->get('hwplatform')."\n";
    +    $output .= "current orionprofile:\t".$redis->get('orionprofile')."\n";
    +    $output .= "\n\n";
    +    //         $output .=  "kernel scheduler for mmcblk0:\t\t".((empty(file_get_contents('/sys/block/mmcblk0/queue/scheduler'))) ? "\n" : file_get_contents('/sys/block/mmcblk0/queue/scheduler'));
    +    $output .=  "kernel scheduler for mmcblk0:\t\t\t".file_get_contents('/sys/block/mmcblk0/queue/scheduler');
    +    $output .=  "/proc/sys/vm/swappiness:\t\t\t".file_get_contents('/proc/sys/vm/swappiness');
    +    $output .=  "/proc/sys/kernel/sched_latency_ns:\t\t".file_get_contents('/proc/sys/kernel/sched_latency_ns');
    +    $output .=  "/proc/sys/kernel/sched_rt_period_us:\t\t".file_get_contents('/proc/sys/kernel/sched_rt_period_us');
    +    $output .=  "/proc/sys/kernel/sched_rt_runtime_us:\t\t".file_get_contents('/proc/sys/kernel/sched_rt_runtime_us');
    +    $output .=  "/proc/sys/kernel/sched_autogroup_enabled:\t".file_get_contents('/proc/sys/kernel/sched_autogroup_enabled');
    +    $output .=  "/proc/sys/kernel/sched_rr_timeslice_ms:\t\t".file_get_contents('/proc/sys/kernel/sched_rr_timeslice_ms');
    +    $output .=  "/proc/sys/kernel/sched_min_granularity_ns:\t".file_get_contents('/proc/sys/kernel/sched_min_granularity_ns');
    +    $output .=  "/proc/sys/kernel/sched_wakeup_granularity_ns:\t".file_get_contents('/proc/sys/kernel/sched_wakeup_granularity_ns');
    +    $output .= "\n";
    +    $output .= "\n";
    +    $output .= "###### Kernel module snd_usb_audio settings ######\n";
    +    $command = sysCmd('systool -v -m snd_usb_audio');
    +    $output .= implode("\n", $command)."\n\n";
    +    unset($command);
    +    $output .= "###### Systemd active startup scripts (ls -lah /etc/systemd/system/multi-user.target.wants/) ######\n";
    +    $command = sysCmd('ls -lah /etc/systemd/system/multi-user.target.wants/');
    +    $output .= implode("\n", $command)."\n\n\n";
    +    $output .= "###### Filesystem mounts ######\n";
    +    $output .=  file_get_contents('/proc/mounts')."\n\n";
    +    $output .= "###### Filesystem mounts - free space (df -h) ######\n";
    +    $command = sysCmd('df -h');
    +    $output .= implode("\n", $command)."\n\n\n";
    +    unset($command);
    +    $output .= "###### Loaded kernel modules ######\n";
    +    $command = sysCmd('lsmod');
    +    $output .= implode("\n", $command)."\n\n\n";
    +    unset($command);
    +    $output .= "###### Network listening sockets (netstat -lnp) ######\n";
    +    $command = sysCmd('netstat -lnp');
    +    $output .= implode("\n", $command)."\n\n\n";
    +    unset($command);
    +    $output .= "###### Network established sockets (netstat -np) ######\n";
    +    $command = sysCmd('netstat -np');
    +    $output .= implode("\n", $command)."\n\n\n";
    +    unset($command);
    +    $output .= "###### Network interfaces (ip addr) ######\n";
    +    $command = sysCmd('ip addr');
    +    $output .= implode("\n", $command)."\n\n\n";
    +    unset($command);
    +    $output .= "###### Network interfaces (ifconfig) ######\n";
    +    $command = sysCmd('ifconfig');
    +    $output .= implode("\n", $command)."\n\n";
    +    unset($command);
    +    $output .= "###### Network netctl profiles (netctl list) ######\n";
    +    $command = sysCmd('netctl list');
    +    $output .= implode("\n", $command)."\n\n";
    +    unset($command);
    +    $command = sysCmd("netctl list | cut -d ' ' -f 2,3");
    +    foreach ($command as $interface) {
    +        $command = sysCmd('netctl status '.trim($interface));
    +        $output .= $interface." netctl status\n";
    +        $output .= "--------------------------------------------------------\n";
    +        $output .= implode("\n",$command)."\n\n";
    +        $output .= $interface." netctl profile\n";
    +        $output .= "--------------------------------------------------------\n";
    +        $command = sysCmd('cat /etc/netctl/'.trim($interface));
    +        $output .= implode("\n", $command)."\n\n";
    +    }
    +    $output .= "\n";
    +    $output .= "###### Network wifi status (iwconfig) ######\n";
    +    $command = sysCmd('iwconfig');
    +    $output .= implode("\n", $command)."\n\n";
    +    unset($command);
    +    // $output .= "###### Network wifi site-scan (iwlist scan) ######\n";
    +    // $command = sysCmd('iwlist scan');
    +    // $output .= implode("\n",$command)."\n\n";
    +    // unset($command);
    +    $output .= "###### Kernel status (dmesg) ######\n";
    +    $command = sysCmd('dmesg');
    +    $output .= implode("\n", $command)."\n\n";
    +    unset($command);
    +    $output .= "###### PHP backend ######\n";
    +    $output .= "php version:\t".phpversion()."\n";
    +    $output .= "debug level:\t".$redis->get('debug')."\n";
    +    $output .= "\n";
    +    $output .= "\n";
    +    // $output .= "###### SESSION ######\n";
    +    // $output .= "\n";
    +    // $output .= "STATUS:\t\t".session_status()."\n";
    +    // $output .= "ID:\t\t".session_id()."\n";
    +    // $output .= "SAVE PATH:\t".session_save_path()."\n";
    +    // $output .= "\n";
    +    // $output .= "\n";
    +    // $output .= "###### SESSION DATA ######\n";
    +    // $output .= "\n";
    +    // $output .= print_r($_SESSION);
    +    $output .= "Debug data collected in ".round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']),3)." seconds. ";
    +    // $output .= "\n";
    +    // $output .= "\n";
    +    return $output;
    +}
    +$redis->set('debugdata', debug_data($redis));
    +$redis->close();
    diff --git a/command/info.php b/command/info.php
    index 61ace196..968c8df7 100755
    --- a/command/info.php
    +++ b/command/info.php
    @@ -1,2 +1,3 @@
     
    \ No newline at end of file
    diff --git a/command/refresh_ao b/command/refresh_ao
    index b7592a61..e3476f28 100755
    --- a/command/refresh_ao
    +++ b/command/refresh_ao
    @@ -1,61 +1,61 @@
    -#!/usr/bin/php
    -.
    - *
    - *  file: command/refresh_ao
    - *  version: 1.3
    - *  coder: Simone De Gregori
    - *
    - */
    -// common include
    -ini_set('display_errors', '1');
    -ini_set('error_reporting', -1);
    -ini_set('error_log', '/var/log/runeaudio/refresh_ao.log');
    -// Connect to Redis backend
    -$redis = new Redis();
    -$redis->connect('/tmp/redis.sock');
    -include('/var/www/app/libs/runeaudio.php');
    -$lock = $redis->get('lock_refresh_ao');
    -runelog('lock status ', $lock);
    -// startup
    -runelog('--------------------------- start Audio Outputs refresh ---------------------------');
    -// refesh audio hardware status (soundcards and MPD config)
    -if ($lock === '0') {
    -    $redis->set('lock_refresh_ao', '1');
    -    wrk_mpdconf($redis, 'refresh');
    -    ui_notify('Audio Output', 'configuration changed.');    
    -    sleep(1);
    -    $redis->set('lock_refresh_ao', '0');
    -} else {
    -    echo "LOCKED!";
    -}
    -// startup
    -runelog('-------------------------- #finish Audio Outputs refresh# --------------------------');
    -runelog('lock status ', $redis->get('lock_refresh_ao'));
    -// close Redis connection
    -$redis->close();
    +#!/usr/bin/php
    +.
    + *
    + *  file: command/refresh_ao
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +ini_set('display_errors', '1');
    +ini_set('error_reporting', -1);
    +ini_set('error_log', '/var/log/runeaudio/refresh_ao.log');
    +// Connect to Redis backend
    +$redis = new Redis();
    +$redis->connect('/tmp/redis.sock');
    +include('/var/www/app/libs/runeaudio.php');
    +$lock = $redis->get('lock_refresh_ao');
    +runelog('lock status ', $lock);
    +// startup
    +runelog('--------------------------- start Audio Outputs refresh ---------------------------');
    +// refesh audio hardware status (soundcards and MPD config)
    +if ($lock === '0') {
    +    $redis->set('lock_refresh_ao', '1');
    +    wrk_mpdconf($redis, 'refresh');
    +    ui_notify('Audio Output', 'configuration changed.');    
    +    sleep(1);
    +    $redis->set('lock_refresh_ao', '0');
    +} else {
    +    echo "LOCKED!";
    +}
    +// startup
    +runelog('-------------------------- #finish Audio Outputs refresh# --------------------------');
    +runelog('lock status ', $redis->get('lock_refresh_ao'));
    +// close Redis connection
    +$redis->close();
    diff --git a/command/refresh_nics b/command/refresh_nics
    old mode 100755
    new mode 100644
    index a540fa1b..4d292872
    --- a/command/refresh_nics
    +++ b/command/refresh_nics
    @@ -1,214 +1,177 @@
    -#!/usr/bin/php
    -.
    - *
    - *  file: command/refresh_nics
    - *  version: 1.3
    - *  coder: Simone De Gregori
    - *
    - */
    -// common include
    -ini_set('display_errors', '1');
    -// ini_set('error_reporting', -1);
    -ini_set('error_log', '/var/log/runeaudio/refresh_nics.log');
    -// Connect to Redis backend
    -$redis = new Redis();
    -$redis->connect('/tmp/redis.sock');
    -include('/var/www/app/libs/runeaudio.php');
    -include '/var/www/app/libs/vendor/ziegler/iwlist-parser.class.php';
    -// startup - lock the scan system
    -runelog('--------------------------- lock the scan system ---------------------------');
    -$redis->Set('lock_wifiscan', 1);
    -// random delay
    -$sleep = rand(1000000, 2000000);
    -usleep($sleep);
    -runelog('random delay: ', $sleep);
    -// startup - collect system data
    -runelog('--------------------------- collect system data ---------------------------');
    -$excluded_nics = array('ifb0', 'ifb1');
    -$active_nics = sysCmd("ifconfig | grep UP | cut -d ':' -f 1");
    -$detected_nics = sysCmd("ip addr |grep \"BROADCAST,\" |cut -d':' -f1-2 |cut -d' ' -f2");
    -$detected_nics = array_diff($detected_nics, $excluded_nics);
    -$inactive_nics = array_diff($detected_nics, $active_nics);
    -$configured_nics = array_map("trim", sysCmd("netctl list | cut -d ' ' -f 2,3"));
    -$removed_nics = array_diff($configured_nics, $detected_nics);
    -// debug
    -runelog('########### active_nics ########### ', $active_nics);
    -// print_r($active_nics);
    -// print_r($excluded_nics);
    -runelog('########### excluded_nics ########### ', $excluded_nics);
    -// print_r($detected_nics);
    -runelog('########### detected_nics ########### ', $detected_nics);
    -// print_r(array_diff($detected_nics,$active_nics));
    -// print_r($configured_nics);
    -runelog('########### configured_nics ########### ', $configured_nics);
    -// print_r(array_diff($configured_nics,$detected_nics));
    -// print_r($removed_nics);
    -runelog('########### removed_nics ########### ', $removed_nics);
    -// handle inactive nics
    -runelog('--------------------------- handle inactive nics ---------------------------');
    -if (!empty($inactive_nics)) {
    -    foreach($inactive_nics as $nic) {
    -        $wireless = sysCmd("iwconfig ".$nic." | grep 'no wireless'");
    -        if (empty($wireless)) {
    -            // activate link
    -            runelog('########### activate currently inactive wifi nic: '.$nic);
    -            sysCmd('ip link set '.$nic.' up');
    -            // start wpa_supplicant
    -            runelog('########### start wpa_supplicant for wifi nic: '.$nic);
    -            sysCmd('systemctl start wpa_supplicant@'.$nic);
    -            // check if the nic is unconfigured (= without an existing netctl profile)
    -            if (empty(sysCmd('ls /etc/netctl/ | grep '.$nic))) {
    -                // create a new standard (dhcp) wifi netctl profile
    -                $args = new stdClass;
    -                $args->name = $nic;
    -                $args->wireless = '1';
    -                $args->dhcp = '1';
    -                wrk_netconfig($redis, 'writecfg', $args, 1);
    -            }
    -            // check wpa_supplicant status
    -            // if (empty(sysCmd('ls /etc/systemd/system/multi-user.target.wants/ | grep wpa_supplicant@'.$nic))) {
    -                // enable wpa_supplicant
    -                // runelog('########### enable wpa_supplicant for wifi nic: '.$nic);
    -                // sysCmd('systemctl enable wpa_supplicant@'.$nic);
    -            // }
    -            // enable netctl profile
    -            // if (empty(sysCmd('ls /etc/systemd/system/multi-user.target.wants/ | grep netctl@'.$nic))) {
    -                // runelog('########### enable netctl for wifi nic: '.$nic);
    -                // sysCmd('netctl enable '.$nic);
    -            // }
    -        }
    -    }
    -}
    -unset($nic);
    -runelog('--------------------------- handle active nics ---------------------------');
    -// check if there is a stored profile
    -// refesh visible wifi networks.
    -$iw = new iwlist_parser;
    -// debug
    -// print_r($iw->parseScanDev( 'wlan0' ));
    -wrk_netconfig($redis, 'setnics');
    -$nics = wrk_netconfig($redis, 'getnics');
    -// debug
    -// print_r($nics);
    -foreach ($nics as $nic => $nicdetail) {
    -    if ($nicdetail->wireless === 1) {
    -        // check and activate wpa_supplicant
    -        if (empty(sysCmd("ps x | grep '\-c\/etc\/wpa_supplicant\/wpa_supplicant.conf'"))) {
    -            runelog('########### start wpa_supplicant for nic: '.$nic);
    -            sysCmd('systemctl start wpa_supplicant@'.$nic);
    -            }
    -        // check and activate netctl@nic profile
    -        if (empty(sysCmd('netctl list | grep '.$nic.' | grep \*'))) {
    -            runelog('########### start netctl profile for nic: '.$nic);
    -                if(!empty(sysCmd('netctl start '.$nic))) {
    -                    runelog('########### ZERO condition reached for nic: '.$nic.' restart link and wpa_supplicant helper');
    -                    sysCmd('ip link set '.$nic.' up');
    -                    sysCmd('systemctl start wpa_supplicant@'.$nic);
    -                }
    -            }
    -        // temp
    -        $scan = $iw->parseScanDev( $nic );
    -        $redis->set('wlans',json_encode($scan));
    -        // debug
    -        // print_r($scan);
    -        foreach ($scan as $nicname => $nicscan) {
    -            foreach ($nicscan as $count => $wlan) {
    -                foreach ($wlan as $key => $value) {
    -                    $wlans[$count]['nic'] = $nicname;
    -                    if ($key === 'ESSID') {
    -                        $wlans[$count][$key] = $value;
    -                        if ($nicdetail->currentssid === $value) {
    -                            $wlans[$count]['connected'] = 1;
    -                        } else {
    -                            $wlans[$count]['connected'] = 0;
    -                        }
    -                    }
    -                    if ($key === 'Encryption key') $wlans[$count]['encryption'] = $value;
    -                }
    -            }
    -        } 
    -        // debug
    -        // print_r($wlans);
    -        // render wlans channel
    -        // $wlans = json_encode($wlans);
    -        $wlans_profiles = $redis->hGetAll('wlan_profiles');
    -        $i = count($wlans);
    -        foreach ($wlans_profiles as $profile => $details) {
    -            $i++;
    -            $details = json_decode($details);
    -            $stored_profiles[$i]['nic'] = $details->nic;
    -            if ($details->encryption !== 'open') {
    -                $stored_profiles[$i]['encryption'] = 'on';
    -            } else {
    -                $stored_profiles[$i]['encryption'] = 'off';
    -            }
    -            $stored_profiles[$i]['ESSID'] = $details->ssid;
    -            if ($nicdetail->currentssid === $details->ssid) {
    -                $stored_profiles[$i]['connected'] = 1;
    -            } else {
    -                $stored_profiles[$i]['connected'] = 0;
    -            }
    -            $stored_profiles[$i]['storedprofile'] = 1;
    -            $wlans[] = $stored_profiles[$i];
    -        }
    -        $wlans = json_encode($wlans);
    -        // $wlans = json_encode(array_merge($wlans, $stored_profiles));
    -        ui_render('wlans', $wlans);
    -        runelog('wlans response: ', $wlans);
    -    }
    -}
    -// send nics status to RuneUI
    -ui_render('nics', json_encode($nics));
    -unset($nic);
    -// handle removed nics
    -runelog('--------------------------- handle removed nics ---------------------------');
    -if (!empty($removed_nics)) {
    -    foreach($removed_nics as $nic) {
    -        // check for orphan netctl startup profiles
    -        if (!empty(sysCmd('ls /etc/systemd/system/multi-user.target.wants/ | grep '.$nic))) {
    -            // disable orphan netctl profiles
    -            // sysCmd("rm -f /etc/systemd/system/multi-user.target.wants/netctl@".$nic.".service')");
    -            // sysCmd("systemctl disable netctl@".$nic.".service')");
    -            runelog('########### disable netctl profiles for nic: '.$nic);
    -            sysCmd('netctl disable '.$nic);
    -        }
    -        // check wpa_supplicant startup status
    -        // if (!empty(sysCmd('ls /etc/systemd/system/multi-user.target.wants/ | grep wpa_supplicant@'.$nic))) {
    -            // disable wpa_supplicant autostart 
    -            // runelog('########### disable wpa_supplicant autostart for nic: '.$nic);
    -            // sysCmd("rm -f /etc/systemd/system/multi-user.target.wants/wpa_supplicant@".$nic.".service')");
    -            // sysCmd('systemctl disable wpa_supplicant@'.$nic.'.service');
    -        // }
    -    }
    -}
    -// end - unlock the scan system
    -runelog('--------------------------- unlock the scan system ---------------------------');
    -$redis->Set('lock_wifiscan', 0);
    -// colse Redis connection
    -$redis->close();
    +#!/usr/bin/php
    +.
    + *
    + *  file: command/refresh_nics
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +ini_set('display_errors', '1');
    +// ini_set('error_reporting', -1);
    +ini_set('error_log', '/var/log/runeaudio/refresh_nics.log');
    +// Connect to Redis backend
    +$redis = new Redis();
    +$redis->connect('/tmp/redis.sock');
    +include('/var/www/app/libs/runeaudio.php');
    +include '/var/www/app/libs/vendor/ziegler/iwlist-parser.class.php';
    +// startup - lock the scan system
    +runelog('--------------------------- lock the scan system ---------------------------');
    +$redis->Set('lock_wifiscan', 1);
    +// random delay
    +$sleep = rand(1000000, 2000000);
    +usleep($sleep);
    +runelog('random delay: ', $sleep);
    +// startup - collect system data
    +runelog('--------------------------- collect system data ---------------------------');
    +$excluded_nics = array('ifb0', 'ifb1', 'p2p0', 'bridge');
    +$active_nics = sysCmd("ifconfig | grep UP | cut -d ':' -f 1");
    +$detected_nics = sysCmd("ip addr |grep \"BROADCAST,\" |cut -d':' -f1-2 |cut -d' ' -f2");
    +$detected_nics = array_diff($detected_nics, $excluded_nics);
    +$inactive_nics = array_diff($detected_nics, $active_nics);
    +$configured_nics = array_map("trim", sysCmd("netctl list | cut -d ' ' -f 2,3"));
    +$removed_nics = array_diff($configured_nics, $detected_nics);
    +// debug
    +runelog('########### active_nics ########### ', $active_nics);
    +// print_r($active_nics);
    +// print_r($excluded_nics);
    +runelog('########### excluded_nics ########### ', $excluded_nics);
    +// print_r($detected_nics);
    +runelog('########### detected_nics ########### ', $detected_nics);
    +// print_r(array_diff($detected_nics,$active_nics));
    +// print_r($configured_nics);
    +runelog('########### configured_nics ########### ', $configured_nics);
    +// print_r(array_diff($configured_nics,$detected_nics));
    +// print_r($removed_nics);
    +runelog('########### removed_nics ########### ', $removed_nics);
    +// handle inactive nics
    +runelog('--------------------------- handle inactive nics ---------------------------');
    +unset($nic);
    +runelog('--------------------------- handle active nics ---------------------------');
    +// check if there is a stored profile
    +// refesh visible wifi networks.
    +$iw = new iwlist_parser;
    +wrk_netconfig($redis, 'setnics');
    +$nics = wrk_netconfig($redis, 'getnics');
    +foreach ($nics as $nic => $nicdetail) {
    +    if ($nicdetail->wireless === 1) {
    +        if (empty(sysCmd('ifconfig | grep '.$nic.''))) {
    +            runelog('########### restart link for nic: '.$nic);
    +                sysCmd('ip link set '.$nic.' up');
    +        }
    +        runelog('  try nic: '.$nic);
    +        
    +        // test if service is enabled
    +        sysCmd('systemctl enable netctl-auto@'.$nic.'.service');
    +        
    +        // get stored profiles
    +        $wlans_profiles = sysCmd("netctl-auto list | cut -c3-");
    +        $redis->set('stored_profiles',json_encode($wlans_profiles));
    +        // scan for ap in range
    +        $scan = $iw->parseScanDev( $nic );
    +        $tmp_wlans = array();
    +        $redis->set('wlans',json_encode($scan));
    +        foreach ($scan as $nicname => $nicscan) {
    +            runelog('    scan for nic: '.$nic.' nicname = '.$nicname);
    +            foreach ($nicscan as $count => $wlan) {
    +                runelog('      scan for nic: '.$nic.' AP = '.$count);
    +                if ($wlan['ESSID'] === "" OR array_search ($wlan['ESSID'], $tmp_wlans) !== false) {
    +                    runelog('        SSID already found');
    +                } else {
    +                    foreach ($wlan as $key => $value) {
    +                        $wlans[$count]['nic'] = $nicname;
    +                        runelog('        scan for nic: '.$nic.' = '.$key.' = '.$value);
    +                        if ($key === 'ESSID') {
    +                            $wlans[$count][$key] = $value;
    +                            $tmp_wlans[] = $value;
    +                            if ($nicdetail->currentssid === $value) {
    +                                $wlans[$count]['connected'] = 1;
    +                            } else {
    +                                $wlans[$count]['connected'] = 0;
    +                            }
    +                            foreach($wlans_profiles as $item){
    +                                if($item === $value){
    +                                    $wlans[$count]['storedprofile'] = 1;
    +                                    break;   // found it!
    +                                } else {
    +                                    $wlans[$count]['storedprofile'] = 0;
    +                                }
    +                            }
    +                        }
    +                        if ($key === 'Encryption key') $wlans[$count]['encryption'] = $value;
    +                        $wlans[$count]['origin'] = 'scan';
    +                    }
    +                }
    +            }
    +        }
    +
    +        // handle hidden but stored profiles
    +        foreach ($wlans_profiles as $item){
    +            if ((bool)strpos(serialize($wlans), $item) === false){
    +                $wlans[++$count]['nic'] = $nic;
    +                $wlans[$count]['ESSID'] = $item;
    +                if ($nicdetail->currentssid === $item) {
    +                    $wlans[$count]['connected'] = 1;
    +                } else {
    +                    $wlans[$count]['connected'] = 0;
    +                }
    +                $wlans[$count]['storedprofile'] = 1;
    +                $wlans[$count]['encryption'] = 'unknown';
    +                $wlans[$count]['origin'] = 'storage';
    +            }
    +        }
    +
    +        //runelog('wlans response: ', $wlans);
    +        $wlans = json_encode($wlans);
    +        runelog('wlans response(encoded): ', $wlans);
    +        ui_render('wlans', $wlans);
    +    } else {
    +        sysCmd('systemctl enable netctl-ifplugd@'.$nic.'.service');
    +    }
    +}
    +// send nics status to RuneUI
    +ui_render('nics', json_encode($nics));
    +unset($nic);
    +// handle removed nics
    +runelog('--------------------------- handle removed nics ---------------------------');
    +if (!empty($removed_nics)) {
    +    foreach($removed_nics as $nic) {
    +        // check for orphan netctl startup profiles
    +        if (!empty(sysCmd('ls /etc/systemd/system/multi-user.target.wants/ | grep '.$nic))) {
    +            runelog('########### disable netctl profiles for nic: '.$nic);
    +            sysCmd('systemctl disable netctl-ifplugd@'.$nic);
    +            sysCmd('systemctl disable netctl-auto@'.$nic);
    +        }
    +    }
    +}
    +// end - unlock the scan system
    +runelog('--------------------------- unlock the scan system ---------------------------');
    +$redis->Set('lock_wifiscan', 0);
    +// colse Redis connection
    +$redis->close();
    diff --git a/command/rune_PL_wrk b/command/rune_PL_wrk
    index b04e966d..e56c0960 100755
    --- a/command/rune_PL_wrk
    +++ b/command/rune_PL_wrk
    @@ -81,20 +81,12 @@ if (!$socket) {
                 $redis->set('pl_length', $status['playlistlength']);
                 // runelog('---------status data------------',$status);
                 $status = ui_status($socket, $status);
    -            // runelog('---------status data(2)------------',$status);
    +            runelog('---------status data(2)------------',$status);
                 // render Queue (push async)
    -            // if ($status['changed'] === 'playlist') {
    -            // $queue = new ui_renderQueue($socket);
    -            // $queue->start();
    -            // runelog('---------------- PLAYLIST RENDER ----------------');
    -            // }
    -            // CMediaFix
    -            if ($redis->get('cmediafix') === '1' && $status['state'] === 'play' ) {
    -                $status['lastbitdepth'] = $redis->get('lastbitdepth');
    -                    if ($redis->get('lastbitdepth') !== $status['audio']) {
    -                        sendMpdCommand($socket, 'cmediafix');
    -                    }
    -            }
    +            //if ($status['changed'] === 'playlist') {
    +            //    sysCmdAsync('ui_render_pl');
    +            //    runelog('---------------- PLAYLIST RENDER ----------------');
    +            //}
                 // Global Random
                 if (($redis->get('globalrandom') === '1') && ($redis->get('lastsongid') != $status['songid']) && ($redis->get('lock_globalrandom') === '0')) {
                     $addsong = new globalRandom($status);
    @@ -123,6 +115,7 @@ if (!$socket) {
                 runelog('rune_PL_wrk: close SPOP socket');
                 closeSpopSocket($socket);
             } elseif ($activePlayer === 'Airplay') {
    +            ui_render('playback', readShairportStatus('/run/shairport/now_playing'));
                 sleep(1);
                 $forceupdate = 1;
             } else {
    diff --git a/command/rune_SY_wrk b/command/rune_SY_wrk
    index 14e57e60..eb39a3d8 100755
    --- a/command/rune_SY_wrk
    +++ b/command/rune_SY_wrk
    @@ -39,6 +39,30 @@ error_reporting(E_ALL);
     ini_set('error_log', '/var/log/runeaudio/rune_SY_wrk.log');
     define('APP', '/srv/http/app/');
     include('/srv/http/app/libs/runeaudio.php');
    +
    +// if os updates are needed
    +if(file_exists('/srv/http/command/update_os')) 
    +{
    +    include '/srv/http/command/update_os';
    +    updateOS();
    +}
    +
    +// test if all services are enabled
    +$retval = sysCmd('systemctl is-enabled ntpd.service');
    +if ($retval[0] === 'disabled')
    +    sysCmd('systemctl enable ntpd.service');
    +$retval = sysCmd('systemctl is-enabled rune_PL_wrk.service');
    +if ($retval[0] === 'disabled')
    +    sysCmd('systemctl enable rune_PL_wrk.service');
    +$retval = sysCmd('systemctl is-enabled redis.service');
    +if ($retval[0] === 'disabled')
    +    sysCmd('systemctl enable redis.service');
    +$retval = sysCmd('systemctl is-enabled nginx.service');
    +if ($retval[0] === 'disabled')
    +    sysCmd('systemctl enable nginx.service');
    +$retval = sysCmd('systemctl is-enabled dhcpcd.service');
    +if ($retval[0] === 'disabled')
    +    sysCmd('systemctl enable dhcpcd.service');
     // stop php-fpm
     sysCmd('systemctl stop php-fpm');
     // setup /run dir
    @@ -54,6 +78,9 @@ if ($activePlayer === 'MPD') {
     } elseif ($activePlayer === 'Spotify') {
         // check MPD process
         sysCmd('pgrep -x spopd || systemctl start spopd');
    +} else {
    +    // reset activePlayer state to MPD (default)
    +    $redis->set('activePlayer', 'MPD');
     }
     // read registered HW architecture
     $arch_db = $redis->get('hwplatformid');
    @@ -77,6 +104,10 @@ if ($arch_db !== $arch) {
         // reset playerID if architectureID not match. This condition "fire" another first-run process
         $playerid_db = '';
     }
    +// check git branch
    +$gitbranch = sysCmd("cd /var/www/ ; git branch | grep \"*\" | cut -d ' ' -f 2");
    +runelog('GIT BRANCH: ', $gitbranch[0]);
    +$redis->hSet('git', 'branch', $gitbranch[0]);
     if ($playerid_db === '') {
         // RUNEAUDIO FIRST RUN PROCESS --- //
         runelog('>>>>>>RUNEAUDIO FIRST RUN PROCESS ---');
    @@ -124,8 +155,8 @@ if ($playerid_db === '') {
         // NTP sync
         $start2 = microtime(true);
         $firstlap = $start2-$start;
    -    runelog('NTP sync', $redis->get('ntpserver'));
    -    wrk_NTPsync($redis->get('ntpserver'));
    +    //runelog('NTP sync', $redis->get('ntpserver'));
    +    //wrk_NTPsync($redis->get('ntpserver'));
         $start3 = microtime(true);
         // check HOSTNAME << TODO: integrate in wrk_sysEnvCheck >>
         $hn = sysCmd('hostname');
    @@ -197,25 +228,7 @@ if ($redis->get('udevil') === '1') {
     // hashCFG('check_net',$redis);
     // check /etc/mpd.conf integrity
     // hashCFG('check_mpd',$redis);
    -// Cmediafix startup check
    -if ($redis->get('cmediafix') === '1') {
    -    // close palyer backend connection
    -    if ($activePlayer === 'MPD') {
    -        // open MPD connection
    -        $mpd = openMpdSocket('/run/mpd.sock');
    -        // send cmediafix command
    -        sendMpdCommand($mpd, 'cmediafix');
    -        // close MPD connection
    -        closeMpdSocket($mpd);
    -    } elseif ($activePlayer === 'Spotify') {
    -        // open SPOP connection
    -        $spop = openSpopSocket('localhost', 6602, 1);
    -        // send cmediafix command
    -        sendSpopCommand($mpd, 'cmediafix');
    -        // close SPOP connection
    -        closeSpopSocket($spop);
    -    }
    -}
    +
     // initialize OrionProfile
     runelog('env: SET KERNEL PROFILE',$redis->get('orionprofile'));
     sysCmd("/var/www/command/orion_optimize.sh ".$redis->get('orionprofile')." ".$redis->get('hwplatformid'));
    @@ -225,8 +238,7 @@ sysCmd('systemctl start php-fpm');
     if ($redis->get('dev') === '0') {
         // prime PHP OPCache
         runelog('prime PHP OPCache');
    -    // wrk_opcache('prime',$redis);
    -    sysCmdAsync("curl -s -X GET 'http://localhost/command/cachectl.php?action=prime'");
    +    sysCmdAsync("curl -s -X GET 'http://127.0.0.1/command/cachectl.php?action=prime'");
     }
     runelog('--- NORMAL STARTUP');
     // --- NORMAL STARTUP //
    @@ -400,6 +412,7 @@ $redis->pconnect('/tmp/redis.sock');
                         if (wrk_kernelswitch($redis,$job->args)) {
                             if ($job->args === 'linux-rune-rpi_3.12.13-rt21_wosa') {
                                 $redis->set('ao', 'snd_rpi_wsp_1');
    +                            $redis->set('i2smodule', 'none');
                                 $redis->set('orionprofile', 'OrionV2');
                             }
                             $notification = new stdClass();
    @@ -479,6 +492,7 @@ $redis->pconnect('/tmp/redis.sock');
                         }
                         if ($job->action === 'reset') {
                             wrk_mpdconf($redis, 'reset');
    +                        wrk_mpdconf($redis, 'restart');
                         }
                         if ($job->action === 'switchao') {
                             wrk_mpdconf($redis, 'switchao', $job->args, $jobID);
    @@ -494,7 +508,7 @@ $redis->pconnect('/tmp/redis.sock');
                         wrk_mpdconf($redis, 'stop');
                         // write mpd.conf file
                         $fh = fopen('/etc/mpd.conf', 'w');
    -                    fwrite($fh, $job['args']);
    +                    fwrite($fh, $job->args);
                         fclose($fh);
                         wrk_mpdconf($redis, 'start');
                         $redis->sRem('w_lock', $jobID);
    @@ -513,21 +527,35 @@ $redis->pconnect('/tmp/redis.sock');
                         // reset network configuration to default
                         if ($job->action === 'reset') {
                             wrk_netconfig($redis,'reset',$job->args);
    +                        $redis->sRem('w_lock', $jobID);
                         }
                         // write network configuration
                         if ($job->action === 'config') {
    -                        wrk_netconfig($redis, 'writecfg', $job->args);
    -                        sysCmd('/var/www/command/refresh_nics');
    +                        // send notfy to UI
    +                        ui_notify('Networkconfiguration', 'it takes some time .....');
    +                        $retval = wrk_netconfig($redis, 'writecfg', $job->args);
    +                        $redis->sRem('w_lock', $jobID);
    +                        // reboot if needed !!!
    +                        if ($retval === 'reboot') {
    +                            //$notification = new stdClass();
    +                            //$notification->title = 'Network config';
    +                            //$notification->text = 'Config changed successfully, reboot reqired';
    +                            //wrk_notify($redis, 'kernelswitch', $notification, $jobID);
    +                            sysCmd('/var/www/command/rune_shutdown');
    +                            sysCmd('systemctl reboot');
    +                            //$redis->sRem('w_lock', $jobID);
    +                        }
                         }
                         // refresh network configuration
                         if ($job->action === 'refresh') {
                             sysCmd('/var/www/command/refresh_nics');
    +                        $redis->sRem('w_lock', $jobID);
                         }
                         // manual network configuration
                         if ($job->action === 'manual') {
                             // wrk_netconfig($redis,'manual',$job->args);
    +                        $redis->sRem('w_lock', $jobID);
                         }
    -                    $redis->sRem('w_lock', $jobID);
                         break;
                     case 'ntpserver':
                         runelog('wrk_SY: ', $job->wrkcmd);
    @@ -540,18 +568,16 @@ $redis->pconnect('/tmp/redis.sock');
                         $redis->sAdd('w_lock', $jobID);
                         // Restart PHP service
                         if ($job->action === 'enable') {
    -                        wrk_opcache('enable');
    +                        wrk_opcache('enable', $redis);
                             runelog('PHP 5.5 OPcache enabled');
    -                        sysCmd('systemctl restart php-fpm');
    +                        sysCmdAsync('systemctl restart php-fpm', 5);
                             // wrk_opcache('forceprime');
    -                        $redis->set('opcache', 1);
                             // send notfy to UI
                             ui_notify_async('PHP OpCache', 'cache enabled', $jobID);
                         } else {
    -                        wrk_opcache('disable');
    +                        wrk_opcache('disable', $redis);
                             runelog('PHP 5.5 OPcache disabled');
    -                        sysCmd('systemctl restart php-fpm');
    -                        $redis->set('opcache', 0);
    +                        sysCmdAsync('systemctl restart php-fpm', 5);
                             // send notfy to UI
                             ui_notify_async('PHP OpCache', 'cache disabled', $jobID);
                         }
    @@ -713,36 +739,47 @@ $redis->pconnect('/tmp/redis.sock');
                         break;
                     case 'wificfg':
                         runelog('wrk_SY: ', $job->wrkcmd);
    -                    // inject random delay to avoid wifi scan overlapping
    -                    if ($job->action === 'scan') {
    -                        // random delay
    -                        $sleep = rand(1000000, 2000000);
    -                        usleep($sleep);
    -                        $lock = $redis->Get('lock_wifiscan');
    -                    }
                         $redis->sAdd('w_lock',$jobID);
                         // add profile
                         if ($job->action === 'add') {
                             runelog('wificfg: add profile for SSID: '.$job->args->ssid);
    +                        ui_notify_async('Adding WIFI-Profile', $job->args->ssid, $jobID);
                             wrk_wifiprofile($redis, 'add', $job->args);
    +                        ui_notify_async('Added WIFI-Profile', $job->args->ssid, $jobID);
                         }
                         if ($job->action === 'edit') {
                             runelog('wificfg: edit profile for SSID: '.$job->args->ssid);
    +                        ui_notify_async('Changing WIFI-Profile', $job->args->ssid, $jobID);
                             wrk_wifiprofile($redis, 'edit', $job->args);
    +                        ui_notify_async('Changed WIFI-Profile', $job->args->ssid, $jobID);
                         }
                         if ($job->action === 'delete') {
                             runelog('wificfg: delete profile for SSID: '.$job->args->ssid);
    +                        ui_notify_async('Deleting WIFI-Profile', $job->args->ssid, $jobID);
                             wrk_wifiprofile($redis, 'delete', $job->args);
    +                        ui_notify_async('Deleted WIFI-Profile', $job->args->ssid, $jobID);
                         }
                         if ($job->action === 'disconnect') {
                             runelog('wificfg: disconnect profile for SSID: '.$job->args->ssid);
    -                        wrk_wifiprofile($redis, 'disconnect');
    +                        ui_notify_async('Disconnecting from WIFI-Profile', $job->args->ssid, $jobID);
    +                        wrk_wifiprofile($redis, 'disconnect', $job->args);
    +                        ui_notify_async('Disconnected from WIFI-Profile', $job->args->ssid, $jobID);
                         }
                         if ($job->action === 'connect') {
                             runelog('wificfg: connect profile for SSID: '.$job->args->ssid);
    -                        wrk_wifiprofile($redis, 'connect');
    +                        ui_notify_async('Connecting to WIFI-Profile', $job->args->ssid, $jobID);
    +                        wrk_wifiprofile($redis, 'connect', $job->args);
    +                        ui_notify_async('Connected to WIFI-Profile', $job->args->ssid, $jobID);
                         }
    +                    // inject random delay to avoid wifi scan overlapping
                         if ($job->action === 'scan') {
    +                        // random delay
    +//                        $sleep = rand(1000000, 2000000);
    +//                        usleep($sleep);
    +//                        $lock = $redis->Get('lock_wifiscan');
    +                    }
    +                    if ($job->action === 'scan') {
    +                        $lock = $redis->Get('lock_wifiscan');
                             if ($lock !== '1') {
                                 runelog('wificfg: scan ');
                                 // refresh nics status
    diff --git a/command/rune_prio b/command/rune_prio
    index a95e2614..ba6958eb 100755
    --- a/command/rune_prio
    +++ b/command/rune_prio
    @@ -1,144 +1,144 @@
    -#!/bin/bash
    -#
    -#  Copyright (C) 2013-2014 RuneAudio Team
    -#  http://www.runeaudio.com
    -#
    -#  RuneUI
    -#  copyright (C) 2013-2014 – Andrea Coiutti (aka ACX) & Simone De Gregori (aka Orion)
    -#
    -#  RuneOS
    -#  copyright (C) 2013-2014 – Simone De Gregori (aka Orion) & Carmelo San Giovanni (aka Um3ggh1U)
    -#
    -#  RuneAudio website and logo
    -#  copyright (C) 2013-2014 – ACX webdesign (Andrea Coiutti)
    -#
    -#  This Program is free software; you can redistribute it and/or modify
    -#  it under the terms of the GNU General Public License as published by
    -#  the Free Software Foundation; either version 3, or (at your option)
    -#  any later version.
    -#
    -#  This Program is distributed in the hope that it will be useful,
    -#  but WITHOUT ANY WARRANTY; without even the implied warranty of
    -#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -#  GNU General Public License for more details.
    -#
    -#  You should have received a copy of the GNU General Public License
    -#  along with RuneAudio; see the file COPYING.  If not, see
    -#  .
    -# 
    -#  file: command/rune_prio
    -#  version: 1.3
    -#  coder: Simone De Gregori
    -#
    -#####################################
    -prio_nice () {
    -count=1
    -for pid in $(pgrep -w mpd);
    -do
    -    if ((count == 3))
    -    then
    -        echo "### Set priority for: mpd-player thread ###";
    -        renice -16 $pid;
    -    fi
    -    if ((count == 4))
    -    then
    -        echo "### Set priority for: mpd-output thread ###";
    -        renice -18 $pid;
    -    fi
    -    if ((count == 5))
    -    then
    -            echo "### Set priority for: mpd-decoder thread ###";
    -            renice -16 $pid;
    -    fi
    -count=$((count+1))
    -done
    -renice -18 $(pgrep -w spopd)
    -renice 20 $(pgrep -w rune_SY_wrk)
    -renice 20 $(pgrep -w rune_PL_wrk)
    -renice 20 $(pgrep -w php-fpm)
    -renice 20 $(pgrep -w nginx)
    -renice 20 $(pgrep -w nmbd)
    -renice 20 $(pgrep -w smbd)
    -renice 20 $(pgrep -w sshd)
    -renice 20 $(pgrep -w mpdscribble)
    -renice 20 $(pgrep -w shairport)
    -renice 20 $(pgrep -w upmpdcli)
    -renice 20 $(pgrep -w cifsd)
    -renice 20 $(pgrep -w rpcbind)
    -renice 20 $(pgrep -w redis-server)
    -renice 20 $(pgrep -w ksoftirqd)
    -renice 20 $(pgrep -w rcu_preempt)
    -renice 20 $(pgrep -w systemd)
    -renice 20 $(pgrep -w systemd-udevd)
    -renice 20 $(pgrep -w ifplugd)
    -renice 20 $(pgrep -w dhcpcd)
    -renice 20 $(pgrep -w dbus-daemon)
    -renice 20 $(pgrep -w avahi-dnsconfd)
    -renice 20 $(pgrep -w avahi-daemon)
    -renice 20 $(pgrep -w agetty)
    -}
    -
    -prio_default () {
    -count=1
    -for pid in $(pgrep -w mpd);
    -do
    -    if ((count == 3))
    -    then
    -        echo "### Set priority for: mpd-player thread ###";
    -        renice 20 $pid;
    -    fi
    -    if ((count == 4))
    -    then
    -        echo "### Set priority for: mpd-output thread ###";
    -        renice 20 $pid;
    -    fi
    -    if ((count == 5))
    -    then
    -        echo "### Set priority for: mpd-decoder thread ###";
    -        renice 20 $pid;
    -    fi
    -count=$((count+1))
    -done
    -renice 0 $(pgrep -w spopd)
    -renice 0 $(pgrep -w rune_SY_wrk)
    -renice 0 $(pgrep -w rune_PL_wrk)
    -renice 0 $(pgrep -w php-fpm)
    -renice 0 $(pgrep -w nginx)
    -renice 0 $(pgrep -w nmbd)
    -renice 0 $(pgrep -w smbd)
    -renice 0 $(pgrep -w sshd)
    -renice 0 $(pgrep -w mpdscribble)
    -renice 0 $(pgrep -w shairport)
    -renice 0 $(pgrep -w upmpdcli)
    -renice 0 $(pgrep -w cifsd)
    -renice 0 $(pgrep -w rpcbind)
    -renice 0 $(pgrep -w redis-server)
    -renice 0 $(pgrep -w ksoftirqd)
    -renice 0 $(pgrep -w rcu_preempt)
    -renice 0 $(pgrep -w systemd)
    -renice 0 $(pgrep -w systemd-udevd)
    -renice 0 $(pgrep -w ifplugd)
    -renice 0 $(pgrep -w dhcpcd)
    -renice 0 $(pgrep -w dbus-daemon)
    -renice 0 $(pgrep -w avahi-dnsconfd)
    -renice 0 $(pgrep -w avahi-daemon)
    -renice 0 $(pgrep -w agetty)
    -}
    -
    -if [ "$1" == "default" ]; then
    -    echo "set default priority settings"
    -    prio_default
    -    exit 0
    -fi
    -
    -if [ "$1" == "nice" ]; then
    -    echo "set nice priority settings"
    -    prio_nice
    -    exit 0
    -fi
    -
    -if [ "$1" == "" ]; then
    -    echo "RuneAudio process priority settings"
    -    echo "Usage: $0 {default|nice}"
    -    exit 1
    -fi
    +#!/bin/bash
    +#
    +#  Copyright (C) 2013-2014 RuneAudio Team
    +#  http://www.runeaudio.com
    +#
    +#  RuneUI
    +#  copyright (C) 2013-2014 – Andrea Coiutti (aka ACX) & Simone De Gregori (aka Orion)
    +#
    +#  RuneOS
    +#  copyright (C) 2013-2014 – Simone De Gregori (aka Orion) & Carmelo San Giovanni (aka Um3ggh1U)
    +#
    +#  RuneAudio website and logo
    +#  copyright (C) 2013-2014 – ACX webdesign (Andrea Coiutti)
    +#
    +#  This Program is free software; you can redistribute it and/or modify
    +#  it under the terms of the GNU General Public License as published by
    +#  the Free Software Foundation; either version 3, or (at your option)
    +#  any later version.
    +#
    +#  This Program is distributed in the hope that it will be useful,
    +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
    +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    +#  GNU General Public License for more details.
    +#
    +#  You should have received a copy of the GNU General Public License
    +#  along with RuneAudio; see the file COPYING.  If not, see
    +#  .
    +# 
    +#  file: command/rune_prio
    +#  version: 1.3
    +#  coder: Simone De Gregori
    +#
    +#####################################
    +prio_nice () {
    +count=1
    +for pid in $(pgrep -w mpd);
    +do
    +    if ((count == 3))
    +    then
    +        echo "### Set priority for: mpd-player thread ###";
    +        renice -16 $pid;
    +    fi
    +    if ((count == 4))
    +    then
    +        echo "### Set priority for: mpd-output thread ###";
    +        renice -18 $pid;
    +    fi
    +    if ((count == 5))
    +    then
    +            echo "### Set priority for: mpd-decoder thread ###";
    +            renice -16 $pid;
    +    fi
    +count=$((count+1))
    +done
    +renice -18 $(pgrep -w spopd)
    +renice 20 $(pgrep -w rune_SY_wrk)
    +renice 20 $(pgrep -w rune_PL_wrk)
    +renice 20 $(pgrep -w php-fpm)
    +renice 20 $(pgrep -w nginx)
    +renice 20 $(pgrep -w nmbd)
    +renice 20 $(pgrep -w smbd)
    +renice 20 $(pgrep -w sshd)
    +renice 20 $(pgrep -w mpdscribble)
    +renice 20 $(pgrep -w shairport)
    +renice 20 $(pgrep -w upmpdcli)
    +renice 20 $(pgrep -w cifsd)
    +renice 20 $(pgrep -w rpcbind)
    +renice 20 $(pgrep -w redis-server)
    +renice 20 $(pgrep -w ksoftirqd)
    +renice 20 $(pgrep -w rcu_preempt)
    +renice 20 $(pgrep -w systemd)
    +renice 20 $(pgrep -w systemd-udevd)
    +renice 20 $(pgrep -w ifplugd)
    +renice 20 $(pgrep -w dhcpcd)
    +renice 20 $(pgrep -w dbus-daemon)
    +renice 20 $(pgrep -w avahi-dnsconfd)
    +renice 20 $(pgrep -w avahi-daemon)
    +renice 20 $(pgrep -w agetty)
    +}
    +
    +prio_default () {
    +count=1
    +for pid in $(pgrep -w mpd);
    +do
    +    if ((count == 3))
    +    then
    +        echo "### Set priority for: mpd-player thread ###";
    +        renice 20 $pid;
    +    fi
    +    if ((count == 4))
    +    then
    +        echo "### Set priority for: mpd-output thread ###";
    +        renice 20 $pid;
    +    fi
    +    if ((count == 5))
    +    then
    +        echo "### Set priority for: mpd-decoder thread ###";
    +        renice 20 $pid;
    +    fi
    +count=$((count+1))
    +done
    +renice 0 $(pgrep -w spopd)
    +renice 0 $(pgrep -w rune_SY_wrk)
    +renice 0 $(pgrep -w rune_PL_wrk)
    +renice 0 $(pgrep -w php-fpm)
    +renice 0 $(pgrep -w nginx)
    +renice 0 $(pgrep -w nmbd)
    +renice 0 $(pgrep -w smbd)
    +renice 0 $(pgrep -w sshd)
    +renice 0 $(pgrep -w mpdscribble)
    +renice 0 $(pgrep -w shairport)
    +renice 0 $(pgrep -w upmpdcli)
    +renice 0 $(pgrep -w cifsd)
    +renice 0 $(pgrep -w rpcbind)
    +renice 0 $(pgrep -w redis-server)
    +renice 0 $(pgrep -w ksoftirqd)
    +renice 0 $(pgrep -w rcu_preempt)
    +renice 0 $(pgrep -w systemd)
    +renice 0 $(pgrep -w systemd-udevd)
    +renice 0 $(pgrep -w ifplugd)
    +renice 0 $(pgrep -w dhcpcd)
    +renice 0 $(pgrep -w dbus-daemon)
    +renice 0 $(pgrep -w avahi-dnsconfd)
    +renice 0 $(pgrep -w avahi-daemon)
    +renice 0 $(pgrep -w agetty)
    +}
    +
    +if [ "$1" == "default" ]; then
    +    echo "set default priority settings"
    +    prio_default
    +    exit 0
    +fi
    +
    +if [ "$1" == "nice" ]; then
    +    echo "set nice priority settings"
    +    prio_nice
    +    exit 0
    +fi
    +
    +if [ "$1" == "" ]; then
    +    echo "RuneAudio process priority settings"
    +    echo "Usage: $0 {default|nice}"
    +    exit 1
    +fi
    diff --git a/command/rune_shutdown b/command/rune_shutdown
    index ab9a8ca7..12567c01 100755
    --- a/command/rune_shutdown
    +++ b/command/rune_shutdown
    @@ -1,41 +1,41 @@
    -#!/bin/bash
    -#
    -#  Copyright (C) 2013-2014 RuneAudio Team
    -#  http://www.runeaudio.com
    -#
    -#  RuneUI
    -#  copyright (C) 2013-2014 – Andrea Coiutti (aka ACX) & Simone De Gregori (aka Orion)
    -#
    -#  RuneOS
    -#  copyright (C) 2013-2014 – Simone De Gregori (aka Orion) & Carmelo San Giovanni (aka Um3ggh1U)
    -#
    -#  RuneAudio website and logo
    -#  copyright (C) 2013-2014 – ACX webdesign (Andrea Coiutti)
    -#
    -#  This Program is free software; you can redistribute it and/or modify
    -#  it under the terms of the GNU General Public License as published by
    -#  the Free Software Foundation; either version 3, or (at your option)
    -#  any later version.
    -#
    -#  This Program is distributed in the hope that it will be useful,
    -#  but WITHOUT ANY WARRANTY; without even the implied warranty of
    -#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -#  GNU General Public License for more details.
    -#
    -#  You should have received a copy of the GNU General Public License
    -#  along with RuneAudio; see the file COPYING.  If not, see
    -#  .
    -#
    -#  file: command/rune_shutdown
    -#  version: 1.3
    -#  coder: Simone De Gregori
    -#
    -echo "Stop MPD and unmount shares..."
    -mpc stop
    -systemctl stop nginx
    -sleep 1
    -systemctl stop mpd
    -systemctl stop spopd
    -sleep 1
    -umount -aft nfs
    -umount -aft cifs
    +#!/bin/bash
    +#
    +#  Copyright (C) 2013-2014 RuneAudio Team
    +#  http://www.runeaudio.com
    +#
    +#  RuneUI
    +#  copyright (C) 2013-2014 – Andrea Coiutti (aka ACX) & Simone De Gregori (aka Orion)
    +#
    +#  RuneOS
    +#  copyright (C) 2013-2014 – Simone De Gregori (aka Orion) & Carmelo San Giovanni (aka Um3ggh1U)
    +#
    +#  RuneAudio website and logo
    +#  copyright (C) 2013-2014 – ACX webdesign (Andrea Coiutti)
    +#
    +#  This Program is free software; you can redistribute it and/or modify
    +#  it under the terms of the GNU General Public License as published by
    +#  the Free Software Foundation; either version 3, or (at your option)
    +#  any later version.
    +#
    +#  This Program is distributed in the hope that it will be useful,
    +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
    +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    +#  GNU General Public License for more details.
    +#
    +#  You should have received a copy of the GNU General Public License
    +#  along with RuneAudio; see the file COPYING.  If not, see
    +#  .
    +#
    +#  file: command/rune_shutdown
    +#  version: 1.3
    +#  coder: Simone De Gregori
    +#
    +echo "Stop MPD and unmount shares..."
    +mpc stop
    +systemctl stop nginx
    +sleep 1
    +systemctl stop mpd
    +systemctl stop spopd
    +sleep 1
    +umount -aft nfs
    +umount -aft cifs
    diff --git a/command/sndusb_nrpacks b/command/sndusb_nrpacks
    index 8a3543dc..39d51a63 100755
    --- a/command/sndusb_nrpacks
    +++ b/command/sndusb_nrpacks
    @@ -1,53 +1,53 @@
    -#!/bin/bash
    -#
    -#  Copyright (C) 2013-2014 RuneAudio Team
    -#  http://www.runeaudio.com
    -#
    -#  RuneUI
    -#  copyright (C) 2013-2014 - Andrea Coiutti (aka ACX) & Simone De Gregori (aka Orion)
    -#
    -#  RuneOS
    -#  copyright (C) 2013-2014 - Simone De Gregori (aka Orion) & Carmelo San Giovanni (aka Um3ggh1U)
    -#
    -#  RuneAudio website and logo
    -#  copyright (C) 2013-2014 - ACX webdesign (Andrea Coiutti)
    -#
    -#  This Program is free software; you can redistribute it and/or modify
    -#  it under the terms of the GNU General Public License as published by
    -#  the Free Software Foundation; either version 3, or (at your option)
    -#  any later version.
    -#
    -#  This Program is distributed in the hope that it will be useful,
    -#  but WITHOUT ANY WARRANTY; without even the implied warranty of
    -#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    -#  GNU General Public License for more details.
    -#
    -#  You should have received a copy of the GNU General Public License
    -#  along with RuneAudio; see the file COPYING.  If not, see
    -#  .
    -#
    -#  file: command/sndusb_nrpacks
    -#  version: 1.3
    -#  coder: Simone De Gregori
    -#
    -#####################################
    -sndusb_profile() {
    -    local "${@}"
    -    mpc pause > /dev/null 2>&1
    -    sleep 0.3
    -    modprobe -r snd-usb-audio
    -    echo "options snd-usb-audio nrpacks=${nrpacks}" > /etc/modprobe.d/modprobe.conf
    -    sleep 0.2
    -    modprobe snd-usb-audio
    -    sleep 0.5
    -}
    -if [ $1 -ge 1 ]; then
    -    echo "set snd-usb-audio nrpacks="$1
    -    sndusb_profile nrpacks=$1
    -    exit 0
    -fi
    -if [ "$1" == "" ]; then
    -    echo "set snd-usb-audio nrpacks {value}"
    -    echo "Usage: $0 {value}"
    -    exit 1
    -fi
    +#!/bin/bash
    +#
    +#  Copyright (C) 2013-2014 RuneAudio Team
    +#  http://www.runeaudio.com
    +#
    +#  RuneUI
    +#  copyright (C) 2013-2014 - Andrea Coiutti (aka ACX) & Simone De Gregori (aka Orion)
    +#
    +#  RuneOS
    +#  copyright (C) 2013-2014 - Simone De Gregori (aka Orion) & Carmelo San Giovanni (aka Um3ggh1U)
    +#
    +#  RuneAudio website and logo
    +#  copyright (C) 2013-2014 - ACX webdesign (Andrea Coiutti)
    +#
    +#  This Program is free software; you can redistribute it and/or modify
    +#  it under the terms of the GNU General Public License as published by
    +#  the Free Software Foundation; either version 3, or (at your option)
    +#  any later version.
    +#
    +#  This Program is distributed in the hope that it will be useful,
    +#  but WITHOUT ANY WARRANTY; without even the implied warranty of
    +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    +#  GNU General Public License for more details.
    +#
    +#  You should have received a copy of the GNU General Public License
    +#  along with RuneAudio; see the file COPYING.  If not, see
    +#  .
    +#
    +#  file: command/sndusb_nrpacks
    +#  version: 1.3
    +#  coder: Simone De Gregori
    +#
    +#####################################
    +sndusb_profile() {
    +    local "${@}"
    +    mpc pause > /dev/null 2>&1
    +    sleep 0.3
    +    modprobe -r snd-usb-audio
    +    echo "options snd-usb-audio nrpacks=${nrpacks}" > /etc/modprobe.d/modprobe.conf
    +    sleep 0.2
    +    modprobe snd-usb-audio
    +    sleep 0.5
    +}
    +if [ $1 -ge 1 ]; then
    +    echo "set snd-usb-audio nrpacks="$1
    +    sndusb_profile nrpacks=$1
    +    exit 0
    +fi
    +if [ "$1" == "" ]; then
    +    echo "set snd-usb-audio nrpacks {value}"
    +    echo "Usage: $0 {value}"
    +    exit 1
    +fi
    diff --git a/command/ui_notify.php b/command/ui_notify.php
    index 2f91eb40..d4fa8b4a 100755
    --- a/command/ui_notify.php
    +++ b/command/ui_notify.php
    @@ -36,35 +36,10 @@
     ini_set('display_errors', '1');
     ini_set('error_reporting', -1);
     ini_set('error_log','/var/log/runeaudio/ui_notify.log');
    -
    -// ---- functions -----
    -// push UI update to NGiNX channel
    -function ui_render($channel, $data)
    -{
    -    // runelog('ui_render channel: '.$channel.', data: ',$data);
    -    curlPost('http://127.0.0.1/pub?id='.$channel,$data);
    -}
    -
    -function curlPost($url,$data,$proxy = null)
    -{
    -$ch = curl_init($url);
    -    if (isset($proxy)) {
    -        $proxy['user'] === '' || curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy['user'].':'.$proxy['pass']);
    -        curl_setopt($ch, CURLOPT_PROXY, $proxy['host']);
    -        //runelog('cURL proxy HOST: ',$proxy['host']);
    -        //runelog('cURL proxy USER: ',$proxy['user']);
    -        //runelog('cURL proxy PASS: ',$proxy['pass']);
    -    }
    -    curl_setopt($ch, CURLOPT_TIMEOUT, 2);
    -    curl_setopt($ch, CURLOPT_POST, 1);
    -    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    -    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    -    curl_setopt($ch, CURLOPT_HEADER, 0);  // DO NOT RETURN HTTP HEADERS
    -    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  // RETURN THE CONTENTS OF THE CALL
    -    $response = curl_exec($ch);
    -    curl_close($ch);
    -    return $response;
    -}
    +// Connect to Redis backend
    +$redis = new Redis();
    +$redis->connect('/tmp/redis.sock');
    +include('/var/www/app/libs/runeaudio.php');
     // ---- end functions -----
     if (isset($argv[2]) && !isset($argv[3])) {
     // Connect to Redis backend
    diff --git a/command/ui_update_async b/command/ui_update_async
    index 10a7c401..933fd050 100755
    --- a/command/ui_update_async
    +++ b/command/ui_update_async
    @@ -1,55 +1,55 @@
    -#!/usr/bin/php
    -.
    - *
    - *  file: command/ui_update_async {usec_delay}
    - *  version: 1.3
    - *  coder: Simone De Gregori
    - *
    - */
    -// common include
    -ini_set('display_errors', '1');
    -error_reporting(E_ALL);
    -ini_set('error_log', '/var/log/runeaudio/ui_update_async.log');
    -define('APP', '/srv/http/app/');
    -include('/srv/http/app/libs/runeaudio.php');
    -// Connect to Redis backend
    -$redis = new Redis();
    -$redis->connect('/tmp/redis.sock');
    -$activePlayer = $redis->get('activePlayer');
    -if ($activePlayer === 'MPD') {
    -    $socket = openMpdSocket('/run/mpd.sock');
    -} elseif ($activePlayer === 'Spotify') {
    -    $socket = openSpopSocket('localhost', 6602, 1);
    -} else {
    -    exit(1);
    -}
    -if (isset($argv[1])) {
    -    usleep($argv[1]);
    -}
    -ui_update($redis, $socket);
    +#!/usr/bin/php
    +.
    + *
    + *  file: command/ui_update_async {usec_delay}
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +ini_set('display_errors', '1');
    +error_reporting(E_ALL);
    +ini_set('error_log', '/var/log/runeaudio/ui_update_async.log');
    +define('APP', '/srv/http/app/');
    +include('/srv/http/app/libs/runeaudio.php');
    +// Connect to Redis backend
    +$redis = new Redis();
    +$redis->connect('/tmp/redis.sock');
    +$activePlayer = $redis->get('activePlayer');
    +if ($activePlayer === 'MPD') {
    +    $socket = openMpdSocket('/run/mpd.sock');
    +} elseif ($activePlayer === 'Spotify') {
    +    $socket = openSpopSocket('localhost', 6602, 1);
    +} else {
    +    exit(1);
    +}
    +if (isset($argv[1])) {
    +    usleep($argv[1]);
    +}
    +ui_update($redis, $socket);
    diff --git a/command/update_os b/command/update_os
    new file mode 100644
    index 00000000..d3944696
    --- /dev/null
    +++ b/command/update_os
    @@ -0,0 +1,51 @@
    +#!/usr/bin/php
    +.
    + *
    + *  file: command/update_os
    + *  version: 0.1
    + *  coder: Frank Friedmann
    + *
    + */
    +
    +function updateOS()
    +{
    +    sysCmd('cp /srv/http/app/config/_os/usr/lib/systemd/system/rune_SY_wrk.service /usr/lib/systemd/system/');
    +    sysCmd('cp /srv/http/app/config/_os/usr/lib/systemd/system/rune_PL_wrk.service /usr/lib/systemd/system/');
    +    sysCmd('cp /srv/http/app/config/_os/usr/lib/systemd/system/redis.service /usr/lib/systemd/system/');
    +    sysCmd('cp /srv/http/app/config/_os/usr/lib/systemd/system/mpd.service /usr/lib/systemd/system/');
    +    sysCmd('cp /srv/http/app/config/_os/usr/lib/systemd/system/ntpd.service /usr/lib/systemd/system/');
    +    sysCmd('cp /srv/http/app/config/_os/usr/lib/systemd/system/php-fpm.service /usr/lib/systemd/system/');
    +    sysCmd('systemctl daemon-reload');
    +    
    +    sysCmd('rm /etc/netctl/wlan0');
    +
    +    sysCmd('cp /srv/http/app/config/_os/etc/wpa_supplicant/wpa_supplicant.conf /etc/wpa_supplicant/');
    +
    +    sysCmd('rm /srv/http/command/update_os');
    +}
    diff --git a/command/usbmount b/command/usbmount
    index 21675c27..9b703f65 100755
    --- a/command/usbmount
    +++ b/command/usbmount
    @@ -1,68 +1,68 @@
    -#!/usr/bin/php
    -.
    - *
    - *  file: command/usbmount
    - *  version: 1.3
    - *  coder: Simone De Gregori
    - *
    - */
    -// common include
    -ini_set('display_errors', '1');
    -ini_set('error_reporting', -1);
    -ini_set('error_log','/var/log/runeaudio/usbmount.log');
    -include('/var/www/app/libs/runeaudio.php');
    -// Connect to Redis backend
    -$redis = new Redis();
    -$redis->connect('/tmp/redis.sock');
    -// handle HFS+ mount
    -if (strpos(file_get_contents('/proc/mounts'), '/mnt/MPD/USB/EFI vfat')) {
    -    sysCmd('devmon --unmount /mnt/MPD/USB/EFI');
    -}
    -// record mount point info in Redis
    -$redis->Del('usbmounts');
    -// $usbmounts = sysCmd("df -h | grep USB | cut -d ' ' -f 1,23,31,32 | cut -d '/' -f 3,6-7");
    -$usbmounts = sysCmd("df -h | grep USB");
    -foreach ($usbmounts as $line) {
    -    $line = array_values(array_filter(explode(' ', trim($line))));
    -    // debug
    -    // print_r($line);
    -    $redis->hSet('usbmounts', $line[0], json_encode(array('device' => $line[0], 'size' => $line[1], 'use' => $line[4], 'name' => substr($line[5], 9))));
    -    // debug
    -    runelog('usbmount line: ', $line);
    -}
    -// connect to MPD daemon
    -$mpd = openMpdSocket('/run/mpd.sock');
    -// update MPD database
    -sendMpdCommand($mpd, 'update USB');
    -// update library panel
    -ui_libraryHome($redis);
    -// close MPD connection
    -closeMpdSocket($mpd);
    -// close Redis connection
    -$redis->close();
    +#!/usr/bin/php
    +.
    + *
    + *  file: command/usbmount
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +ini_set('display_errors', '1');
    +ini_set('error_reporting', -1);
    +ini_set('error_log','/var/log/runeaudio/usbmount.log');
    +include('/var/www/app/libs/runeaudio.php');
    +// Connect to Redis backend
    +$redis = new Redis();
    +$redis->connect('/tmp/redis.sock');
    +// handle HFS+ mount
    +if (strpos(file_get_contents('/proc/mounts'), '/mnt/MPD/USB/EFI vfat')) {
    +    sysCmd('devmon --unmount /mnt/MPD/USB/EFI');
    +}
    +// record mount point info in Redis
    +$redis->Del('usbmounts');
    +// $usbmounts = sysCmd("df -h | grep USB | cut -d ' ' -f 1,23,31,32 | cut -d '/' -f 3,6-7");
    +$usbmounts = sysCmd("df -h | grep USB");
    +foreach ($usbmounts as $line) {
    +    $line = array_values(array_filter(explode(' ', trim($line))));
    +    // debug
    +    // print_r($line);
    +    $redis->hSet('usbmounts', $line[0], json_encode(array('device' => $line[0], 'size' => $line[1], 'use' => $line[4], 'name' => substr($line[5], 9))));
    +    // debug
    +    runelog('usbmount line: ', $line);
    +}
    +// connect to MPD daemon
    +$mpd = openMpdSocket('/run/mpd.sock');
    +// update MPD database
    +sendMpdCommand($mpd, 'update USB');
    +// update library panel
    +ui_libraryHome($redis);
    +// close MPD connection
    +closeMpdSocket($mpd);
    +// close Redis connection
    +$redis->close();
    diff --git a/command/wolfson.sh b/command/wolfson.sh
    new file mode 100755
    index 00000000..78fe70d2
    --- /dev/null
    +++ b/command/wolfson.sh
    @@ -0,0 +1,136 @@
    +#!/bin/bash
    +#
    +# wolson.sh
    +# ----------------
    +# Select the audio output for the Wolfson Audio Card in RuneAudio
    +#
    +# Set some standard paths
    +AMIXER=/usr/bin/amixer
    +MPC=/usr/bin/mpc
    +#
    +# Set variables
    +CARD="$1"
    +ENABLE_OUTPUT="$2"
    +#
    +function amixer_cset {
    +   $AMIXER -c $CARD cset name="$1" $2
    +}
    +#
    +# function wolfson_enable {
    +#   $MPC enable only snd_rpi_wsp
    +#
    +#
    +function reset_paths {
    +  #
    +  # Switch everything off
    +  #
    +  amixer_cset 'TX Playback Switch'          off
    +  amixer_cset 'RX Playback Switch'          off
    +  amixer_cset 'AIF Playback Switch'         off
    +  amixer_cset 'SPDIF in Switch'             off
    +  amixer_cset 'SPDIF out Switch'            off
    +  #amixer_cset 'IN1 Digital Switch'          off
    +  #amixer_cset 'IN2 Digital Switch'          off
    +  #amixer_cset 'IN3 Digital Switch'          off
    +  amixer_cset 'IN3 High Performance Switch' off
    +  amixer_cset 'Headset Mic Switch'          off
    +  amixer_cset 'DMIC Switch'                 off
    +  amixer_cset 'Speaker Digital Switch'      off
    +  amixer_cset 'AIF1TX1 Input 1'             None
    +  amixer_cset 'AIF1TX2 Input 1'             None
    +  amixer_cset 'AIF2TX1 Input 1'             None
    +  amixer_cset 'AIF2TX2 Input 1'             None
    +  amixer_cset 'HPOUT1L Input 1'             None
    +  amixer_cset 'HPOUT1R Input 1'             None
    +  amixer_cset 'HPOUT1L Input 2'             None
    +  amixer_cset 'HPOUT1R Input 2'             None
    +  amixer_cset 'HPOUT2L Input 1'             None
    +  amixer_cset 'HPOUT2R Input 1'             None
    +  amixer_cset 'HPOUT2L Input 2'             None
    +  amixer_cset 'HPOUT2R Input 2'             None
    +  amixer_cset 'SPKOUTL Input 1'             None
    +  amixer_cset 'SPKOUTR Input 1'             None
    +  amixer_cset 'SPKOUTL Input 2'             None
    +  amixer_cset 'SPKOUTR Input 2'             None
    +}
    +#
    +function line_out {
    +  #
    +  # Enable MPD output only to line_out
    +  #  $MPC enable only snd_rpi_wsp_1
    +  #
    +  # Playback from AP to Lineout
    +  #
    +  amixer_cset 'HPOUT2 Digital Switch'  on
    +  amixer_cset 'HPOUT2L Input 1'        AIF1RX1
    +  amixer_cset 'HPOUT2L Input 1 Volume' 32
    +  amixer_cset 'HPOUT2R Input 1'        AIF1RX2
    +  amixer_cset 'HPOUT2R Input 1 Volume' 32
    +}
    +#
    +function speakers_out {
    +  #
    +  # Enable MPD output only to speakers
    +  # $MPC enable only snd_rpi_wsp_4
    +  #
    +  # Playback from AP to Speakers
    +  #
    +  amixer_cset 'Speaker Digital Volume' 128
    +  # reset speaker mixer inputs
    +  amixer_cset 'SPKOUTL Input 1'        None
    +  amixer_cset 'SPKOUTR Input 1'        None
    +  amixer_cset 'SPKOUTL Input 2'        None
    +  amixer_cset 'SPKOUTR Input 2'        None
    +  # Route AP to Speaker mixer
    +  amixer_cset 'SPKOUTL Input 1'        AIF1RX1
    +  amixer_cset 'SPKOUTL Input 1 Volume' 32
    +  amixer_cset 'SPKOUTR Input 1'        AIF1RX2
    +  amixer_cset 'SPKOUTR Input 1 Volume' 32
    +  # Unmute speaker output
    +  amixer_cset 'Speaker Digital Switch' on
    +}
    +#
    +function spdif_out {
    +  #
    +  # Enable MPD output only to SPDIF
    +  # $MPC enable only snd_rpi_wsp_2
    +  #
    +  # Playback from AP to SPDIF
    +  #
    +  amixer_cset 'SPDIF out Switch'       on
    +  amixer_cset 'TX Playback Switch'     on
    +  amixer_cset 'Input Source'           AIF
    +  amixer_cset 'AIF Playback Switch'    on
    +  amixer_cset 'AIF2TX1 Input 1'        AIF1RX1
    +  amixer_cset 'AIF2TX1 Input 1 Volume' 32
    +  amixer_cset 'AIF2TX2 Input 1'        AIF1RX2
    +  amixer_cset 'AIF2TX2 Input 1 Volume' 32
    +}
    +function headset_out {
    +  #
    +  # Enable MPD output to headset only
    +  # $MPC enable only snd_rpi_wsp_3
    +  #
    +  # Playback from AP to Headset
    +  #
    +  #
    +  amixer_cset 'HPOUT1 Digital Switch'  on
    +  # Set path gain to -6dB for safety. ie max 0.5Vrms output level
    +  amixer_cset 'HPOUT1 Digital Volume'  116
    +  # Do we want to clear the HPOUT mixer inputs?
    +  amixer_cset 'HPOUT1L Input 1'        None
    +  amixer_cset 'HPOUT1R Input 1'        None
    +  amixer_cset 'HPOUT1L Input 2'        None
    +  amixer_cset 'HPOUT1R Input 2'        None
    +  amixer_cset 'HPOUT1L Input 1'        AIF1RX1
    +  amixer_cset 'HPOUT1L Input 1 Volume' 32
    +  amixer_cset 'HPOUT1R Input 1'        AIF1RX2
    +  amixer_cset 'HPOUT1R Input 1 Volume' 32
    +}
    +#
    +#
    +# MAIN
    +#
    +# wolfson_enable
    +reset_paths
    +$ENABLE_OUTPUT
    diff --git a/db/phpinfo.php b/db/phpinfo.php
    new file mode 100644
    index 00000000..3576cd12
    --- /dev/null
    +++ b/db/phpinfo.php
    @@ -0,0 +1,8 @@
    +pconnect('127.0.0.1');
    +$sub_interfaces = $redis->sMembers($card);
    +
    +//phpinfo();
    +?>
    \ No newline at end of file
    diff --git a/db/redis_acards_details b/db/redis_acards_details
    old mode 100755
    new mode 100644
    index 0ba0aaf4..6ffa43b9
    --- a/db/redis_acards_details
    +++ b/db/redis_acards_details
    @@ -1,72 +1,75 @@
    -#!/usr/bin/php
    -.
    - *
    - *  file: db/redis_acards_details
    - *  version: 1.3
    - *  coder: Simone De Gregori
    - *
    - */
    -// common include
    -include('/srv/http/app/config/config.php');
    -// ini_set('display_errors',1);
    -// error_reporting('E_ALL');
    -// acards_details database
    -$redis->hSet('acards_details', 'snd_rpi_iqaudio_dac', '{"sysname":"snd_rpi_iqaudio_dac","extlabel":"IQaudIO Pi-DAC (I²S)","mixer_numid":"1","mixer_control":"Playback Digital","hwplatformid":"01","type":"i2s"}');
    -$redis->hSet('acards_details', 'berrynosmini', '{"sysname":"snd_rpi_hifiberry_dac","extlabel":"BerryNOS mini (I²S)","hwplatformid":"01","type":"i2s"}');
    -$redis->hSet('acards_details', 'berrynos', '{"sysname":"snd_rpi_hifiberry_dac","extlabel":"BerryNOS 1543 (I²S)","hwplatformid":"01","type":"i2s"}');
    -$redis->hSet('acards_details', 'snd_rpi_hifiberry_dac', '{"sysname":"snd_rpi_hifiberry_dac","extlabel":"HiFiBerry DAC (I²S)","hwplatformid":"01","type":"i2s"}');
    -$redis->hSet('acards_details', 'snd_rpi_hifiberry_digi', '{"sysname":"snd_rpi_hifiberry_digi","extlabel":"HiFiBerry Digi (I²S)","hwplatformid":"01","type":"i2s"}');
    -$redis->hSet('acards_details', 'snd_rpi_hifiberry_dacplus', '{"sysname":"snd_rpi_hifiberry_dacplus","extlabel":"HiFiBerry DAC+ (I²S)","mixer_numid":"1","mixer_control":"Playback Digital","hwplatformid":"01","type":"i2s"}');
    -$redis->hSet('acards_details', 'raspyplay3', '{"sysname":"snd_rpi_hifiberry_dac","extlabel":"RaspyPlay3 (I²S)","hwplatformid":"01","type":"i2s"}');
    -$redis->hSet('acards_details', 'raspyplay4', '{"sysname":"snd_rpi_iqaudio_dac","extlabel":"RaspyPlay4 (I²S)","mixer_numid":"1","mixer_control":"Playback Digital","hwplatformid":"01","type":"i2s"}');
    -$redis->hSet('acards_details', 'XMOS USB Audio 2.0', '{"sysname":"XMOS USB Audio 2.0","extlabel":"XMOS AK4399 USB-Audio DAC","mixer_numid":"3","mixer_control":"XMOS Clock Selector","type":"usb"}');
    -$redis->hSet('acards_details', 'wm8731-audio', '{"sysname":"wm8731-audio","extlabel":"Utilite Analog Out","mixer_numid":"1","mixer_control":"Master","hwplatformid":"05","type":"integrated"}');
    -$redis->hSet('acards_details', 'imx-spdif', '{"sysname":"imx-spdif","extlabel":"Utilite Coax SPDIF Out","hwplatformid":"05","type":"integrated"}');
    -$redis->hSet('acards_details', 'imx-hdmi-soc', '{"sysname":"imx-hdmi-soc","extlabel":"Utilite HDMI Out","hwplatformid":"05","type":"integrated"}');
    -$redis->hSet('acards_details', 'bcm2835 ALSA', '{"sysname":"bcm2835 ALSA","extlabel":"none","mixer_numid":"1","mixer_control":"PCM","hwplatformid":"01","type":"integrated_sub"}');
    -$redis->hSet('acards_details', 'SA9023 USB Audio', '{"sysname":"SA9023 USB Audio","extlabel":"HiFimeDIY SABRE U2","mixer_numid":"4","mixer_control":"PCM","type":"usb"}');
    -$redis->hSet('acards_details', 'UAC1 DAC', '{"sysname":"UAC1 DAC","extlabel":"ObjectiveDAC (ODAC)","mixer_numid":"3","mixer_control":"PCM","type":"usb"}');
    -$redis->hSet('acards_details', 'USB Sound Device', '{"sysname":"USB Sound Device","extlabel":"Cmedia CM2606","mixer_numid":"7","mixer_control":"Speaker","type":"usb"}');
    -$redis->hSet('acards_details', 'Geek Out HD Audio 1V0', '{"sysname":"Geek Out HD Audio 1V0","extlabel":"LH Labs Geek Out 720","mixer_numid":"3","mixer_control":"LH Labs Clock Selector","type":"usb"}');
    -$redis->hSet('acards_details', 'Hugo', '{"sysname":"Hugo","extlabel":"Chord Hugo","mixer_numid":"4","mixer_control":"Hugo","type":"usb"}');
    -$redis->hSet('acards_details', 'Devialet USB Audio 2.0', '{"sysname":"Devialet USB Audio 2.0","extlabel":"Devialet Audio","mixer_numid":"3","mixer_control":"Devialet Clock Selector","type":"usb"}');
    -$redis->hSet('acards_details', 'ARCAM USB Audio 2.0', '{"sysname":"ARCAM USB Audio 2.0","extlabel":"Arcam rDAC","mixer_numid":"3","mixer_control":"ARCAM Clock Selector","type":"usb"}');
    -$redis->hSet('acards_details', 'AudioQuest DragonFly', '{"sysname":"AudioQuest DragonFly","extlabel":"AudioQuest DragonFly DAC","mixer_numid":"3","mixer_control":"PCM","type":"usb"}');
    -$redis->hSet('acards_details', 'USB Audio CODEC', '{"sysname":"USB Audio CODEC","extlabel":"Behringer U-CONTROL UCA202","mixer_numid":"3","mixer_control":"PCM","type":"usb"}');
    -$redis->hSet('acards_details', 'NuForce USB Audio', '{"sysname":"NuForce USB Audio","extlabel":"NuForce DDA-100","mixer_numid":"4","mixer_control":"PCM","type":"usb"}');
    -$redis->hSet('acards_details', 'Rotel PC-USB', '{"sysname":"Rotel PC-USB","extlabel":"Rotel USB","type":"usb"}');
    -$redis->hSet('acards_details', 'snd_rpi_wsp', '{"sysname":"snd_rpi_wsp","extlabel":"none","mixer_numid":"1","mixer_control":"HPOUT2 Digital","hwplatformid":"01","type":"integrated_sub"}');
    -$redis->sAdd('bcm2835 ALSA', '{"id":"1","sysname":"bcm2835 ALSA","extlabel":"RaspberryPi Analog Out","hwplatformid":"01","route_cmd":"amixer -c *CARDID* cset numid=3 1 > /dev/null"}');
    -$redis->sAdd('bcm2835 ALSA', '{"id":"2","sysname":"bcm2835 ALSA","extlabel":"RaspberryPi HDMI Out","hwplatformid":"01","route_cmd":"amixer -c *CARDID* cset numid=3 2 > /dev/null"}');
    -$redis->sAdd('snd_rpi_wsp', '{"id":"1","sysname":"snd_rpi_wsp","extlabel":"Wolfson Card Line Out","hwplatformid":"01","route_cmd":"/srv/http/command/wolfson.sh *CARDID* line_out > /dev/null"}');
    -$redis->sAdd('snd_rpi_wsp', '{"id":"2","sysname":"snd_rpi_wsp","extlabel":"Wolfson Card S/PDIF Out","hwplatformid":"01","route_cmd":"/srv/http/command/wolfson.sh *CARDID* spdif_out > /dev/null"}');
    -$redis->sAdd('snd_rpi_wsp', '{"id":"3","sysname":"snd_rpi_wsp","extlabel":"Wolfson Card Headset Out","hwplatformid":"01","route_cmd":"/srv/http/command/wolfson.sh *CARDID* headset_out > /dev/null"}');
    -$redis->sAdd('snd_rpi_wsp', '{"id":"4","sysname":"snd_rpi_wsp","extlabel":"Wolfson Card Speakers Out","hwplatformid":"01","route_cmd":"/srv/http/command/wolfson.sh *CARDID* speakers_out > /dev/null"}');
    -
    -echo "Audio Cards database initialized\n";
    +#!/usr/bin/php
    +.
    + *
    + *  file: db/redis_acards_details
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +include('/srv/http/app/config/config.php');
    +// ini_set('display_errors',1);
    +// error_reporting('E_ALL');
    +// acards_details database
    +$redis->hSet('acards_details', 'snd_rpi_iqaudio_dac', '{"sysname":"snd_rpi_iqaudio_dac","extlabel":"IQaudIO Pi-DAC (I²S)","mixer_numid":"1","mixer_control":"Playback Digital","hwplatformid":"01","type":"i2s"}');
    +$redis->hSet('acards_details', 'berrynosmini', '{"sysname":"snd_rpi_hifiberry_dac","extlabel":"BerryNOS mini (I²S)","hwplatformid":"01","type":"i2s"}');
    +$redis->hSet('acards_details', 'berrynos', '{"sysname":"snd_rpi_hifiberry_dac","extlabel":"BerryNOS 1543 (I²S)","hwplatformid":"01","type":"i2s"}');
    +$redis->hSet('acards_details', 'snd_rpi_hifiberry_dac', '{"sysname":"snd_rpi_hifiberry_dac","extlabel":"HiFiBerry DAC (I²S)","hwplatformid":"01","type":"i2s"}');
    +$redis->hSet('acards_details', 'snd_rpi_hifiberry_digi', '{"sysname":"snd_rpi_hifiberry_digi","extlabel":"HiFiBerry Digi (I²S)","hwplatformid":"01","type":"i2s"}');
    +$redis->hSet('acards_details', 'snd_rpi_hifiberry_dacplus', '{"sysname":"snd_rpi_hifiberry_dacplus","extlabel":"HiFiBerry DAC+ (I²S)","mixer_numid":"1","mixer_control":"Playback Digital","hwplatformid":"01","type":"i2s"}');
    +$redis->hSet('acards_details', 'raspyplay3', '{"sysname":"snd_rpi_hifiberry_dac","extlabel":"RaspyPlay3 (I²S)","hwplatformid":"01","type":"i2s"}');
    +$redis->hSet('acards_details', 'raspyplay4', '{"sysname":"snd_rpi_iqaudio_dac","extlabel":"RaspyPlay4 (I²S)","mixer_numid":"1","mixer_control":"Playback Digital","hwplatformid":"01","type":"i2s"}');
    +$redis->hSet('acards_details', 'XMOS USB Audio 2.0', '{"sysname":"XMOS USB Audio 2.0","extlabel":"XMOS AK4399 USB-Audio DAC","mixer_numid":"3","mixer_control":"XMOS Clock Selector","type":"usb"}');
    +$redis->hSet('acards_details', 'wm8731-audio', '{"sysname":"wm8731-audio","extlabel":"Utilite Analog Out","mixer_numid":"1","mixer_control":"Master","hwplatformid":"05","type":"integrated"}');
    +$redis->hSet('acards_details', 'imx-spdif', '{"sysname":"imx-spdif","extlabel":"Utilite Coax SPDIF Out","hwplatformid":"05","type":"integrated"}');
    +$redis->hSet('acards_details', 'imx-hdmi-soc', '{"sysname":"imx-hdmi-soc","extlabel":"Utilite HDMI Out","hwplatformid":"05","type":"integrated"}');
    +$redis->hSet('acards_details', 'SA9023 USB Audio', '{"sysname":"SA9023 USB Audio","extlabel":"HiFimeDIY SABRE U2","mixer_numid":"4","mixer_control":"PCM","type":"usb"}');
    +$redis->hSet('acards_details', 'UAC1 DAC', '{"sysname":"UAC1 DAC","extlabel":"ObjectiveDAC (ODAC)","mixer_numid":"3","mixer_control":"PCM","type":"usb"}');
    +$redis->hSet('acards_details', 'USB Sound Device', '{"sysname":"USB Sound Device","extlabel":"Cmedia CM2606","mixer_numid":"7","mixer_control":"Speaker","type":"usb"}');
    +$redis->hSet('acards_details', 'Geek Out HD Audio 1V0', '{"sysname":"Geek Out HD Audio 1V0","extlabel":"LH Labs Geek Out 720","mixer_numid":"3","mixer_control":"LH Labs Clock Selector","type":"usb"}');
    +$redis->hSet('acards_details', 'Hugo', '{"sysname":"Hugo","extlabel":"Chord Hugo","mixer_numid":"4","mixer_control":"Hugo","type":"usb"}');
    +$redis->hSet('acards_details', 'Devialet USB Audio 2.0', '{"sysname":"Devialet USB Audio 2.0","extlabel":"Devialet Audio","mixer_numid":"3","mixer_control":"Devialet Clock Selector","type":"usb"}');
    +$redis->hSet('acards_details', 'ARCAM USB Audio 2.0', '{"sysname":"ARCAM USB Audio 2.0","extlabel":"Arcam rDAC","mixer_numid":"3","mixer_control":"ARCAM Clock Selector","type":"usb"}');
    +$redis->hSet('acards_details', 'AudioQuest DragonFly', '{"sysname":"AudioQuest DragonFly","extlabel":"AudioQuest DragonFly DAC","mixer_numid":"3","mixer_control":"PCM","type":"usb"}');
    +$redis->hSet('acards_details', 'USB Audio CODEC', '{"sysname":"USB Audio CODEC","extlabel":"Behringer U-CONTROL UCA202","mixer_numid":"3","mixer_control":"PCM","type":"usb"}');
    +$redis->hSet('acards_details', 'NuForce USB Audio', '{"sysname":"NuForce USB Audio","extlabel":"NuForce DDA-100","mixer_numid":"4","mixer_control":"PCM","type":"usb"}');
    +$redis->hSet('acards_details', 'Rotel PC-USB', '{"sysname":"Rotel PC-USB","extlabel":"Rotel USB","type":"usb"}');
    +$redis->hSet('acards_details', 'bcm2835 ALSA', '{"sysname":"bcm2835 ALSA","extlabel":"none","hwplatformid":"01","type":"integrated_sub"}');
    +$redis->hSet('acards_details', 'snd_rpi_wsp', '{"sysname":"snd_rpi_wsp","extlabel":"none","hwplatformid":"01","type":"integrated_sub"}');
    +$redis->sAdd('bcm2835 ALSA', '{"id":"1","sysname":"bcm2835 ALSA","extlabel":"RaspberryPi Analog Out","hwplatformid":"01","mixer_numid":"1","mixer_control":"PCM","route_cmd":"amixer -c *CARDID* cset numid=3 1 > /dev/null"}');
    +$redis->sAdd('bcm2835 ALSA', '{"id":"2","sysname":"bcm2835 ALSA","extlabel":"RaspberryPi HDMI Out","hwplatformid":"01","mixer_numid":"1","mixer_control":"PCM","route_cmd":"amixer -c *CARDID* cset numid=3 2 > /dev/null"}');
    +$redis->sAdd('snd_rpi_wsp', '{"id":"1","sysname":"snd_rpi_wsp","extlabel":"Wolfson Card Line Out","hwplatformid":"01","mixer_numid":"1","mixer_control":"HPOUT2 Digital","route_cmd":"/srv/http/command/wolfson.sh *CARDID* line_out > /dev/null"}');
    +$redis->sAdd('snd_rpi_wsp', '{"id":"2","sysname":"snd_rpi_wsp","extlabel":"Wolfson Card S/PDIF Out","hwplatformid":"01","mixer_numid":"1","route_cmd":"/srv/http/command/wolfson.sh *CARDID* spdif_out > /dev/null"}');
    +$redis->sAdd('snd_rpi_wsp', '{"id":"3","sysname":"snd_rpi_wsp","extlabel":"Wolfson Card Headset Out","hwplatformid":"01","mixer_numid":"1","mixer_control":"HPOUT1 Digital","route_cmd":"/srv/http/command/wolfson.sh *CARDID* headset_out > /dev/null"}');
    +$redis->sAdd('snd_rpi_wsp', '{"id":"4","sysname":"snd_rpi_wsp","extlabel":"Wolfson Card Speakers Out","hwplatformid":"01","mixer_numid":"1","mixer_control":"Speaker Digital","route_cmd":"/srv/http/command/wolfson.sh *CARDID* speakers_out > /dev/null"}');
    +// KEW: not sure about these two:
    +$redis->hSet('acards_details', 'sunxi-sndi2s', '{"sysname":"sunxi-sndi2s","extlabel":"MP Transducer (I²S)","hwplatformid":"06","type":"i2s"}');
    +$redis->hSet('acards_details', 'sunxi-CODEC', '{"sysname":"sunxi-CODEC","extlabel":"MP PCM Output","hwplatformid":"06","type":"integrated"}');
    +// END
    +echo "Audio Cards database initialized\n";
    diff --git a/db/redis_datastore_setup b/db/redis_datastore_setup
    old mode 100755
    new mode 100644
    index 43c9dd30..a7e90e32
    --- a/db/redis_datastore_setup
    +++ b/db/redis_datastore_setup
    @@ -1,313 +1,315 @@
    -#!/usr/bin/php
    -.
    - *
    - *  file: db/redis_datastore_setup
    - *  version: 1.3
    - *  coder: Simone De Gregori
    - *
    - */
    -// common include
    -include('/srv/http/app/config/config.php');
    -// ini_set('display_errors',1);
    -// error_reporting('E_ALL');
    -function redisDatastore($redis,$action) {
    -    switch ($action) {
    -        case 'reset':
    -            // kernel
    -            $redis->set('kernel', 'linux-arch-rpi_3.12.26-1-ARCH');
    -            $redis->set('i2smodule', 'none');
    -            $redis->set('orionprofile', 'RuneAudio');
    -            $redis->set('activePlayer', 'MPD');
    -            // player features
    -            $redis->set('hostname', 'runeaudio');
    -            $redis->set('ntpserver', 'pool.ntp.org');
    -            $redis->hSet('airplay', 'enable', 1);
    -            $redis->hSet('airplay', 'name', 'RuneAudio');
    -            $redis->hSet('dlna', 'enable', 0);
    -            $redis->hSet('dlna', 'name', 'RuneAudio');
    -            $redis->hSet('dlna', 'queue', 1);
    -            $redis->hSet('dlna', 'logfile', '/var/log/runeaudio/upmpdcli.log');
    -            $redis->hSet('dlna', 'loglevel', 6);
    -            $redis->set('udevil', 1);
    -            $redis->hSet('spotify', 'enable', 0);
    -            $redis->hSet('spotify', 'user', 'user');
    -            $redis->hSet('spotify', 'pass', 'pass'); 
    -            $redis->hSet('spotify', 'plversion', 1);
    -            $redis->hSet('spotify', 'lastcmd', '');            
    -            $redis->set('coverart', 1);
    -            $redis->set('playmod', 0);
    -            $redis->set('ramplay', 0);
    -            $redis->hSet('lastfm', 'enable', 0);
    -            $redis->hSet('lastfm', 'user', 'user');
    -            $redis->hSet('lastfm', 'pass', 'pass');
    -            $redis->set('cmediafix', 0);
    -            $redis->set('globalrandom', 0);
    -            $redis->set('stoppedPlayer', '');    
    -            // default MPD config
    -            $redis->hSet('mpdconf', 'zeroconf_enabled', 'yes');
    -            $redis->hSet('mpdconf', 'zeroconf_name', 'RuneAudio');
    -            $redis->hSet('mpdconf', 'log_level', 'none');
    -            $redis->hSet('mpdconf', 'bind_to_address', 'any');
    -            $redis->hSet('mpdconf', 'port', '6600');
    -            $redis->hSet('mpdconf', 'max_connections', '20');
    -            $redis->hSet('mpdconf', 'user', 'mpd');
    -            $redis->hSet('mpdconf', 'db_file', '/var/lib/mpd/mpd.db');
    -            $redis->hSet('mpdconf', 'sticker_file', '/var/lib/mpd/sticker.sql');
    -            $redis->hSet('mpdconf', 'pid_file', '/var/run/mpd/pid');
    -            $redis->hSet('mpdconf', 'music_directory', '/mnt/MPD');
    -            $redis->hSet('mpdconf', 'playlist_directory', '/var/lib/mpd/playlists');
    -            $redis->hSet('mpdconf', 'state_file', '/var/lib/mpd/mpdstate');
    -            $redis->hSet('mpdconf', 'follow_outside_symlinks', 'yes');
    -            $redis->hSet('mpdconf', 'follow_inside_symlinks', 'yes');
    -            $redis->hSet('mpdconf', 'auto_update', 'no');
    -            $redis->hSet('mpdconf', 'filesystem_charset', 'UTF-8');
    -            $redis->hSet('mpdconf', 'id3v1_encoding', 'UTF-8');
    -            $redis->hSet('mpdconf', 'volume_normalization', 'no');
    -            $redis->hSet('mpdconf', 'audio_buffer_size', '2048');
    -            $redis->hSet('mpdconf', 'buffer_before_play', '10%');
    -            $redis->hSet('mpdconf', 'gapless_mp3_playback', 'yes');
    -            $redis->hSet('mpdconf', 'mixer_type', 'software');
    -            $redis->hSet('mpdconf', 'curl', 'yes');
    -            $redis->hSet('mpdconf', 'ffmpeg', 'yes');
    -            $redis->hSet('mpdconf', 'log_file', '/var/log/runeaudio/mpd.log');
    -            // plugins api-keys
    -            $redis->set('lastfm_apikey', 'ba8ad00468a50732a3860832eaed0882');
    -            $redis->hSet('lastfm', 'apikey', 'ba8ad00468a50732a3860832eaed0882');
    -            $redis->hSet('spotify', 'clientid', 'c236e0f5a84e48a3a7f6c730f684f255');
    -            $redis->hSet('jamendo', 'clientid', '5f3ed86c');
    -            $redis->hSet('jamendo', 'secret', '1afcdcb13eb5ce8f6e534ac4566a3ab9');
    -            $redis->hSet('dirble', 'apikey', '134aabbce2878ce0dbfdb23fb3b46265eded085b');
    -            $redis->hSet('dirble', 'baseurl', 'http://api.dirble.com/v1/');
    -            // internal config hash control
    -            // $redis->set('mpdconfhash', '');
    -            // $redis->set('netconfhash', '');
    -            $redis->set('mpdconfhash', '12eed229f02c52816ed997cbce4b9f32');
    -            $redis->hSet('netconfhash', 'eth0', '643f8967af551f683b3cfd493950c550');
    -            $redis->set('mpdconf_advanced', 0);
    -            $redis->set('netconf_advanced', 0);
    -            // MPD parameters
    -            wrk_mpdconf($redis, 'writecfg');
    -            $redis->set('mpd_playback_status', '');
    -            // developer parameters
    -            $redis->set('dev', 0);
    -            $redis->set('debug', 0);
    -            $redis->set('debugdata', '');
    -            $redis->set('opcache', 1);
    -            $redis->set('buildversion', 'beta-20141027');
    -            // HW platform data
    -            // $redis->set('playerid', '');
    -            // $redis->set('hwplatform', '');
    -            // $redis->set('hwplatformid', '');
    -            // player control
    -            if ($redis->get('hwplatformid') === '01') {
    -                $redis->set('ao', 'bcm2835 ALSA_1');
    -            } else {
    -                $redis->set('ao', '');
    -            }
    -            $redis->set('volume', 0);
    -            $redis->set('pl_length', 0);
    -            $redis->set('nextsongid', 0);
    -            $redis->set('lastsongid', 0);
    -            // network parameters
    -            $redis->hSet('resolvconf', 'timeout', '0.3');
    -            $redis->hSet('resolvconf', 'attempts', '1');
    -            // proxy settings
    -            $redis->hSet('proxy', 'host', '');
    -            $redis->hSet('proxy', 'user', '');
    -            $redis->hSet('proxy', 'pass', '');
    -            // lock keys
    -            $redis->set('lock_globalrandom', 0);
    -            $redis->set('lock_refresh_ao', 0);
    -            $redis->set('lock_wifiscan', 0);
    -            // env settings
    -            $redis->del('usbmounts');
    -            $redis->set('wlans', '');
    -            $redis->del('acards');
    -            $redis->set('mountidx', 1);
    -            $redis->set('debugdata', '');
    -            // PHP precache (prime) scripts
    -            $redis->del('php_opcache_prime');
    -            $transaction = $redis->multi();
    -            // RuneAudio Libs & config
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/config/config.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/runeaudio.php');
    -            // RuneAudio Controllers
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/index.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/db/index.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/command/index.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/coverart_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/credits_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/debug_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/dev_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/login_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/mpd_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/network_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/playback_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/settings_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/sources_ctl.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/tun_ctl.php');
    -            // RuneAudio Templates
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/credits.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/debug.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/default_lo.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/dev.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/error.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/footer.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/header.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/login.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/mpd.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/mpd_manual.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/network.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/network_edit.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/network_edit_manual.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/network_wlan.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/playback.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/settings.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/sources.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/sources_edit.php');
    -            // Vendor libs
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/autoload.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Engine.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Template.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/Escape.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/Batch.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/ExtensionInterface.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/Asset.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/URI.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_namespaces.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_files.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_real.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/ClassLoader.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_classmap.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_psr4.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.tag.id3v2.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/audioinfo.class.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.tag.id3v1.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/getid3.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.audio.ogg.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.tag.apetag.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/getid3.lib.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.audio.flac.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.tag.lyrics3.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/react/promise/src/React/Promise/functions.php');
    -            // RuneAudio Commands
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/command/refresh_nics');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/command/ui_notify.php');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/command/debug_collector');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/command/opcache.php');
    -            // RuneAudio Workers
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/command/rune_PL_wrk');
    -            $transaction->sAdd('php_opcache_prime', '/srv/http/command/rune_SY_wrk');
    -            $transaction->exec();
    -            sysCmd('redis-cli KEYS "mount_*" | xargs redis-cli DEL');
    -            break;
    -        case 'check':
    -            // kernel
    -            $redis->get('kernel') || $redis->set('kernel', 'linux-arch-rpi_3.12.26-1-ARCH');
    -            $redis->get('i2smodule') || $redis->set('i2smodule', 'none');
    -            $redis->get('orionprofile') || $redis->set('orionprofile', 'RuneAudio');
    -            $redis->get('activePlayer') || $redis->set('activePlayer', 'MPD');
    -            // player features
    -            $redis->get('hostname') || $redis->set('hostname', 'runeaudio');
    -            $redis->get('ntpserver') || $redis->set('ntpserver', 'pool.ntp.org');
    -            $redis->hGet('airplay', 'enable') || $redis->hSet('airplay', 'enable', 1);
    -            $redis->hGet('airplay', 'name') || $redis->hSet('airplay', 'name', 'RuneAudio');        
    -            $redis->hGet('dlna', 'enable') || $redis->hSet('dlna', 'enable', 0);
    -            $redis->hGet('dlna', 'name') || $redis->hSet('dlna', 'name', 'RuneAudio');
    -            $redis->hGet('dlna', 'queue') || $redis->hSet('dlna', 'queue', 1);
    -            $redis->hGet('dlna', 'logfile') || $redis->hSet('dlna', 'logfile', '/var/log/runeaudio/upmpdcli.log');
    -            $redis->hGet('dlna', 'loglevel') || $redis->hSet('dlna', 'loglevel', 0);
    -            $redis->hGet('spotify', 'enable') || $redis->hSet('spotify', 'enable', 0);
    -            $redis->hGet('spotify', 'user') || $redis->hSet('spotify', 'user', 'user');
    -            $redis->hGet('spotify', 'pass') || $redis->hSet('spotify', 'pass', 'pass');
    -            $redis->hGet('spotify', 'plversion') || $redis->hSet('spotify', 'plversion', 1);
    -            $redis->hGet('spotify', 'lastcmd') || $redis->hSet('spotify', 'lastcmd', '');
    -            $redis->get('udevil') || $redis->set('udevil', 1);
    -            $redis->get('coverart') || $redis->set('coverart', 1);
    -            $redis->get('playmod') || $redis->set('playmod', 0);
    -            $redis->get('ramplay') || $redis->set('ramplay', 0);
    -            $redis->hGet('lastfm', 'enable') || $redis->hSet('lastfm', 'enable', 0);
    -            $redis->hGet('lastfm', 'username') || $redis->hSet('lastfm', 'username', '');
    -            $redis->hGet('lastfm', 'password') || $redis->hSet('lastfm', 'password', '');
    -            $redis->get('cmediafix') || $redis->set('cmediafix', 0);
    -            $redis->get('globalrandom') || $redis->set('globalrandom', 0);
    -            $redis->get('stoppedPlayer') || $redis->set('stoppedPlayer', '');
    -            // plugins api-keys
    -            $redis->get('lastfm_apikey') || $redis->set('lastfm_apikey', 'ba8ad00468a50732a3860832eaed0882');
    -            $redis->hGet('lastfm', 'apikey') || $redis->hSet('lastfm', 'apikey', 'ba8ad00468a50732a3860832eaed0882');
    -            $redis->hGet('jamendo', 'clientid') || $redis->hSet('jamendo', 'clientid', '5f3ed86c');
    -            $redis->hGet('jamendo', 'secret') || $redis->hSet('jamendo', 'secret', '1afcdcb13eb5ce8f6e534ac4566a3ab9');
    -            $redis->hGet('dirble','apikey') || $redis->hSet('dirble', 'apikey', '134aabbce2878ce0dbfdb23fb3b46265eded085b');
    -            // internal config hash control
    -            $redis->get('mpdconfhash') || $redis->set('mpdconfhash', '12eed229f02c52816ed997cbce4b9f32');
    -            $redis->hGet('netconfhash') || $redis->hSet('netconfhash', 'eth0', '643f8967af551f683b3cfd493950c550');
    -            $redis->get('mpdconf_advanced') || $redis->set('mpdconf_advanced', 0);
    -            $redis->get('netconf_advanced') || $redis->set('netconf_advanced', 0);
    -            // MPD parameters
    -            $redis->get('mpd_playback_status') || $redis->set('mpd_playback_status', '');
    -            // developer parameters
    -            $redis->get('dev') || $redis->set('dev', 0);
    -            $redis->get('debug') || $redis->set('debug', 0);
    -            $redis->get('debugdata') || $redis->set('debugdata', '');
    -            $redis->get('opcache') || $redis->set('opcache', 1);
    -            $redis->get('buildversion') || $redis->set('buildversion', 'alpha-20141002');
    -            // HW platform data
    -            $redis->get('playerid') || $redis->set('playerid', '');
    -            $redis->get('hwplatform') || $redis->set('hwplatform', '');
    -            $redis->get('hwplatformid') || $redis->set('hwplatformid', '');
    -            // player control
    -            $redis->get('ao') || $redis->set('ao', 1);
    -            $redis->get('volume') || $redis->set('volume', 0);
    -            $redis->get('pl_length') || $redis->set('pl_length', 0);
    -            $redis->get('nextsongid') || $redis->set('nextsongid', 0);
    -            $redis->get('lastsongid') || $redis->set('lastsongid', 0);
    -            // network parameters
    -            $redis->hGet('resolvconf', 'timeout') || $redis->hSet('resolvconf', 'timeout', '0.3');
    -            $redis->hGet('resolvconf', 'attempts') || $redis->hSet('resolvconf', 'attempts', '1');
    -            // lock keys
    -            $redis->get('lock_globalrandom') || $redis->set('globalrandom_lock', 0);
    -            $redis->get('lock_refresh_ao') || $redis->set('lock_refresh_ao', 0);
    -            $redis->get('lock_wifiscan') || $redis->set('lock_wifiscan', 0);
    -            break;
    -    }
    -}
    -// inspect console command
    -if (isset($argv[1])) {
    -    switch ($argv[1]) {
    -        case 'reset':
    -            redisDatastore($redis,'reset');
    -            sysCmd('/srv/http/db/redis_acards_details');
    -            break;
    -        case 'check':
    -            redisDatastore($redis,'check');
    -            break;
    -    }
    -} else {
    -    echo "RuneAudio Datastore reset\nredis_datastore_setup {reset|check}\n";
    -}
    +#!/usr/bin/php
    +.
    + *
    + *  file: db/redis_datastore_setup
    + *  version: 1.3
    + *  coder: Simone De Gregori
    + *
    + */
    +// common include
    +include('/srv/http/app/config/config.php');
    +// ini_set('display_errors',1);
    +// error_reporting('E_ALL');
    +function redisDatastore($redis,$action) {
    +    switch ($action) {
    +        case 'reset':
    +            // kernel
    +            $redis->set('kernel', 'linux-arch-rpi_3.12.26-1-ARCH');
    +            $redis->set('i2smodule', 'none');
    +            $redis->set('orionprofile', 'RuneAudio');
    +            $redis->set('activePlayer', 'MPD');
    +            // player features
    +            $redis->set('hostname', 'runeaudio');
    +            $redis->set('ntpserver', 'pool.ntp.org');
    +            $redis->hSet('airplay', 'enable', 1);
    +            $redis->hSet('airplay', 'name', 'RuneAudio');
    +            $redis->hSet('dlna', 'enable', 0);
    +            $redis->hSet('dlna', 'name', 'RuneAudio');
    +            $redis->hSet('dlna', 'queue', 1);
    +            $redis->hSet('dlna', 'logfile', '/var/log/runeaudio/upmpdcli.log');
    +            $redis->hSet('dlna', 'loglevel', 6);
    +            $redis->set('udevil', 1);
    +            $redis->hSet('spotify', 'enable', 0);
    +            $redis->hSet('spotify', 'user', 'user');
    +            $redis->hSet('spotify', 'pass', 'pass'); 
    +            $redis->hSet('spotify', 'plversion', 1);
    +            $redis->hSet('spotify', 'lastcmd', '');            
    +            $redis->set('coverart', 1);
    +            $redis->set('playmod', 0);
    +            $redis->set('ramplay', 0);
    +            $redis->hSet('lastfm', 'enable', 0);
    +            $redis->hSet('lastfm', 'user', 'user');
    +            $redis->hSet('lastfm', 'pass', 'pass');
    +            $redis->set('cmediafix', 0);
    +            $redis->set('globalrandom', 0);
    +            $redis->set('stoppedPlayer', '');    
    +            // default MPD config
    +            $redis->hSet('mpdconf', 'zeroconf_enabled', 'yes');
    +            $redis->hSet('mpdconf', 'zeroconf_name', 'RuneAudio');
    +            $redis->hSet('mpdconf', 'log_level', 'none');
    +            $redis->hSet('mpdconf', 'bind_to_address', 'any');
    +            $redis->hSet('mpdconf', 'port', '6600');
    +            $redis->hSet('mpdconf', 'max_connections', '20');
    +            $redis->hSet('mpdconf', 'user', 'mpd');
    +            $redis->hSet('mpdconf', 'db_file', '/var/lib/mpd/mpd.db');
    +            $redis->hSet('mpdconf', 'sticker_file', '/var/lib/mpd/sticker.sql');
    +            $redis->hSet('mpdconf', 'pid_file', '/var/run/mpd/pid');
    +            $redis->hSet('mpdconf', 'music_directory', '/mnt/MPD');
    +            $redis->hSet('mpdconf', 'playlist_directory', '/var/lib/mpd/playlists');
    +            $redis->hSet('mpdconf', 'state_file', '/var/lib/mpd/mpdstate');
    +            $redis->hSet('mpdconf', 'follow_outside_symlinks', 'yes');
    +            $redis->hSet('mpdconf', 'follow_inside_symlinks', 'yes');
    +            $redis->hSet('mpdconf', 'auto_update', 'no');
    +            $redis->hSet('mpdconf', 'filesystem_charset', 'UTF-8');
    +            $redis->hSet('mpdconf', 'id3v1_encoding', 'UTF-8');
    +            $redis->hSet('mpdconf', 'volume_normalization', 'no');
    +            $redis->hSet('mpdconf', 'audio_buffer_size', '2048');
    +            $redis->hSet('mpdconf', 'buffer_before_play', '10%');
    +            $redis->hSet('mpdconf', 'gapless_mp3_playback', 'yes');
    +            $redis->hSet('mpdconf', 'mixer_type', 'software');
    +            $redis->hSet('mpdconf', 'curl', 'yes');
    +            $redis->hSet('mpdconf', 'ffmpeg', 'yes');
    +            $redis->hSet('mpdconf', 'log_file', '/var/log/runeaudio/mpd.log');
    +            // plugins api-keys
    +            $redis->set('lastfm_apikey', 'ba8ad00468a50732a3860832eaed0882');
    +            $redis->hSet('lastfm', 'apikey', 'ba8ad00468a50732a3860832eaed0882');
    +            $redis->hSet('spotify', 'clientid', 'c236e0f5a84e48a3a7f6c730f684f255');
    +            $redis->hSet('jamendo', 'clientid', '5f3ed86c');
    +            $redis->hSet('jamendo', 'secret', '1afcdcb13eb5ce8f6e534ac4566a3ab9');
    +            $redis->hSet('dirble', 'apikey', '134aabbce2878ce0dbfdb23fb3b46265eded085b');
    +            $redis->hSet('dirble', 'baseurl', 'http://api.dirble.com/v1/');
    +            // internal config hash control
    +            // $redis->set('mpdconfhash', '');
    +            // $redis->set('netconfhash', '');
    +            $redis->set('mpdconfhash', '12eed229f02c52816ed997cbce4b9f32');
    +            $redis->hSet('netconfhash', 'eth0', '643f8967af551f683b3cfd493950c550');
    +            $redis->set('mpdconf_advanced', 0);
    +            $redis->set('netconf_advanced', 0);
    +            // MPD parameters
    +            wrk_mpdconf($redis, 'writecfg');
    +            $redis->set('mpd_playback_status', '');
    +            // developer parameters
    +            $redis->set('dev', 0);
    +            $redis->set('debug', 0);
    +            $redis->set('debugdata', '');
    +            $redis->set('opcache', 1);
    +            $redis->set('buildversion', 'beta-20141216');
    +            $redis->set('release', '0.4');
    +            // HW platform data
    +            // $redis->set('playerid', '');
    +            // $redis->set('hwplatform', '');
    +            // $redis->set('hwplatformid', '');
    +            // player control
    +            if ($redis->get('hwplatformid') === '01') {
    +                $redis->set('ao', 'bcm2835 ALSA_1');
    +            } else {
    +                $redis->set('ao', '');
    +            }
    +            $redis->set('volume', 0);
    +            $redis->set('pl_length', 0);
    +            $redis->set('nextsongid', 0);
    +            $redis->set('lastsongid', 0);
    +            // network parameters
    +            $redis->hSet('resolvconf', 'timeout', '0.3');
    +            $redis->hSet('resolvconf', 'attempts', '1');
    +            // proxy settings
    +            $redis->hSet('proxy', 'host', '');
    +            $redis->hSet('proxy', 'user', '');
    +            $redis->hSet('proxy', 'pass', '');
    +            // lock keys
    +            $redis->set('lock_globalrandom', 0);
    +            $redis->set('lock_refresh_ao', 0);
    +            $redis->set('lock_wifiscan', 0);
    +            // env settings
    +            $redis->del('usbmounts');
    +            $redis->set('wlans', '');
    +            $redis->del('acards');
    +            $redis->set('mountidx', 1);
    +            $redis->set('debugdata', '');
    +            // PHP precache (prime) scripts
    +            $redis->del('php_opcache_prime');
    +            $transaction = $redis->multi();
    +            // RuneAudio Libs & config
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/config/config.php');
    +            // RuneAudio Controllers
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/index.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/db/index.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/command/index.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/coverart_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/credits_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/debug_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/dev_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/login_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/mpd_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/network_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/playback_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/settings_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/sources_ctl.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/tun_ctl.php');
    +            // RuneAudio Templates
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/credits.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/debug.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/default_lo.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/dev.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/error.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/footer.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/header.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/login.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/mpd.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/mpd_manual.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/network.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/network_edit.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/network_edit_manual.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/network_wlan.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/playback.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/settings.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/sources.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/templates/sources_edit.php');
    +            // Vendor libs
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/autoload.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Engine.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Template.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/Escape.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/Batch.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/ExtensionInterface.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/Asset.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/league/plates/src/Extension/URI.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_namespaces.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_files.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_real.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/ClassLoader.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_classmap.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/composer/autoload_psr4.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.tag.id3v2.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/audioinfo.class.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.tag.id3v1.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/getid3.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.audio.ogg.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.tag.apetag.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/getid3.lib.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.audio.flac.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/getid3/module.tag.lyrics3.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/app/libs/vendor/react/promise/src/React/Promise/functions.php');
    +            // RuneAudio Commands
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/command/refresh_nics');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/command/ui_notify.php');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/command/debug_collector');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/command/opcache.php');
    +            // RuneAudio Workers
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/command/rune_PL_wrk');
    +            $transaction->sAdd('php_opcache_prime', '/srv/http/command/rune_SY_wrk');
    +            $transaction->exec();
    +            sysCmd('redis-cli KEYS "mount_*" | xargs redis-cli DEL');
    +            break;
    +        case 'check':
    +            // kernel
    +            $redis->get('kernel') || $redis->set('kernel', 'linux-arch-rpi_3.12.26-1-ARCH');
    +            $redis->get('i2smodule') || $redis->set('i2smodule', 'none');
    +            $redis->get('orionprofile') || $redis->set('orionprofile', 'RuneAudio');
    +            $redis->get('activePlayer') || $redis->set('activePlayer', 'MPD');
    +            // player features
    +            $redis->get('hostname') || $redis->set('hostname', 'runeaudio');
    +            $redis->get('ntpserver') || $redis->set('ntpserver', 'pool.ntp.org');
    +            $redis->hGet('airplay', 'enable') || $redis->hSet('airplay', 'enable', 1);
    +            $redis->hGet('airplay', 'name') || $redis->hSet('airplay', 'name', 'RuneAudio');        
    +            $redis->hGet('dlna', 'enable') || $redis->hSet('dlna', 'enable', 0);
    +            $redis->hGet('dlna', 'name') || $redis->hSet('dlna', 'name', 'RuneAudio');
    +            $redis->hGet('dlna', 'queue') || $redis->hSet('dlna', 'queue', 1);
    +            $redis->hGet('dlna', 'logfile') || $redis->hSet('dlna', 'logfile', '/var/log/runeaudio/upmpdcli.log');
    +            $redis->hGet('dlna', 'loglevel') || $redis->hSet('dlna', 'loglevel', 0);
    +            $redis->hGet('spotify', 'enable') || $redis->hSet('spotify', 'enable', 0);
    +            $redis->hGet('spotify', 'user') || $redis->hSet('spotify', 'user', 'user');
    +            $redis->hGet('spotify', 'pass') || $redis->hSet('spotify', 'pass', 'pass');
    +            $redis->hGet('spotify', 'plversion') || $redis->hSet('spotify', 'plversion', 1);
    +            $redis->hGet('spotify', 'lastcmd') || $redis->hSet('spotify', 'lastcmd', '');
    +            $redis->get('udevil') || $redis->set('udevil', 1);
    +            $redis->get('coverart') || $redis->set('coverart', 1);
    +            $redis->get('playmod') || $redis->set('playmod', 0);
    +            $redis->get('ramplay') || $redis->set('ramplay', 0);
    +            $redis->hGet('lastfm', 'enable') || $redis->hSet('lastfm', 'enable', 0);
    +            $redis->hGet('lastfm', 'username') || $redis->hSet('lastfm', 'username', '');
    +            $redis->hGet('lastfm', 'password') || $redis->hSet('lastfm', 'password', '');
    +            $redis->get('cmediafix') || $redis->set('cmediafix', 0);
    +            $redis->get('globalrandom') || $redis->set('globalrandom', 0);
    +            $redis->get('stoppedPlayer') || $redis->set('stoppedPlayer', '');
    +            // plugins api-keys
    +            $redis->get('lastfm_apikey') || $redis->set('lastfm_apikey', 'ba8ad00468a50732a3860832eaed0882');
    +            $redis->hGet('lastfm', 'apikey') || $redis->hSet('lastfm', 'apikey', 'ba8ad00468a50732a3860832eaed0882');
    +            $redis->hGet('jamendo', 'clientid') || $redis->hSet('jamendo', 'clientid', '5f3ed86c');
    +            $redis->hGet('jamendo', 'secret') || $redis->hSet('jamendo', 'secret', '1afcdcb13eb5ce8f6e534ac4566a3ab9');
    +            $redis->hGet('dirble','apikey') || $redis->hSet('dirble', 'apikey', '134aabbce2878ce0dbfdb23fb3b46265eded085b');
    +            // internal config hash control
    +            $redis->get('mpdconfhash') || $redis->set('mpdconfhash', '12eed229f02c52816ed997cbce4b9f32');
    +            // KEW: Missing 'eth0'
    +            $redis->hGet('netconfhash', 'eth0') || $redis->hSet('netconfhash', 'eth0', '643f8967af551f683b3cfd493950c550');
    +            // END
    +            $redis->get('mpdconf_advanced') || $redis->set('mpdconf_advanced', 0);
    +            $redis->get('netconf_advanced') || $redis->set('netconf_advanced', 0);
    +            // MPD parameters
    +            $redis->get('mpd_playback_status') || $redis->set('mpd_playback_status', '');
    +            // developer parameters
    +            $redis->get('dev') || $redis->set('dev', 0);
    +            $redis->get('debug') || $redis->set('debug', 0);
    +            $redis->get('debugdata') || $redis->set('debugdata', '');
    +            $redis->get('opcache') || $redis->set('opcache', 1);
    +            $redis->get('buildversion') || $redis->set('buildversion', 'alpha-20141002');
    +            // HW platform data
    +            $redis->get('playerid') || $redis->set('playerid', '');
    +            $redis->get('hwplatform') || $redis->set('hwplatform', '');
    +            $redis->get('hwplatformid') || $redis->set('hwplatformid', '');
    +            // player control
    +            $redis->get('ao') || $redis->set('ao', 1);
    +            $redis->get('volume') || $redis->set('volume', 0);
    +            $redis->get('pl_length') || $redis->set('pl_length', 0);
    +            $redis->get('nextsongid') || $redis->set('nextsongid', 0);
    +            $redis->get('lastsongid') || $redis->set('lastsongid', 0);
    +            // network parameters
    +            $redis->hGet('resolvconf', 'timeout') || $redis->hSet('resolvconf', 'timeout', '0.3');
    +            $redis->hGet('resolvconf', 'attempts') || $redis->hSet('resolvconf', 'attempts', '1');
    +            // lock keys
    +            $redis->get('lock_globalrandom') || $redis->set('globalrandom_lock', 0);
    +            $redis->get('lock_refresh_ao') || $redis->set('lock_refresh_ao', 0);
    +            $redis->get('lock_wifiscan') || $redis->set('lock_wifiscan', 0);
    +            break;
    +    }
    +}
    +// inspect console command
    +if (isset($argv[1])) {
    +    switch ($argv[1]) {
    +        case 'reset':
    +            redisDatastore($redis,'reset');
    +            sysCmd('/srv/http/db/redis_acards_details');
    +            break;
    +        case 'check':
    +            redisDatastore($redis,'check');
    +            break;
    +    }
    +} else {
    +    echo "RuneAudio Datastore reset\nredis_datastore_setup {reset|check}\n";
    +}
    diff --git a/index.php b/index.php
    index dc2c12b4..d9a37b1a 100755
    --- a/index.php
    +++ b/index.php
    @@ -43,6 +43,7 @@
     $engine->loadExtension(new \League\Plates\Extension\Asset('/srv/http/assets', true));
     // plates: load URI extension
     $engine->loadExtension(new \League\Plates\Extension\URI($_SERVER['REQUEST_URI']));
    +//$engine->loadExtension(new \League\Plates\Extension\URI($request->getPathInfo()));
     // plates: create a new template
     $template = new \League\Plates\Template($engine);
     // set devmode
    @@ -53,8 +54,10 @@
     $template->activePlayer = $activePlayer;
     // allowed controllers
     $controllers = array(
    -    'credits',
    +    'api',
    +    'config',
         'coverart',
    +    'credits',
         'dev',
         'debug',
         'help',
    @@ -69,21 +72,24 @@
     );
     // check page
     if (in_array($template->uri(1), $controllers) OR empty($template->uri(1))) {
    -    // decode REQUEST_URL and assing section
    +    // decode REQUEST_URL and assign section
         if (!empty($template->uri(1)) && ($template->uri(1) !== 'playback')) {
             // decode ACTION
             if (!empty($template->uri(2))) {
    -        $template->action = $template->uri(2);
    -                // assign SUB-TEMPLATE
    -                if ($template->action === 'add') {
    -                    $subtpl = 'edit';
    -                } else {
    -                    $subtpl = $template->action;
    -                }
    -            // decode ARG
    -            if(!empty($template->uri(3))) {
    -                $template->arg = $template->uri(3);
    +            $template->action = $template->uri(2);
    +            // assign SUB-TEMPLATE
    +            if ($template->action === 'add') {
    +                $subtpl = 'edit';
    +            } else {
    +                $subtpl = $template->action;
                 }
    +            // decode ARG
    +            //if(!empty($template->uri(3))) {
    +                //$temp = $template->uri(3);
    +            $template->arg = $template->uri(3); //, '0', ':)', $temp); // pass a '0' if we have a 0 in the URL
    +            //} else {
    +            //    $template->arg = 'empty as empty can be';
    +            //}
                 // assign TEMPLATE
                 $template->content = $template->uri(1).'_'.$subtpl;
             } else {
    @@ -99,7 +105,7 @@
             include(APP.$template->uri(1).'_ctl.php');
             // register current controller in SESSION
             if ($template->uri(1) !== 'coverart' && $template->uri(1) !== 'coverart2') {
    -        $_SESSION['controller'] = $template->uri(1);
    +            $_SESSION['controller'] = $template->uri(1);
             }
         } else {
             // debug
    @@ -122,6 +128,8 @@
     // plates: render layout (if you want to output direct, set $tplfile = 0 into controller)
     if ($tplfile !== 0) {
         echo $template->render('default_lo');
    +} elseif ($template->uri(1) === 'api') {
    +    echo $template->render('api_lo');
     }
     // close palyer backend connection
     if ($activePlayer === 'MPD') {
    diff --git a/manifest.webapp b/manifest.webapp
    index dfc4d8d7..05f1b30c 100644
    --- a/manifest.webapp
    +++ b/manifest.webapp
    @@ -1,24 +1,24 @@
    -{
    -  "version": "0.3",
    -  "name": "RuneAudio",
    -  "launch_path": "/",
    -  "description": "Remote control for your RuneAudio system!",
    -  "icons": {
    -    "16": "/assets/img/favicon-16x16.png",
    -    "32": "/assets/img/favicon-32x32.png",
    -    "48": "/assets/img/favicon-48x48.png",
    -    "60": "/assets/img/favicon-60x60.png",
    -    "64": "/assets/img/favicon-64x64.png",
    -    "90": "/assets/img/favicon-90x90.png",
    -    "120": "/assets/img/favicon-120x120.png",
    -    "128": "/assets/img/favicon-128x128.png"
    -  },
    -  "developer": {
    -    "name": "RuneAudio",
    -    "url": "http://www.runeaudio.com"
    -  },
    -  "installs_allowed_from": [
    -    "*"
    -  ],
    -  "default_locale": "en"
    -} 
    +{
    +  "version": "0.3",
    +  "name": "RuneAudio",
    +  "launch_path": "/",
    +  "description": "Remote control for your RuneAudio system!",
    +  "icons": {
    +    "16": "/assets/img/favicon-16x16.png",
    +    "32": "/assets/img/favicon-32x32.png",
    +    "48": "/assets/img/favicon-48x48.png",
    +    "60": "/assets/img/favicon-60x60.png",
    +    "64": "/assets/img/favicon-64x64.png",
    +    "90": "/assets/img/favicon-90x90.png",
    +    "120": "/assets/img/favicon-120x120.png",
    +    "128": "/assets/img/favicon-128x128.png"
    +  },
    +  "developer": {
    +    "name": "RuneAudio",
    +    "url": "http://www.runeaudio.com"
    +  },
    +  "installs_allowed_from": [
    +    "*"
    +  ],
    +  "default_locale": "en"
    +}