├── .gitignore ├── LICENSE ├── README.md ├── admin ├── config.py ├── config.yml.sample ├── database.py ├── model.py ├── reference_settings.yml ├── searx_manager.py ├── static │ ├── css │ │ ├── main.css │ │ ├── material.css │ │ ├── material.min.css │ │ └── material.min.css.map │ └── js │ │ ├── material.js │ │ ├── material.min.js │ │ └── material.min.js.map ├── templates │ ├── base.html │ ├── edit_engine.html │ ├── engines.html │ ├── index.html │ ├── macros.html │ ├── manage.html │ ├── outgoing.html │ ├── search.html │ ├── security │ │ ├── login_user.html │ │ └── register_user.html │ ├── server.html │ └── ui.html └── webapp.py ├── docs └── images │ └── engineslist.png └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | admin/config.yml 2 | admin/searx_generated_settings.yml 3 | admin/searx_uwsgi.ini 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # searx admin 2 | 3 | A web based management interface for searx. 4 | 5 | ![engineslist](docs/images/engineslist.png) 6 | 7 | ## Features 8 | 9 | - Edit configuration 10 | - Update from remote repository 11 | - Manage process (start, stop, reload) 12 | 13 | ## Installation & usage 14 | 15 | ### Install dependencies 16 | 17 | Searx-admin depends on `git` and `uwsgi` tools and implemented in python. 18 | 19 | Please make sure that dependencies of searx are installed in the same virtualenv or on the 20 | same host searx admin is ran. 21 | 22 | ``` 23 | virtualenv venv 24 | source venv/bin/activate 25 | pip install -r requirements.txt 26 | ``` 27 | 28 | ### Edit config 29 | 30 | Edit `admin/config.yml` 31 | 32 | 33 | ### Start application 34 | 35 | ``` 36 | source venv/bin/activate 37 | python admin/webapp.py 38 | ``` 39 | -------------------------------------------------------------------------------- /admin/config.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | 4 | with open('admin/config.yml') as config_file: 5 | configuration = yaml.load(config_file) 6 | -------------------------------------------------------------------------------- /admin/config.yml.sample: -------------------------------------------------------------------------------- 1 | app: 2 | port: 8889 3 | secretkey: 'my-dirty-secret' 4 | database_connection_string: 'sqlite:////tmp/users.db' 5 | 6 | searx: 7 | root: '/path/to/searx' 8 | uwsgi_extra_args: ['--venv', 'env'] 9 | -------------------------------------------------------------------------------- /admin/database.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.orm import scoped_session, sessionmaker 3 | from sqlalchemy.ext.declarative import declarative_base 4 | 5 | from config import configuration 6 | 7 | engine = create_engine(configuration['app']['database_connection_string'], convert_unicode=True) 8 | db_session = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine)) 9 | Base = declarative_base() 10 | Base.query = db_session.query_property() 11 | 12 | 13 | def init_db(): 14 | Base.metadata.create_all(bind=engine) 15 | -------------------------------------------------------------------------------- /admin/model.py: -------------------------------------------------------------------------------- 1 | from database import Base 2 | from flask_security import UserMixin, RoleMixin 3 | from sqlalchemy.orm import relationship, backref 4 | from sqlalchemy import Boolean, DateTime, Column, Integer, String, ForeignKey 5 | 6 | 7 | class RolesUsers(Base): 8 | __tablename__ = 'roles_users' 9 | id = Column(Integer(), primary_key=True) 10 | user_id = Column('user_id', Integer(), ForeignKey('user.id')) 11 | role_id = Column('role_id', Integer(), ForeignKey('role.id')) 12 | 13 | 14 | class Role(Base, RoleMixin): 15 | __tablename__ = 'role' 16 | id = Column(Integer(), primary_key=True) 17 | name = Column(String(80), unique=True) 18 | description = Column(String(255)) 19 | 20 | 21 | class User(Base, UserMixin): 22 | __tablename__ = 'user' 23 | id = Column(Integer, primary_key=True) 24 | email = Column(String(255), unique=True) 25 | username = Column(String(255)) 26 | password = Column(String(255)) 27 | last_login_at = Column(DateTime()) 28 | current_login_at = Column(DateTime()) 29 | last_login_ip = Column(String(100)) 30 | current_login_ip = Column(String(100)) 31 | active = Column(Boolean()) 32 | confirmed_at = Column(DateTime()) 33 | roles = relationship('Role', secondary='roles_users', backref=backref('users', lazy='dynamic')) 34 | -------------------------------------------------------------------------------- /admin/reference_settings.yml: -------------------------------------------------------------------------------- 1 | general: 2 | debug : True # Debug mode, only for development 3 | instance_name : "searx" # displayed name 4 | 5 | search: 6 | safe_search : 0 # Filter results. 0: None, 1: Moderate, 2: Strict 7 | autocomplete : "" # Existing autocomplete backends: "dbpedia", "duckduckgo", "google", "startpage", "wikipedia" - leave blank to turn it off by default 8 | language : "all" 9 | 10 | server: 11 | port : 8888 12 | bind_address : "127.0.0.1" # address to listen on 13 | secret_key : "ultrasecretkey" # change this! 14 | base_url : False # Set custom base_url. Possible values: False or "https://your.custom.host/location/" 15 | image_proxy : False # Proxying image results through searx 16 | http_protocol_version : "1.0" # 1.0 and 1.1 are supported 17 | 18 | ui: 19 | static_path : "" # Custom static path - leave it blank if you didn't change 20 | templates_path : "" # Custom templates path - leave it blank if you didn't change 21 | default_theme : oscar # ui theme 22 | default_locale : "" # Default interface locale - leave blank to detect from browser information or use codes from the 'locales' config section 23 | 24 | # searx supports result proxification using an external service: https://github.com/asciimoo/morty 25 | result_proxy: 26 | url : "" 27 | key : "" 28 | 29 | outgoing: # communication with search engines 30 | request_timeout : 2.0 # seconds 31 | useragent_suffix : "" # suffix of searx_useragent, could contain informations like an email address to the administrator 32 | pool_connections : 100 # Number of different hosts 33 | pool_maxsize : 10 # Number of simultaneous requests by host 34 | # see http://docs.python-requests.org/en/latest/user/advanced/#proxies 35 | # SOCKS proxies are also supported: see http://docs.python-requests.org/en/master/user/advanced/#socks 36 | proxies : 37 | http : "" 38 | https : "" 39 | # If you have more than one public IP address, here you can specify 40 | # which can be the source of outgoing search requests 41 | # leave it blank if you have only one public IP. 42 | source_ips: [] 43 | 44 | engines: 45 | - name : arch linux wiki 46 | engine : archlinux 47 | shortcut : al 48 | 49 | - name : archive is 50 | engine : xpath 51 | search_url : https://archive.is/{query} 52 | url_xpath : (//div[@class="TEXT-BLOCK"]/a)/@href 53 | title_xpath : (//div[@class="TEXT-BLOCK"]/a) 54 | content_xpath : //div[@class="TEXT-BLOCK"]/ul/li 55 | categories : general 56 | timeout : 7.0 57 | disabled : True 58 | shortcut : ai 59 | 60 | - name : base 61 | engine : base 62 | shortcut : bs 63 | 64 | - name : wikipedia 65 | engine : wikipedia 66 | shortcut : wp 67 | base_url : 'https://{language}.wikipedia.org/' 68 | 69 | - name : bing 70 | engine : bing 71 | shortcut : bi 72 | 73 | - name : bing images 74 | engine : bing_images 75 | shortcut : bii 76 | 77 | - name : bing news 78 | engine : bing_news 79 | shortcut : bin 80 | 81 | - name : bitbucket 82 | engine : xpath 83 | paging : True 84 | search_url : https://bitbucket.org/repo/all/{pageno}?name={query} 85 | url_xpath : //article[@class="repo-summary"]//a[@class="repo-link"]/@href 86 | title_xpath : //article[@class="repo-summary"]//a[@class="repo-link"] 87 | content_xpath : //article[@class="repo-summary"]/p 88 | categories : it 89 | timeout : 4.0 90 | disabled : True 91 | shortcut : bb 92 | 93 | - name : ccc-tv 94 | engine : xpath 95 | paging : False 96 | search_url : https://media.ccc.de/search/?q={query} 97 | url_xpath : //div[@class="caption"]/h3/a/@href 98 | title_xpath : //div[@class="caption"]/h3/a/text() 99 | content_xpath : //div[@class="caption"]/h4/@title 100 | categories : videos 101 | disabled : True 102 | shortcut : c3tv 103 | 104 | - name : crossref 105 | engine : json_engine 106 | paging : True 107 | search_url : http://search.crossref.org/dois?q={query}&page={pageno} 108 | url_query : doi 109 | title_query : title 110 | content_query : fullCitation 111 | categories : science 112 | shortcut : cr 113 | 114 | - name : currency 115 | engine : currency_convert 116 | categories : general 117 | shortcut : cc 118 | 119 | - name : deezer 120 | engine : deezer 121 | shortcut : dz 122 | 123 | - name : deviantart 124 | engine : deviantart 125 | shortcut : da 126 | timeout: 3.0 127 | 128 | - name : ddg definitions 129 | engine : duckduckgo_definitions 130 | shortcut : ddd 131 | weight : 2 132 | disabled : True 133 | 134 | - name : digbt 135 | engine : digbt 136 | shortcut : dbt 137 | timeout : 6.0 138 | disabled : True 139 | 140 | - name : digg 141 | engine : digg 142 | shortcut : dg 143 | 144 | - name : erowid 145 | engine : xpath 146 | paging : True 147 | first_page_num : 0 148 | page_size : 30 149 | search_url : https://www.erowid.org/search.php?q={query}&s={pageno} 150 | url_xpath : //dl[@class="results-list"]/dt[@class="result-title"]/a/@href 151 | title_xpath : //dl[@class="results-list"]/dt[@class="result-title"]/a/text() 152 | content_xpath : //dl[@class="results-list"]/dd[@class="result-details"] 153 | categories : general 154 | shortcut : ew 155 | disabled : True 156 | 157 | - name : wikidata 158 | engine : wikidata 159 | shortcut : wd 160 | weight : 2 161 | 162 | - name : duckduckgo 163 | engine : duckduckgo 164 | shortcut : ddg 165 | disabled : True 166 | 167 | - name : duckduckgo images 168 | engine : duckduckgo_images 169 | shortcut : ddi 170 | timeout: 3.0 171 | disabled : True 172 | 173 | - name : etymonline 174 | engine : xpath 175 | paging : True 176 | search_url : http://etymonline.com/?search={query}&p={pageno} 177 | url_xpath : //dt/a[1]/@href 178 | title_xpath : //dt 179 | content_xpath : //dd 180 | suggestion_xpath : //a[@class="crossreference"] 181 | first_page_num : 0 182 | shortcut : et 183 | disabled : True 184 | 185 | # api-key required: http://www.faroo.com/hp/api/api.html#key 186 | # - name : faroo 187 | # engine : faroo 188 | # shortcut : fa 189 | # api_key : 'apikey' # required! 190 | 191 | - name : 500px 192 | engine : www500px 193 | shortcut : px 194 | 195 | - name : 1x 196 | engine : www1x 197 | shortcut : 1x 198 | disabled : True 199 | 200 | - name : fdroid 201 | engine : fdroid 202 | shortcut : fd 203 | disabled : True 204 | 205 | - name : flickr 206 | categories : images 207 | shortcut : fl 208 | # You can use the engine using the official stable API, but you need an API key 209 | # See : https://www.flickr.com/services/apps/create/ 210 | # engine : flickr 211 | # api_key: 'apikey' # required! 212 | # Or you can use the html non-stable engine, activated by default 213 | engine : flickr_noapi 214 | 215 | - name : free software directory 216 | engine : mediawiki 217 | shortcut : fsd 218 | categories : it 219 | base_url : https://directory.fsf.org/ 220 | number_of_results : 5 221 | # what part of a page matches the query string: title, text, nearmatch 222 | # title - query matches title, text - query matches the text of page, nearmatch - nearmatch in title 223 | search_type : title 224 | timeout : 5.0 225 | disabled : True 226 | 227 | - name : frinkiac 228 | engine : frinkiac 229 | shortcut : frk 230 | disabled : True 231 | 232 | - name : gigablast 233 | engine : gigablast 234 | shortcut : gb 235 | timeout : 3.0 236 | disabled: True 237 | 238 | - name : gitlab 239 | engine : xpath 240 | paging : True 241 | search_url : https://gitlab.com/search?page={pageno}&search={query} 242 | url_xpath : //li[@class="project-row"]//a[@class="project"]/@href 243 | title_xpath : //li[@class="project-row"]//span[contains(@class, "project-full-name")] 244 | content_xpath : //li[@class="project-row"]//div[@class="description"]/p 245 | categories : it 246 | shortcut : gl 247 | timeout : 5.0 248 | disabled : True 249 | 250 | - name : github 251 | engine : github 252 | shortcut : gh 253 | 254 | - name : google 255 | engine : google 256 | shortcut : go 257 | 258 | - name : google images 259 | engine : google_images 260 | shortcut : goi 261 | 262 | - name : google news 263 | engine : google_news 264 | shortcut : gon 265 | 266 | - name : google scholar 267 | engine : xpath 268 | paging : True 269 | search_url : https://scholar.google.com/scholar?start={pageno}&q={query}&hl=en&as_sdt=0,5&as_vis=1 270 | results_xpath : //div[@class="gs_r"]/div[@class="gs_ri"] 271 | url_xpath : .//h3/a/@href 272 | title_xpath : .//h3/a 273 | content_xpath : .//div[@class="gs_rs"] 274 | suggestion_xpath : //div[@id="gs_qsuggest"]/ul/li 275 | page_size : 10 276 | first_page_num : 0 277 | categories : science 278 | shortcut : gos 279 | 280 | - name : google play apps 281 | engine : xpath 282 | search_url : https://play.google.com/store/search?q={query}&c=apps 283 | url_xpath : //a[@class="title"]/@href 284 | title_xpath : //a[@class="title"] 285 | content_xpath : //a[@class="subtitle"] 286 | categories : files 287 | shortcut : gpa 288 | disabled : True 289 | 290 | - name : google play movies 291 | engine : xpath 292 | search_url : https://play.google.com/store/search?q={query}&c=movies 293 | url_xpath : //a[@class="title"]/@href 294 | title_xpath : //a[@class="title"]/@title 295 | content_xpath : //a[contains(@class, "subtitle")] 296 | categories : videos 297 | shortcut : gpm 298 | disabled : True 299 | 300 | - name : google play music 301 | engine : xpath 302 | search_url : https://play.google.com/store/search?q={query}&c=music 303 | url_xpath : //a[@class="title"]/@href 304 | title_xpath : //a[@class="title"] 305 | content_xpath : //a[@class="subtitle"] 306 | categories : music 307 | shortcut : gps 308 | disabled : True 309 | 310 | - name : geektimes 311 | engine : xpath 312 | paging : True 313 | search_url : https://geektimes.ru/search/page{pageno}/?q={query} 314 | url_xpath : //div[@class="search_results"]//a[@class="post__title_link"]/@href 315 | title_xpath : //div[@class="search_results"]//a[@class="post__title_link"] 316 | content_xpath : //div[@class="search_results"]//div[contains(@class, "content")] 317 | categories : it 318 | timeout : 4.0 319 | disabled : True 320 | shortcut : gt 321 | 322 | - name : habrahabr 323 | engine : xpath 324 | paging : True 325 | search_url : https://habrahabr.ru/search/page{pageno}/?q={query} 326 | url_xpath : //div[@class="search_results"]//a[contains(@class, "post__title_link")]/@href 327 | title_xpath : //div[@class="search_results"]//a[contains(@class, "post__title_link")] 328 | content_xpath : //div[@class="search_results"]//div[contains(@class, "content")] 329 | categories : it 330 | timeout : 4.0 331 | disabled : True 332 | shortcut : habr 333 | 334 | - name : hoogle 335 | engine : json_engine 336 | paging : True 337 | search_url : https://www.haskell.org/hoogle/?mode=json&hoogle={query}&start={pageno} 338 | results_query : results 339 | url_query : location 340 | title_query : self 341 | content_query : docs 342 | page_size : 20 343 | categories : it 344 | shortcut : ho 345 | 346 | - name : ina 347 | engine : ina 348 | shortcut : in 349 | timeout : 6.0 350 | disabled : True 351 | 352 | - name: kickass 353 | engine : kickass 354 | shortcut : kc 355 | timeout : 4.0 356 | disabled : True 357 | 358 | - name : library genesis 359 | engine : xpath 360 | search_url : http://libgen.io/search.php?req={query} 361 | url_xpath : //a[contains(@href,"bookfi.net")]/@href 362 | title_xpath : //a[contains(@href,"book/")]/text()[1] 363 | content_xpath : //td/a[1][contains(@href,"=author")]/text() 364 | categories : general 365 | timeout : 7.0 366 | disabled : True 367 | shortcut : lg 368 | 369 | - name : lobste.rs 370 | engine : xpath 371 | search_url : https://lobste.rs/search?utf8=%E2%9C%93&q={query}&what=stories&order=relevance 372 | results_xpath : //li[contains(@class, "story")] 373 | url_xpath : .//span[@class="link"]/a/@href 374 | title_xpath : .//span[@class="link"]/a 375 | content_xpath : .//a[@class="domain"] 376 | categories : it 377 | shortcut : lo 378 | 379 | - name : microsoft academic 380 | engine : json_engine 381 | paging : True 382 | search_url : https://academic.microsoft.com/api/search/GetEntityResults?query=%40{query}%40&filters=&offset={pageno}&limit=8&correlationId=undefined 383 | results_query : results 384 | url_query : u 385 | title_query : dn 386 | content_query : d 387 | page_size : 8 388 | first_page_num : 0 389 | categories : science 390 | shortcut : ma 391 | 392 | - name : mixcloud 393 | engine : mixcloud 394 | shortcut : mc 395 | 396 | - name : nyaa 397 | engine : nyaa 398 | shortcut : nt 399 | disabled : True 400 | 401 | - name : openstreetmap 402 | engine : openstreetmap 403 | shortcut : osm 404 | 405 | - name : openrepos 406 | engine : xpath 407 | paging : True 408 | search_url : https://openrepos.net/search/node/{query}?page={pageno} 409 | url_xpath : //li[@class="search-result"]//h3[@class="title"]/a/@href 410 | title_xpath : //li[@class="search-result"]//h3[@class="title"]/a 411 | content_xpath : //li[@class="search-result"]//div[@class="search-snippet-info"]//p[@class="search-snippet"] 412 | categories : files 413 | timeout : 4.0 414 | disabled : True 415 | shortcut : or 416 | 417 | - name : pdbe 418 | engine : pdbe 419 | shortcut : pdb 420 | # Hide obsolete PDB entries. 421 | # Default is not to hide obsolete structures 422 | # hide_obsolete : False 423 | 424 | - name : photon 425 | engine : photon 426 | shortcut : ph 427 | 428 | - name : piratebay 429 | engine : piratebay 430 | shortcut : tpb 431 | url: https://pirateproxy.red/ 432 | timeout : 3.0 433 | 434 | - name : qwant 435 | engine : qwant 436 | shortcut : qw 437 | categories : general 438 | disabled : True 439 | 440 | - name : qwant images 441 | engine : qwant 442 | shortcut : qwi 443 | categories : images 444 | 445 | - name : qwant news 446 | engine : qwant 447 | shortcut : qwn 448 | categories : news 449 | 450 | - name : qwant social 451 | engine : qwant 452 | shortcut : qws 453 | categories : social media 454 | 455 | - name : reddit 456 | engine : reddit 457 | shortcut : re 458 | page_size : 25 459 | timeout : 10.0 460 | disabled : True 461 | 462 | - name : scanr structures 463 | shortcut: scs 464 | engine : scanr_structures 465 | disabled : True 466 | 467 | - name : soundcloud 468 | engine : soundcloud 469 | shortcut : sc 470 | 471 | - name : stackoverflow 472 | engine : stackoverflow 473 | shortcut : st 474 | 475 | - name : searchcode doc 476 | engine : searchcode_doc 477 | shortcut : scd 478 | 479 | - name : searchcode code 480 | engine : searchcode_code 481 | shortcut : scc 482 | disabled : True 483 | 484 | - name : framalibre 485 | engine : framalibre 486 | shortcut : frl 487 | disabled : True 488 | 489 | # - name : searx 490 | # engine : searx_engine 491 | # shortcut : se 492 | # instance_urls : 493 | # - http://127.0.0.1:8888/ 494 | # - ... 495 | # disabled : True 496 | 497 | - name : semantic scholar 498 | engine : xpath 499 | paging : True 500 | search_url : https://www.semanticscholar.org/search?q={query}&sort=relevance&page={pageno}&ae=false 501 | results_xpath : //article 502 | url_xpath : .//div[@class="search-result-title"]/a/@href 503 | title_xpath : .//div[@class="search-result-title"]/a 504 | content_xpath : .//div[@class="search-result-abstract"] 505 | shortcut : se 506 | categories : science 507 | 508 | - name : spotify 509 | engine : spotify 510 | shortcut : stf 511 | 512 | - name : subtitleseeker 513 | engine : subtitleseeker 514 | shortcut : ss 515 | # The language is an option. You can put any language written in english 516 | # Examples : English, French, German, Hungarian, Chinese... 517 | # language : English 518 | 519 | - name : startpage 520 | engine : startpage 521 | shortcut : sp 522 | timeout : 6.0 523 | disabled : True 524 | 525 | - name : ixquick 526 | engine : startpage 527 | base_url : 'https://www.ixquick.eu/' 528 | search_url : 'https://www.ixquick.eu/do/search' 529 | shortcut : iq 530 | timeout : 6.0 531 | disabled : True 532 | 533 | - name : swisscows 534 | engine : swisscows 535 | shortcut : sw 536 | disabled : True 537 | 538 | - name : tokyotoshokan 539 | engine : tokyotoshokan 540 | shortcut : tt 541 | timeout : 6.0 542 | disabled : True 543 | 544 | - name : twitter 545 | engine : twitter 546 | shortcut : tw 547 | 548 | # maybe in a fun category 549 | - name : uncyclopedia 550 | engine : mediawiki 551 | shortcut : unc 552 | base_url : https://uncyclopedia.wikia.com/ 553 | number_of_results : 5 554 | inactive : true 555 | 556 | # tmp suspended - too slow, too many errors 557 | - name : urbandictionary 558 | engine : xpath 559 | search_url : http://www.urbandictionary.com/define.php?term={query} 560 | url_xpath : //*[@class="word"]/@href 561 | title_xpath : //*[@class="def-header"] 562 | content_xpath : //*[@class="meaning"] 563 | shortcut : ud 564 | inactive : true 565 | 566 | - name : yahoo 567 | engine : yahoo 568 | shortcut : yh 569 | 570 | - name : yandex 571 | engine : yandex 572 | shortcut : yn 573 | disabled : True 574 | 575 | - name : yahoo news 576 | engine : yahoo_news 577 | shortcut : yhn 578 | 579 | - name : youtube 580 | shortcut : yt 581 | # You can use the engine using the official stable API, but you need an API key 582 | # See : https://console.developers.google.com/project 583 | # engine : youtube_api 584 | # api_key: 'apikey' # required! 585 | # Or you can use the html non-stable engine, activated by default 586 | engine : youtube_noapi 587 | 588 | - name : dailymotion 589 | engine : dailymotion 590 | shortcut : dm 591 | 592 | - name : vimeo 593 | engine : vimeo 594 | shortcut : vm 595 | 596 | - name : wolframalpha 597 | shortcut : wa 598 | # You can use the engine using the official stable API, but you need an API key 599 | # See : http://products.wolframalpha.com/api/ 600 | # engine : wolframalpha_api 601 | # api_key: '' # required! 602 | engine : wolframalpha_noapi 603 | timeout: 6.0 604 | categories : science 605 | 606 | - name : seedpeer 607 | engine : seedpeer 608 | shortcut: speu 609 | categories: files, music, videos 610 | disabled: True 611 | 612 | - name : dictzone 613 | engine : dictzone 614 | shortcut : dc 615 | 616 | - name : mymemory translated 617 | engine : translated 618 | shortcut : tl 619 | timeout : 5.0 620 | disabled : True 621 | # You can use without an API key, but you are limited to 1000 words/day 622 | # See : http://mymemory.translated.net/doc/usagelimits.php 623 | # api_key : '' 624 | 625 | - name : voat 626 | engine: xpath 627 | shortcut: vo 628 | categories: social media 629 | search_url : https://voat.co/search?q={query} 630 | url_xpath : //p[contains(@class, "title")]/a/@href 631 | title_xpath : //p[contains(@class, "title")]/a 632 | content_xpath : //span[@class="domain"] 633 | timeout : 10.0 634 | disabled : True 635 | 636 | - name : 1337x 637 | engine : 1337x 638 | shortcut : 1337x 639 | disabled : True 640 | 641 | #The blekko technology and team have joined IBM Watson! -> https://blekko.com/ 642 | # - name : blekko images 643 | # engine : blekko_images 644 | # locale : en-US 645 | # shortcut : bli 646 | 647 | - name : yacy 648 | engine : yacy 649 | shortcut : ya 650 | base_url : 'http://localhost:8090' 651 | number_of_results : 5 652 | timeout : 3.0 653 | inactive : true 654 | 655 | # Doku engine lets you access to any Doku wiki instance: 656 | # A public one or a privete/corporate one. 657 | - name : ubuntuwiki 658 | engine : doku 659 | shortcut : uw 660 | base_url : 'http://doc.ubuntu-fr.org' 661 | inactive : true 662 | 663 | locales: 664 | en : English 665 | bg : Български (Bulgarian) 666 | cs : Čeština (Czech) 667 | de : Deutsch (German) 668 | de_DE : Deutsch (German_Germany) 669 | el_GR : Ελληνικά (Greek_Greece) 670 | eo : Esperanto (Esperanto) 671 | es : Español (Spanish) 672 | fi : Suomi (Finnish) 673 | fr : Français (French) 674 | he : עברית (Hebrew) 675 | hu : Magyar (Hungarian) 676 | it : Italiano (Italian) 677 | ja : 日本語 (Japanese) 678 | nl : Nederlands (Dutch) 679 | pt : Português (Portuguese) 680 | pt_BR : Português (Portuguese_Brazil) 681 | ro : Română (Romanian) 682 | ru : Русский (Russian) 683 | sk : Slovenčina (Slovak) 684 | sv : Svenska (Swedish) 685 | tr : Türkçe (Turkish) 686 | uk : українська мова (Ukrainian) 687 | zh : 中文 (Chinese) 688 | -------------------------------------------------------------------------------- /admin/searx_manager.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import subprocess 3 | from os import listdir 4 | from os.path import isfile, isdir, abspath, join, dirname 5 | from signal import SIGHUP 6 | from shutil import copy 7 | from sys import path 8 | 9 | from requests import get 10 | 11 | from config import configuration 12 | 13 | path.append(configuration['searx']['root']) 14 | 15 | from searx.engines import load_engines 16 | from searx.languages import language_codes 17 | from searx import autocomplete 18 | 19 | 20 | BASE_DIR = abspath(dirname(__file__)) 21 | REFERENCE_SETTINGS_PATH = join(BASE_DIR, 'reference_settings.yml') 22 | EDITABLE_SETTINGS_PATH = join(BASE_DIR, 'searx_generated_settings.yml') 23 | UWSGI_CONFIG_PATH = join(BASE_DIR, 'searx_uwsgi.ini') 24 | UWSGI_INI_TPL = ''' 25 | [uwsgi] 26 | # disable logging for privacy 27 | #disable-logging = true 28 | 29 | # Number of workers (usually CPU count) 30 | workers = 2 31 | 32 | http-socket = {http_socket} 33 | socket = 127.0.0.1:7777 34 | 35 | master = true 36 | plugin = python 37 | lazy-apps = true 38 | enable-threads = true 39 | 40 | # Module to import 41 | module = searx.webapp 42 | 43 | base = {searx_dir} 44 | pythonpath = {searx_dir} 45 | chdir = {searx_dir}/searx 46 | ''' 47 | 48 | 49 | class Searx(object): 50 | _process = None 51 | root_folder = '' 52 | settings_path = '' 53 | settings = None 54 | uwsgi_extra_args = [] 55 | languages = language_codes 56 | safe_search_options = [('0', 'None'), 57 | ('1', 'Moderate'), 58 | ('2', 'Strict')] 59 | autocomplete_options = zip(list(autocomplete.backends.keys()) + [''], 60 | list(autocomplete.backends.keys()) + ['-']) 61 | 62 | def __init__(self, root, uwsgi_extra_args): 63 | self.root_folder = root 64 | self.uwsgi_extra_args = uwsgi_extra_args 65 | with open(REFERENCE_SETTINGS_PATH) as config_file: 66 | config = config_file.read() 67 | self.settings = yaml.load(config) 68 | self.engines = load_engines(self.settings['engines']) 69 | if isfile(EDITABLE_SETTINGS_PATH): 70 | with open(EDITABLE_SETTINGS_PATH) as config_file2: 71 | self._merge_settings(yaml.load(config_file2.read())) 72 | else: 73 | with open(EDITABLE_SETTINGS_PATH, 'w') as outfile: 74 | outfile.write(config) 75 | 76 | def _merge_settings(self, new_settings): 77 | for k, s in new_settings.items(): 78 | if k == 'engines': 79 | continue 80 | for kk, c in s.items(): 81 | self.settings[k][kk] = c 82 | 83 | editable_engines = {e['name']: e for e in new_settings['engines']} 84 | for i, e in enumerate(self.settings['engines']): 85 | if e['name'] in editable_engines: 86 | self.settings['engines'][i] = editable_engines[e['name']] 87 | 88 | def _save(self, new_settings): 89 | for key in self.settings[new_settings['section']]: 90 | new_val = new_settings.get(key, '') 91 | val_type = type(self.settings[new_settings['section']][key]) 92 | if val_type != type(new_val): 93 | try: 94 | new_val = val_type(new_val) 95 | except: 96 | print("Failed to parse settings attribute", section, '->', val_name) 97 | continue 98 | self.settings[new_settings['section']][key] = new_val 99 | 100 | def _save_server_and_general_settings(self, new_settings): 101 | self.settings['general']['debug'] = 'debug' in new_settings 102 | self.settings['general']['instance_name'] = new_settings.get('instance_name', '') 103 | for key in self.settings['server']: 104 | self.settings['server'][key] = new_settings.get(key, False) 105 | self._save_uwsgi_ini() 106 | 107 | def _save_uwsgi_ini(self): 108 | # save uwsgi.ini too 109 | with open(UWSGI_CONFIG_PATH, 'w') as outfile: 110 | outfile.write(UWSGI_INI_TPL.format( 111 | http_socket = '{}:{}'.format( 112 | self.settings['server']['bind_address'], 113 | self.settings['server']['port'], 114 | ), 115 | searx_dir = self.root_folder, 116 | )) 117 | 118 | def _save_outgoing_settings(self, new_settings): 119 | self._save(new_settings) 120 | self.settings['outgoing']['source_ips'] = new_settings['source_ips'].split(', ') 121 | 122 | def _save_engine(self, engine): 123 | for e2 in self.settings['engines']: 124 | if e2['name'] == engine.name: 125 | for attr in dir(engine): 126 | if attr in e2: 127 | e2[attr] = getattr(engine, attr) 128 | print("engine settings saved") 129 | break 130 | 131 | def save_settings(self, new_settings): 132 | # TODO make it beautiful 133 | if new_settings['section'] == 'server': 134 | self._save_server_and_general_settings(new_settings) 135 | elif new_settings['section'] == 'outgoing': 136 | self._save_outgoing_settings(new_settings) 137 | if new_settings['section'] == 'engine': 138 | self._save_engine(new_settings['engine']) 139 | else: 140 | self._save(new_settings) 141 | 142 | with open(EDITABLE_SETTINGS_PATH, 'w') as config_file: 143 | yaml.dump(self.settings, config_file, default_flow_style=False) 144 | 145 | def available_themes(self): 146 | templates_path = self.settings['ui']['templates_path'] 147 | if self.settings['ui']['templates_path'] == '': 148 | templates_path = self.root_folder + '/searx/templates' 149 | available_themes = [] 150 | if not isdir(templates_path): 151 | # TODO log error 152 | return None 153 | for filename in listdir(templates_path): 154 | if filename != '__common__': 155 | available_themes.append((filename, filename)) 156 | return available_themes 157 | 158 | def restore_defaults(self): 159 | copy(REFERENCE_SETTINGS_PATH, EDITABLE_SETTINGS_PATH) 160 | self.reload() 161 | 162 | def reload(self): 163 | if self.is_running(): 164 | self._process.send_signal(SIGHUP) 165 | else: 166 | self.start() 167 | 168 | def update(self): 169 | subprocess.Popen( 170 | ['git', 'pull', 'origin', 'master'], 171 | cwd=self.root_folder, 172 | ).wait() 173 | try: 174 | new_reference_settings = get('https://raw.githubusercontent.com/kvch/searx-admin/master/admin/reference_settings.yml').text 175 | if new_reference_settings: 176 | with open(REFERENCE_SETTINGS_PATH, 'w') as outfile: 177 | outfile.write(new_reference_settings.encode('utf-8')) 178 | except Exception as e: 179 | print('Failed to fetch new references settings.yml', e) 180 | 181 | self.reload() 182 | 183 | def is_running(self): 184 | if self._process is None: 185 | return False 186 | 187 | self._process.poll() 188 | return self._process.returncode is None 189 | 190 | def start(self): 191 | if self.is_running(): 192 | return 193 | 194 | if not isfile(UWSGI_CONFIG_PATH): 195 | self._save_uwsgi_ini() 196 | 197 | uwsgi_cmd = ['uwsgi', '--ini', UWSGI_CONFIG_PATH] 198 | uwsgi_cmd.extend(self.uwsgi_extra_args) 199 | 200 | self._process = subprocess.Popen( 201 | uwsgi_cmd, 202 | cwd=self.root_folder, 203 | env={'SEARX_SETTINGS_PATH': EDITABLE_SETTINGS_PATH}, 204 | ) 205 | 206 | def stop(self): 207 | if self.is_running(): 208 | self._process.terminate() 209 | if self.is_running(): 210 | self._process.kill() 211 | if not self.is_running(): 212 | self._process = None 213 | 214 | def __enter__(self): 215 | self.start() 216 | 217 | def __exit__(self, exc_type, exc_value, traceback): 218 | self.stop() 219 | -------------------------------------------------------------------------------- /admin/static/css/main.css: -------------------------------------------------------------------------------- 1 | select { 2 | font-family: inherit; 3 | background-color: transparent; 4 | width: 100%; 5 | padding: 4px 0; 6 | font-size: 16px; 7 | color: black; 8 | border: none; 9 | border-bottom: 1px solid black; 10 | } 11 | 12 | select:focus { 13 | outline: none} 14 | 15 | .mdl-selectfield label {display: none;} 16 | .mdl-selectfield select {appearance: none} 17 | .mdl-selectfield { 18 | font-family: 'Roboto','Helvetica','Arial',sans-serif; 19 | position: relative; 20 | &:after { 21 | position: absolute; 22 | top: 0.75em; 23 | right: 0.5em; 24 | /* Styling the down arrow */ 25 | width: 0; 26 | height: 0; 27 | padding: 0; 28 | content: ''; 29 | border-left: .25em solid transparent; 30 | border-right: .25em solid transparent; 31 | border-top: .375em solid black; 32 | pointer-events: none; 33 | } 34 | } 35 | 36 | #engine-search { 37 | padding: 10px; 38 | border: 1px solid #999999; 39 | } 40 | 41 | .menubutton, .menubutton { 42 | color: #DDDDFF; 43 | } 44 | 45 | .engine-container { 46 | display: inline-block; 47 | margin-left: 1.0rem; 48 | margin-top: 1.0rem; 49 | } 50 | 51 | .user-event-card-wide.mdl-card { 52 | width: 30.0rem; 53 | margin: auto; 54 | margin-top: 10.0rem; 55 | padding: 2.0rem; 56 | } 57 | .user-event-error { 58 | font-weight: bold; 59 | } 60 | .page-content { 61 | padding: 0 2em; 62 | } 63 | 64 | .mdl-layout-title > a { 65 | color: #606060; 66 | text-decoration: none; 67 | } 68 | -------------------------------------------------------------------------------- /admin/static/js/material.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * material-design-lite - Material Design Components in CSS, JS and HTML 3 | * @version v1.3.0 4 | * @license Apache-2.0 5 | * @copyright 2015 Google, Inc. 6 | * @link https://github.com/google/material-design-lite 7 | */ 8 | !function(){"use strict";function e(e,t){if(e){if(t.element_.classList.contains(t.CssClasses_.MDL_JS_RIPPLE_EFFECT)){var s=document.createElement("span");s.classList.add(t.CssClasses_.MDL_RIPPLE_CONTAINER),s.classList.add(t.CssClasses_.MDL_JS_RIPPLE_EFFECT);var i=document.createElement("span");i.classList.add(t.CssClasses_.MDL_RIPPLE),s.appendChild(i),e.appendChild(s)}e.addEventListener("click",function(s){if("#"===e.getAttribute("href").charAt(0)){s.preventDefault();var i=e.href.split("#")[1],n=t.element_.querySelector("#"+i);t.resetTabState_(),t.resetPanelState_(),e.classList.add(t.CssClasses_.ACTIVE_CLASS),n.classList.add(t.CssClasses_.ACTIVE_CLASS)}})}}function t(e,t,s,i){function n(){var n=e.href.split("#")[1],a=i.content_.querySelector("#"+n);i.resetTabState_(t),i.resetPanelState_(s),e.classList.add(i.CssClasses_.IS_ACTIVE),a.classList.add(i.CssClasses_.IS_ACTIVE)}if(i.tabBar_.classList.contains(i.CssClasses_.JS_RIPPLE_EFFECT)){var a=document.createElement("span");a.classList.add(i.CssClasses_.RIPPLE_CONTAINER),a.classList.add(i.CssClasses_.JS_RIPPLE_EFFECT);var l=document.createElement("span");l.classList.add(i.CssClasses_.RIPPLE),a.appendChild(l),e.appendChild(a)}i.tabBar_.classList.contains(i.CssClasses_.TAB_MANUAL_SWITCH)||e.addEventListener("click",function(t){"#"===e.getAttribute("href").charAt(0)&&(t.preventDefault(),n())}),e.show=n}var s={upgradeDom:function(e,t){},upgradeElement:function(e,t){},upgradeElements:function(e){},upgradeAllRegistered:function(){},registerUpgradedCallback:function(e,t){},register:function(e){},downgradeElements:function(e){}};s=function(){function e(e,t){for(var s=0;s0&&l(t.children))}function o(t){var s="undefined"==typeof t.widget&&"undefined"==typeof t.widget,i=!0;s||(i=t.widget||t.widget);var n={classConstructor:t.constructor||t.constructor,className:t.classAsString||t.classAsString,cssClass:t.cssClass||t.cssClass,widget:i,callbacks:[]};if(c.forEach(function(e){if(e.cssClass===n.cssClass)throw new Error("The provided cssClass has already been registered: "+e.cssClass);if(e.className===n.className)throw new Error("The provided className has already been registered")}),t.constructor.prototype.hasOwnProperty(C))throw new Error("MDL component classes must not have "+C+" defined as a property.");var a=e(t.classAsString,n);a||c.push(n)}function r(t,s){var i=e(t);i&&i.callbacks.push(s)}function _(){for(var e=0;e0&&this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)&&(e.keyCode===this.Keycodes_.UP_ARROW?(e.preventDefault(),t[t.length-1].focus()):e.keyCode===this.Keycodes_.DOWN_ARROW&&(e.preventDefault(),t[0].focus()))}},d.prototype.handleItemKeyboardEvent_=function(e){if(this.element_&&this.container_){var t=this.element_.querySelectorAll("."+this.CssClasses_.ITEM+":not([disabled])");if(t&&t.length>0&&this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)){var s=Array.prototype.slice.call(t).indexOf(e.target);if(e.keyCode===this.Keycodes_.UP_ARROW)e.preventDefault(),s>0?t[s-1].focus():t[t.length-1].focus();else if(e.keyCode===this.Keycodes_.DOWN_ARROW)e.preventDefault(),t.length>s+1?t[s+1].focus():t[0].focus();else if(e.keyCode===this.Keycodes_.SPACE||e.keyCode===this.Keycodes_.ENTER){e.preventDefault();var i=new MouseEvent("mousedown");e.target.dispatchEvent(i),i=new MouseEvent("mouseup"),e.target.dispatchEvent(i),e.target.click()}else e.keyCode===this.Keycodes_.ESCAPE&&(e.preventDefault(),this.hide())}}},d.prototype.handleItemClick_=function(e){e.target.hasAttribute("disabled")?e.stopPropagation():(this.closing_=!0,window.setTimeout(function(e){this.hide(),this.closing_=!1}.bind(this),this.Constant_.CLOSE_TIMEOUT))},d.prototype.applyClip_=function(e,t){this.element_.classList.contains(this.CssClasses_.UNALIGNED)?this.element_.style.clip="":this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)?this.element_.style.clip="rect(0 "+t+"px 0 "+t+"px)":this.element_.classList.contains(this.CssClasses_.TOP_LEFT)?this.element_.style.clip="rect("+e+"px 0 "+e+"px 0)":this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)?this.element_.style.clip="rect("+e+"px "+t+"px "+e+"px "+t+"px)":this.element_.style.clip=""},d.prototype.removeAnimationEndListener_=function(e){e.target.classList.remove(d.prototype.CssClasses_.IS_ANIMATING)},d.prototype.addAnimationEndListener_=function(){this.element_.addEventListener("transitionend",this.removeAnimationEndListener_),this.element_.addEventListener("webkitTransitionEnd",this.removeAnimationEndListener_)},d.prototype.show=function(e){if(this.element_&&this.container_&&this.outline_){var t=this.element_.getBoundingClientRect().height,s=this.element_.getBoundingClientRect().width;this.container_.style.width=s+"px",this.container_.style.height=t+"px",this.outline_.style.width=s+"px",this.outline_.style.height=t+"px";for(var i=this.Constant_.TRANSITION_DURATION_SECONDS*this.Constant_.TRANSITION_DURATION_FRACTION,n=this.element_.querySelectorAll("."+this.CssClasses_.ITEM),a=0;a0&&this.showSnackbar(this.queuedNotifications_.shift())},C.prototype.cleanup_=function(){this.element_.classList.remove(this.cssClasses_.ACTIVE),setTimeout(function(){this.element_.setAttribute("aria-hidden","true"),this.textElement_.textContent="",Boolean(this.actionElement_.getAttribute("aria-hidden"))||(this.setActionHidden_(!0),this.actionElement_.textContent="",this.actionElement_.removeEventListener("click",this.actionHandler_)),this.actionHandler_=void 0,this.message_=void 0,this.actionText_=void 0,this.active=!1,this.checkQueue_()}.bind(this),this.Constant_.ANIMATION_LENGTH)},C.prototype.setActionHidden_=function(e){e?this.actionElement_.setAttribute("aria-hidden","true"):this.actionElement_.removeAttribute("aria-hidden")},s.register({constructor:C,classAsString:"MaterialSnackbar",cssClass:"mdl-js-snackbar",widget:!0});var u=function(e){this.element_=e,this.init()};window.MaterialSpinner=u,u.prototype.Constant_={MDL_SPINNER_LAYER_COUNT:4},u.prototype.CssClasses_={MDL_SPINNER_LAYER:"mdl-spinner__layer",MDL_SPINNER_CIRCLE_CLIPPER:"mdl-spinner__circle-clipper",MDL_SPINNER_CIRCLE:"mdl-spinner__circle",MDL_SPINNER_GAP_PATCH:"mdl-spinner__gap-patch",MDL_SPINNER_LEFT:"mdl-spinner__left",MDL_SPINNER_RIGHT:"mdl-spinner__right"},u.prototype.createLayer=function(e){var t=document.createElement("div");t.classList.add(this.CssClasses_.MDL_SPINNER_LAYER),t.classList.add(this.CssClasses_.MDL_SPINNER_LAYER+"-"+e);var s=document.createElement("div");s.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER),s.classList.add(this.CssClasses_.MDL_SPINNER_LEFT);var i=document.createElement("div");i.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH);var n=document.createElement("div");n.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER),n.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT);for(var a=[s,i,n],l=0;l=this.maxRows&&e.preventDefault()},L.prototype.onFocus_=function(e){this.element_.classList.add(this.CssClasses_.IS_FOCUSED)},L.prototype.onBlur_=function(e){this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},L.prototype.onReset_=function(e){this.updateClasses_()},L.prototype.updateClasses_=function(){this.checkDisabled(),this.checkValidity(),this.checkDirty(),this.checkFocus()},L.prototype.checkDisabled=function(){this.input_.disabled?this.element_.classList.add(this.CssClasses_.IS_DISABLED):this.element_.classList.remove(this.CssClasses_.IS_DISABLED)},L.prototype.checkDisabled=L.prototype.checkDisabled,L.prototype.checkFocus=function(){Boolean(this.element_.querySelector(":focus"))?this.element_.classList.add(this.CssClasses_.IS_FOCUSED):this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},L.prototype.checkFocus=L.prototype.checkFocus,L.prototype.checkValidity=function(){this.input_.validity&&(this.input_.validity.valid?this.element_.classList.remove(this.CssClasses_.IS_INVALID):this.element_.classList.add(this.CssClasses_.IS_INVALID))},L.prototype.checkValidity=L.prototype.checkValidity,L.prototype.checkDirty=function(){this.input_.value&&this.input_.value.length>0?this.element_.classList.add(this.CssClasses_.IS_DIRTY):this.element_.classList.remove(this.CssClasses_.IS_DIRTY)},L.prototype.checkDirty=L.prototype.checkDirty,L.prototype.disable=function(){this.input_.disabled=!0,this.updateClasses_()},L.prototype.disable=L.prototype.disable,L.prototype.enable=function(){this.input_.disabled=!1,this.updateClasses_()},L.prototype.enable=L.prototype.enable,L.prototype.change=function(e){this.input_.value=e||"",this.updateClasses_()},L.prototype.change=L.prototype.change,L.prototype.init=function(){if(this.element_&&(this.label_=this.element_.querySelector("."+this.CssClasses_.LABEL),this.input_=this.element_.querySelector("."+this.CssClasses_.INPUT),this.input_)){this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)&&(this.maxRows=parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE),10),isNaN(this.maxRows)&&(this.maxRows=this.Constant_.NO_MAX_ROWS)),this.input_.hasAttribute("placeholder")&&this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER),this.boundUpdateClassesHandler=this.updateClasses_.bind(this),this.boundFocusHandler=this.onFocus_.bind(this),this.boundBlurHandler=this.onBlur_.bind(this),this.boundResetHandler=this.onReset_.bind(this),this.input_.addEventListener("input",this.boundUpdateClassesHandler),this.input_.addEventListener("focus",this.boundFocusHandler),this.input_.addEventListener("blur",this.boundBlurHandler),this.input_.addEventListener("reset",this.boundResetHandler),this.maxRows!==this.Constant_.NO_MAX_ROWS&&(this.boundKeyDownHandler=this.onKeyDown_.bind(this),this.input_.addEventListener("keydown",this.boundKeyDownHandler));var e=this.element_.classList.contains(this.CssClasses_.IS_INVALID);this.updateClasses_(),this.element_.classList.add(this.CssClasses_.IS_UPGRADED),e&&this.element_.classList.add(this.CssClasses_.IS_INVALID),this.input_.hasAttribute("autofocus")&&(this.element_.focus(),this.checkFocus())}},s.register({constructor:L,classAsString:"MaterialTextfield",cssClass:"mdl-js-textfield",widget:!0});var I=function(e){this.element_=e,this.init()};window.MaterialTooltip=I,I.prototype.Constant_={},I.prototype.CssClasses_={IS_ACTIVE:"is-active",BOTTOM:"mdl-tooltip--bottom",LEFT:"mdl-tooltip--left",RIGHT:"mdl-tooltip--right",TOP:"mdl-tooltip--top"},I.prototype.handleMouseEnter_=function(e){var t=e.target.getBoundingClientRect(),s=t.left+t.width/2,i=t.top+t.height/2,n=-1*(this.element_.offsetWidth/2),a=-1*(this.element_.offsetHeight/2);this.element_.classList.contains(this.CssClasses_.LEFT)||this.element_.classList.contains(this.CssClasses_.RIGHT)?(s=t.width/2,i+a<0?(this.element_.style.top="0",this.element_.style.marginTop="0"):(this.element_.style.top=i+"px",this.element_.style.marginTop=a+"px")):s+n<0?(this.element_.style.left="0",this.element_.style.marginLeft="0"):(this.element_.style.left=s+"px",this.element_.style.marginLeft=n+"px"),this.element_.classList.contains(this.CssClasses_.TOP)?this.element_.style.top=t.top-this.element_.offsetHeight-10+"px":this.element_.classList.contains(this.CssClasses_.RIGHT)?this.element_.style.left=t.left+t.width+10+"px":this.element_.classList.contains(this.CssClasses_.LEFT)?this.element_.style.left=t.left-this.element_.offsetWidth-10+"px":this.element_.style.top=t.top+t.height+10+"px",this.element_.classList.add(this.CssClasses_.IS_ACTIVE)},I.prototype.hideTooltip_=function(){this.element_.classList.remove(this.CssClasses_.IS_ACTIVE)},I.prototype.init=function(){if(this.element_){var e=this.element_.getAttribute("for")||this.element_.getAttribute("data-mdl-for");e&&(this.forElement_=document.getElementById(e)),this.forElement_&&(this.forElement_.hasAttribute("tabindex")||this.forElement_.setAttribute("tabindex","0"),this.boundMouseEnterHandler=this.handleMouseEnter_.bind(this),this.boundMouseLeaveAndScrollHandler=this.hideTooltip_.bind(this),this.forElement_.addEventListener("mouseenter",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("touchend",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("mouseleave",this.boundMouseLeaveAndScrollHandler,!1),window.addEventListener("scroll",this.boundMouseLeaveAndScrollHandler,!0),window.addEventListener("touchstart",this.boundMouseLeaveAndScrollHandler))}},s.register({constructor:I,classAsString:"MaterialTooltip",cssClass:"mdl-tooltip"});var f=function(e){this.element_=e,this.init()};window.MaterialLayout=f,f.prototype.Constant_={MAX_WIDTH:"(max-width: 1024px)",TAB_SCROLL_PIXELS:100,RESIZE_TIMEOUT:100,MENU_ICON:"",CHEVRON_LEFT:"chevron_left",CHEVRON_RIGHT:"chevron_right"},f.prototype.Keycodes_={ENTER:13,ESCAPE:27,SPACE:32},f.prototype.Mode_={STANDARD:0,SEAMED:1,WATERFALL:2,SCROLL:3},f.prototype.CssClasses_={CONTAINER:"mdl-layout__container",HEADER:"mdl-layout__header",DRAWER:"mdl-layout__drawer",CONTENT:"mdl-layout__content",DRAWER_BTN:"mdl-layout__drawer-button",ICON:"material-icons",JS_RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_CONTAINER:"mdl-layout__tab-ripple-container",RIPPLE:"mdl-ripple",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",HEADER_SEAMED:"mdl-layout__header--seamed",HEADER_WATERFALL:"mdl-layout__header--waterfall",HEADER_SCROLL:"mdl-layout__header--scroll",FIXED_HEADER:"mdl-layout--fixed-header",OBFUSCATOR:"mdl-layout__obfuscator",TAB_BAR:"mdl-layout__tab-bar",TAB_CONTAINER:"mdl-layout__tab-bar-container",TAB:"mdl-layout__tab",TAB_BAR_BUTTON:"mdl-layout__tab-bar-button",TAB_BAR_LEFT_BUTTON:"mdl-layout__tab-bar-left-button",TAB_BAR_RIGHT_BUTTON:"mdl-layout__tab-bar-right-button",TAB_MANUAL_SWITCH:"mdl-layout__tab-manual-switch",PANEL:"mdl-layout__tab-panel",HAS_DRAWER:"has-drawer",HAS_TABS:"has-tabs",HAS_SCROLLING_HEADER:"has-scrolling-header",CASTING_SHADOW:"is-casting-shadow",IS_COMPACT:"is-compact",IS_SMALL_SCREEN:"is-small-screen",IS_DRAWER_OPEN:"is-visible",IS_ACTIVE:"is-active",IS_UPGRADED:"is-upgraded",IS_ANIMATING:"is-animating",ON_LARGE_SCREEN:"mdl-layout--large-screen-only",ON_SMALL_SCREEN:"mdl-layout--small-screen-only"},f.prototype.contentScrollHandler_=function(){if(!this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)){var e=!this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN)||this.element_.classList.contains(this.CssClasses_.FIXED_HEADER);this.content_.scrollTop>0&&!this.header_.classList.contains(this.CssClasses_.IS_COMPACT)?(this.header_.classList.add(this.CssClasses_.CASTING_SHADOW),this.header_.classList.add(this.CssClasses_.IS_COMPACT),e&&this.header_.classList.add(this.CssClasses_.IS_ANIMATING)):this.content_.scrollTop<=0&&this.header_.classList.contains(this.CssClasses_.IS_COMPACT)&&(this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW),this.header_.classList.remove(this.CssClasses_.IS_COMPACT),e&&this.header_.classList.add(this.CssClasses_.IS_ANIMATING))}},f.prototype.keyboardEventHandler_=function(e){e.keyCode===this.Keycodes_.ESCAPE&&this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)&&this.toggleDrawer()},f.prototype.screenSizeHandler_=function(){this.screenSizeMediaQuery_.matches?this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN):(this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN),this.drawer_&&(this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN),this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN)))},f.prototype.drawerToggleHandler_=function(e){if(e&&"keydown"===e.type){if(e.keyCode!==this.Keycodes_.SPACE&&e.keyCode!==this.Keycodes_.ENTER)return;e.preventDefault()}this.toggleDrawer()},f.prototype.headerTransitionEndHandler_=function(){this.header_.classList.remove(this.CssClasses_.IS_ANIMATING)},f.prototype.headerClickHandler_=function(){this.header_.classList.contains(this.CssClasses_.IS_COMPACT)&&(this.header_.classList.remove(this.CssClasses_.IS_COMPACT),this.header_.classList.add(this.CssClasses_.IS_ANIMATING))},f.prototype.resetTabState_=function(e){for(var t=0;t0?c.classList.add(this.CssClasses_.IS_ACTIVE):c.classList.remove(this.CssClasses_.IS_ACTIVE),this.tabBar_.scrollLeft0)return;this.setFrameCount(1);var i,n,a=e.currentTarget.getBoundingClientRect();if(0===e.clientX&&0===e.clientY)i=Math.round(a.width/2),n=Math.round(a.height/2);else{var l=void 0!==e.clientX?e.clientX:e.touches[0].clientX,o=void 0!==e.clientY?e.clientY:e.touches[0].clientY;i=Math.round(l-a.left),n=Math.round(o-a.top)}this.setRippleXY(i,n),this.setRippleStyles(!0),window.requestAnimationFrame(this.animFrameHandler.bind(this))}},S.prototype.upHandler_=function(e){e&&2!==e.detail&&window.setTimeout(function(){this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE)}.bind(this),0)},S.prototype.init=function(){if(this.element_){var e=this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER);this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)||(this.rippleElement_=this.element_.querySelector("."+this.CssClasses_.RIPPLE),this.frameCount_=0,this.rippleSize_=0,this.x_=0,this.y_=0,this.ignoringMouseDown_=!1,this.boundDownHandler=this.downHandler_.bind(this),this.element_.addEventListener("mousedown",this.boundDownHandler),this.element_.addEventListener("touchstart",this.boundDownHandler),this.boundUpHandler=this.upHandler_.bind(this),this.element_.addEventListener("mouseup",this.boundUpHandler),this.element_.addEventListener("mouseleave",this.boundUpHandler),this.element_.addEventListener("touchend",this.boundUpHandler),this.element_.addEventListener("blur",this.boundUpHandler),this.getFrameCount=function(){return this.frameCount_},this.setFrameCount=function(e){this.frameCount_=e},this.getRippleElement=function(){return this.rippleElement_},this.setRippleXY=function(e,t){this.x_=e,this.y_=t},this.setRippleStyles=function(t){if(null!==this.rippleElement_){var s,i,n,a="translate("+this.x_+"px, "+this.y_+"px)";t?(i=this.Constant_.INITIAL_SCALE,n=this.Constant_.INITIAL_SIZE):(i=this.Constant_.FINAL_SCALE,n=this.rippleSize_+"px",e&&(a="translate("+this.boundWidth/2+"px, "+this.boundHeight/2+"px)")),s="translate(-50%, -50%) "+a+i,this.rippleElement_.style.webkitTransform=s,this.rippleElement_.style.msTransform=s,this.rippleElement_.style.transform=s,t?this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING):this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING)}},this.animFrameHandler=function(){this.frameCount_-- >0?window.requestAnimationFrame(this.animFrameHandler.bind(this)):this.setRippleStyles(!1)})}},s.register({constructor:S,classAsString:"MaterialRipple",cssClass:"mdl-js-ripple-effect",widget:!1})}(); 10 | //# sourceMappingURL=material.min.js.map 11 | -------------------------------------------------------------------------------- /admin/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | searx admin 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% block content %} 12 | {% endblock %} 13 | 14 | 15 | -------------------------------------------------------------------------------- /admin/templates/edit_engine.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% from "macros.html" import switch, text_input, numeric_input, dropdown, submit %} 3 | {% block page %} 4 | 5 |
6 |
7 |

"{{ engine.name }}" engine edit

8 |
9 | 15 | {% for key, value, type in engine_attrs %} 16 | {% if type == 'str' %} 17 | {{ text_input(key.capitalize().replace('_', ' '), key, value) }} 18 | {% elif type == 'float' %} 19 | {{ numeric_input(key.capitalize().replace('_', ' '), key, value) }} 20 | {% elif type == 'bool' %} 21 | {{ switch(key.capitalize().replace('_', ' '), key, value) }} 22 | {% endif %} 23 | {% endfor %} 24 | {{ submit() }} 25 |
26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /admin/templates/engines.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block page %} 3 | 4 | 22 | 23 |
24 | 25 |
26 | 27 |
28 | {% for engine in engines.values()|sort(attribute='name') %} 29 |
30 |
31 |
32 |

{{ engine.name }}

33 |
34 |
35 | 39 |

40 |
Shortcut: !{{ engine.shortcut }}
41 |
Categories: {{ engine.categories|join(', ') }}
42 |
Timeout: {{ engine.timeout }}
43 |
44 |
45 | mode_edit 46 |
47 |
48 |
49 | {% endfor %} 50 |
51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /admin/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 8 |
9 |
10 |
11 | {% if instance.is_running() %} 12 |
Searx is running
13 | 14 | 17 | 18 | 19 | 22 | 23 | {% else %} 24 |
44 |
45 | Searx Admin 46 | 51 |
52 |
53 |
54 | {% block page %} 55 | {% endblock %} 56 |
57 |
58 |
59 | {% endblock %} 60 | -------------------------------------------------------------------------------- /admin/templates/macros.html: -------------------------------------------------------------------------------- 1 | {% macro switch(label, id, on) -%} 2 |
3 |
4 | {{ label }}: 5 |
6 |
7 | 11 |
12 |
13 | {% endmacro %} 14 | 15 | {% macro text_input(label, id, value) -%} 16 |
17 |
18 | {{ label }}: 19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 |
27 | {% endmacro %} 28 | 29 | {% macro numeric_input(label, id, value) -%} 30 |
31 |
32 | {{ label }}: 33 |
34 |
35 |
36 | 37 | 38 | Input is not a number! 39 |
40 |
41 |
42 | {% endmacro %} 43 | 44 | {% macro list_input(label, id, value) -%} 45 |
46 |
47 | {{ label }}: 48 |
49 |
50 |
51 | 52 | 53 |
54 |
55 |
56 | {% endmacro %} 57 | 58 | {% macro dropdown(label, id, possible_values, current) -%} 59 |
60 |
61 | {{ label }}: 62 |
63 |
64 |
65 | 72 |
73 |
74 |
75 | {% endmacro %} 76 | 77 | {% macro submit() -%} 78 | 79 | {% endmacro %} 80 | -------------------------------------------------------------------------------- /admin/templates/manage.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% block page %} 3 |

Welcome to searx admin!

4 | 5 | 6 | 9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /admin/templates/outgoing.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% from "macros.html" import text_input, numeric_input, list_input, submit %} 3 | {% block page %} 4 | 5 |
6 | 7 | {{ numeric_input('Request timeout', 'request_timeout', request_timeout) }} 8 | {{ text_input('Suffix of user agent', 'useragent_suffix', useragent_suffix) }} 9 | {{ numeric_input('Number of pool connections', 'pool_connections', pool_connections) }} 10 | {{ numeric_input('Number of connections by pool', 'pool_maxsize', pool_maxsize) }} 11 | {{ list_input('Source IPs', 'source_ips', source_ips) }} 12 | {{ submit() }} 13 |
14 | 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /admin/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% from "macros.html" import dropdown, submit %} 3 | {% block page %} 4 | 5 | 30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /admin/templates/security/login_user.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 |
5 |
6 |

Searx Admin Login

7 |
8 |
9 |
10 | {{ login_user_form.hidden_tag() }} 11 | {% if login_user_form.email.errors %} 12 | {% for error in login_user_form.email.errors %} 13 |
14 | {{ error }} 15 |
16 | {% endfor %} 17 | {% endif %} 18 | {% if login_user_form.password.errors %} 19 | {% for error in login_user_form.password.errors %} 20 |
21 | {{ error }} 22 |
23 | {% endfor %} 24 | {% endif %} 25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 | 37 |

38 |

39 |
40 |
41 |
42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /admin/templates/security/register_user.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 |
5 |
6 |

{{ _('Register') }}

7 |
8 |
9 |
10 | {{ register_user_form.hidden_tag() }} 11 | {% if register_user_form.email.errors %} 12 | {% for error in register_user_form.email.errors %} 13 |
14 | {{ error }} 15 |
16 | {% endfor %} 17 | {% endif %} 18 | {% if register_user_form.password.errors %} 19 | {% for error in register_user_form.password.errors %} 20 |
21 | {{ error }} 22 |
23 | {% endfor %} 24 | {% endif %} 25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 |

38 |
39 |
40 |
41 | {% endblock %} 42 | 43 | -------------------------------------------------------------------------------- /admin/templates/server.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% from "macros.html" import switch, text_input, numeric_input, dropdown, submit %} 3 | {% block page %} 4 | 5 |
6 | 7 | {{ switch('Debug logging', 'debug', debug) }} 8 | {{ text_input('Instance name', 'instance_name', instance_name) }} 9 | {{ numeric_input('Port', 'port', port) }} 10 | {{ text_input('Bind address', 'bind_address', bind_address) }} 11 | {{ text_input('Secret key', 'secret_key', secret_key) }} 12 | {{ text_input('Base URL', 'base_url', base_url) }} 13 | {{ switch('Image proxy', 'image_proxy', image_proxy) }} 14 | {{ dropdown('HTTP protocol version', 'http_protocol_version', [('1.0', '1.0'), ('1.1', '1.1')], http_protocol_version) }} 15 | {{ submit() }} 16 |
17 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /admin/templates/ui.html: -------------------------------------------------------------------------------- 1 | {% extends "index.html" %} 2 | {% from "macros.html" import text_input, dropdown, submit %} 3 | {% block page %} 4 | 5 |
6 | 7 | {{ text_input('Static path', 'static_path', static_path) }} 8 | {{ text_input('Templates path', 'templates_path', templates_path) }} 9 | {{ dropdown('Default template', 'default_theme', available_themes, default_theme) }} 10 | {{ dropdown('Default locale', 'default_locale', locales, default_locale) }} 11 | {{ submit() }} 12 |
13 | 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /admin/webapp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from os.path import isfile 3 | 4 | from flask import Flask, render_template, request, redirect, url_for 5 | from flask_mail import Mail 6 | from flask_security import Security, SQLAlchemySessionUserDatastore, login_required, user_registered 7 | 8 | from config import configuration 9 | from database import db_session, init_db 10 | from model import User, Role 11 | from searx_manager import Searx 12 | 13 | 14 | app = Flask(__name__) 15 | app.secret_key = configuration['app']['secretkey'] 16 | 17 | app.config['SECURITY_PASSWORD_SALT'] = configuration['app']['secretkey'] 18 | app.config['SECURITY_REGISTERABLE'] = True 19 | app.config['SECURITY_SEND_REGISTER_EMAIL'] = False 20 | 21 | mail = Mail(app) 22 | user_datastore = SQLAlchemySessionUserDatastore(db_session, User, Role) 23 | security = Security(app, user_datastore) 24 | instance = Searx(**configuration['searx']) 25 | is_user_missing = True 26 | 27 | 28 | def render(template_name, **kwargs): 29 | kwargs['instance'] = instance 30 | kwargs['menu_items'] = ( 31 | ('server', 'Instance'), 32 | ('search', 'Search'), 33 | ('ui', 'User interface'), 34 | ('outgoing', 'Outgoing requests'), 35 | ('engines', 'Engines'), 36 | ) 37 | return render_template(template_name, **kwargs) 38 | 39 | 40 | @app.before_request 41 | def _create_user_if_missing(): 42 | global is_user_missing 43 | accessible_paths = [ 44 | url_for('security.register'), 45 | url_for('static', filename='css/main.css'), 46 | url_for('static', filename='css/material.min.css'), 47 | url_for('static', filename='js/material.min.js'), 48 | ] 49 | if is_user_missing and request.path not in accessible_paths: 50 | return redirect(url_for('security.register')) 51 | 52 | 53 | @user_registered.connect_via(app) 54 | def user_registered_sighandler(sender, **extra): 55 | global is_user_missing 56 | is_user_missing = False 57 | 58 | 59 | @app.route('/') 60 | @login_required 61 | def index(): 62 | return render('manage.html', 63 | bind_address=instance.settings['server']['bind_address'], 64 | port=instance.settings['server']['port']) 65 | 66 | 67 | @app.route('/instance') 68 | @login_required 69 | def server(): 70 | return render('server.html', 71 | instance_name=instance.settings['general']['instance_name'], 72 | debug=instance.settings['general']['debug'], 73 | **instance.settings['server']) 74 | 75 | 76 | @app.route('/search') 77 | @login_required 78 | def search(): 79 | return render('search.html', 80 | safe_search_options=instance.safe_search_options, 81 | autocomplete_options=instance.autocomplete_options, 82 | languages=instance.languages, 83 | **instance.settings['search']) 84 | 85 | 86 | def _setup_locales_to_display(): 87 | locales = [] 88 | for key, val in instance.settings['locales'].items(): 89 | locales.append((key, val)) 90 | locales.append(('', 'Default')) 91 | return locales 92 | 93 | 94 | @app.route('/ui') 95 | @login_required 96 | def ui(): 97 | locales = _setup_locales_to_display() 98 | available_themes = instance.available_themes() 99 | return render('ui.html', 100 | locales=locales, 101 | available_themes=available_themes, 102 | **instance.settings['ui']) 103 | 104 | 105 | @app.route('/outgoing') 106 | @login_required 107 | def outgoing(): 108 | return render('outgoing.html', **instance.settings['outgoing']) 109 | 110 | 111 | @app.route('/engines') 112 | @login_required 113 | def engines(): 114 | return render('engines.html', engines=instance.engines) 115 | 116 | 117 | @app.route('/engine//edit', methods=['GET', 'POST']) 118 | @login_required 119 | def edit_engine(engine_name): 120 | skip_attrs = ('name', 'continuous_errors', 'paging', 'suspend_end_time') 121 | engine = instance.engines[engine_name] 122 | attrs = [] 123 | type_map = {str: 'str', float: 'float', int: 'float', bool: 'bool'} 124 | for attr in dir(engine): 125 | if attr.startswith('_') or attr in skip_attrs: 126 | continue 127 | attr_value = getattr(engine, attr) 128 | attr_type = type(attr_value) 129 | if attr_type not in (str, int, float, bool, unicode): 130 | continue 131 | if request.method == 'POST': 132 | try: 133 | attr_value = attr_type(request.form[attr]) 134 | setattr(engine, attr, attr_value) 135 | except: 136 | print("attr not found or type mismatched", attr, attr_type, request.form.get(attr)) 137 | attrs.append((attr, attr_value, type_map[attr_type])) 138 | if request.method == 'POST': 139 | instance.save_settings({'section': 'engine', 'engine': engine}) 140 | instance.reload() 141 | return render('edit_engine.html', engine=engine, engine_attrs=attrs, isinstance=isinstance) 142 | 143 | 144 | @app.route('/settings') 145 | @login_required 146 | def settings(): 147 | return 'settings' 148 | 149 | 150 | @app.route('/save', methods=['POST']) 151 | @login_required 152 | def save(): 153 | if request.form is None or 'section' not in request.form: 154 | return redirect(url_for('index')) 155 | 156 | instance.save_settings(request.form) 157 | instance.reload() 158 | 159 | return redirect(url_for(request.form['section'])) 160 | 161 | 162 | @app.route('/start') 163 | @login_required 164 | def start_instance(): 165 | instance.start() 166 | return redirect(url_for('index')) 167 | 168 | 169 | @app.route('/stop') 170 | @login_required 171 | def stop_instance(): 172 | instance.stop() 173 | return redirect(url_for('index')) 174 | 175 | 176 | @app.route('/restore_defaults') 177 | @login_required 178 | def restore_defaults(): 179 | instance.restore_defaults() 180 | return redirect(url_for('index')) 181 | 182 | 183 | @app.route('/reload') 184 | @login_required 185 | def reload_instance(): 186 | instance.reload() 187 | return redirect(url_for('index')) 188 | 189 | 190 | @app.route('/update') 191 | @login_required 192 | def update(): 193 | instance.update() 194 | return redirect(url_for('index')) 195 | 196 | 197 | def _check_db(): 198 | global is_user_missing 199 | try: 200 | user = User.query.first() 201 | if user: 202 | is_user_missing = False 203 | except: 204 | pass 205 | 206 | 207 | def run(): 208 | init_db() 209 | _check_db() 210 | with instance: 211 | app.run(port=configuration['app']['port'], debug=False) 212 | 213 | 214 | if __name__ == '__main__': 215 | run() 216 | -------------------------------------------------------------------------------- /docs/images/engineslist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvch/searx-admin/95fe63bd6b39cbd58b61f77bd585fb88cd805076/docs/images/engineslist.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | flask 3 | flask-sqlalchemy 4 | flask-security 5 | bcrypt 6 | requests 7 | --------------------------------------------------------------------------------