diff options
author | Michael DeHaan <mdehaan@redhat.com> | 2008-07-08 12:21:57 -0400 |
---|---|---|
committer | Michael DeHaan <mdehaan@redhat.com> | 2008-07-08 12:21:57 -0400 |
commit | 692807b31f32d79c90e202a8edca3508dbd03e6b (patch) | |
tree | 4e6dc47e8a7f61d2477b9837a527407e51ac6162 | |
parent | cd7320d59ec7f6acc087507686a05711a59654bc (diff) | |
parent | 2db8f86f027509846d2bf0c0af801c902f124e78 (diff) | |
download | func-692807b31f32d79c90e202a8edca3508dbd03e6b.tar.gz func-692807b31f32d79c90e202a8edca3508dbd03e6b.tar.xz func-692807b31f32d79c90e202a8edca3508dbd03e6b.zip |
Merge branch 'makkalot_extreme'
33 files changed, 1616 insertions, 106 deletions
diff --git a/func/minion/modules/echo.py b/func/minion/modules/echo.py new file mode 100644 index 0000000..31ecf5f --- /dev/null +++ b/func/minion/modules/echo.py @@ -0,0 +1,131 @@ +""" +Test module for rendering funcweb +""" + +import func_module + +class EchoTest(func_module.FuncModule): + + version = "0.0.1" + api_version = "0.0.1" + description = "Module that all of its methods returns back the same thing it recieves!" + + def run_string(self, command): + """ + Run String + """ + return str(command) + + def run_int(self,command): + """ + Run Integer + """ + return int(command) + + def run_float(self,command): + """ + Run float + """ + return float(command) + + def run_options(self,command): + """ + Run options + """ + return str(command) + + def run_list(self,command): + """ + Run a list + """ + return command + + def run_hash(self,command): + """ + Run hash + """ + + return command + + def run_boolean(self,command): + """ + Run boolean + """ + return command + + def register_method_args(self): + """ + Implementing the argument getter + """ + return { + 'run_string':{ + 'args': + { + 'command':{ + 'type':'string', + 'optional':False + } + }, + 'description':'Returns back a string' + }, + 'run_int':{ + 'args': + { + 'command':{ + 'type':'int', + 'optional':False + } + }, + 'description':'Returns back an integer' + }, + 'run_float':{ + 'args': + { + 'command':{ + 'type':'float', + 'optional':False + }, + }, + 'description':'Returns back a float' + }, + 'run_options':{ + 'args':{ + 'command':{ + 'type':'string', + 'optional':False, + 'options':['first_option','second_option','third_option'] + }, + }, + 'description':'Getting the status of the service_name' + }, + 'run_list':{ + 'args': + { + 'command':{ + 'type':'list', + 'optional':False + } + }, + 'description':'Returns back a list' + }, + 'run_hash':{ + 'args': + { + 'command':{ + 'type':'hash', + 'optional':False + } + }, + 'description':'Returns back a hash' + }, + 'run_boolean':{ + 'args': + { + 'command':{ + 'type':'boolean', + 'optional':False + } + }, + 'description':'Returns back a boolean' + } + } diff --git a/funcweb/AUTHORS b/funcweb/AUTHORS new file mode 100644 index 0000000..375480b --- /dev/null +++ b/funcweb/AUTHORS @@ -0,0 +1,15 @@ + +funcweb is written and maintained by (alphabetically) ... + + Michael DeHaan <mdehaan@redhat.com> + Adrian Likins <alikins@redhat.com> + Seth Vidal <skvidal@redhat.com> + ... + +Additional patches and contributions by ... + + Denis Kurov <makkalot@gmail.com> + Luke Macken <lmacken@redhat.com> + [ send in patches to get your name here ] + + diff --git a/funcweb/LICENSE b/funcweb/LICENSE new file mode 100644 index 0000000..08ddefd --- /dev/null +++ b/funcweb/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/funcweb/MANIFEST.in b/funcweb/MANIFEST.in new file mode 100644 index 0000000..388846a --- /dev/null +++ b/funcweb/MANIFEST.in @@ -0,0 +1,8 @@ +include version +recursive-include etc * +recursive-include init-scripts * +recursive-include funcweb * +include AUTHORS +include LICENSE +include README + diff --git a/funcweb/Makefile b/funcweb/Makefile new file mode 100755 index 0000000..d9a865a --- /dev/null +++ b/funcweb/Makefile @@ -0,0 +1,87 @@ +VERSION = $(shell echo `awk '{ print $$1 }' version`) +RELEASE = $(shell echo `awk '{ print $$2 }' version`) +NEWRELEASE = $(shell echo $$(($(RELEASE) + 1))) + + +TOPDIR = $(shell pwd) +DIRS = funcweb funcweb/config funcweb/identity funcweb/templates funcweb/tests funcweb/static \ + funcweb/static/javascript funcweb/static/css funcweb/static/images +PYDIRS = funcweb funcweb/identity funcweb/tests +INITDIR = init-scripts + +all: rpms + +bumprelease: + -echo "$(VERSION) $(NEWRELEASE)" > version + +setversion: + -echo "$(VERSION) $(RELEASE)" > version + +build: clean + python setup.py build -f + +clean: + -rm -f MANIFEST + -rm -rf build/ + -rm -rf dist/ + -rm -rf *~ + -rm -rf rpm-build/ + -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done + +clean_hard: + -rm -rf $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")/funcweb + +clean_harder: + -rm -rf /etc/httpd/conf.d/funcweb.conf + -rm -rf /etc/funcweb + +clean_hardest: clean_rpms + +sdist: + python setup.py sdist + +install: build + python setup.py install -f + +install_hard: clean_hard install + +install_harder: clean_harder install + +install_hardest: clean_harder clean_rpms rpms install_rpm restart + +install_rpm: + -rpm -Uvh rpm-build/funcweb-$(VERSION)-$(RELEASE)$(shell rpm -E "%{?dist}").noarch.rpm + +restart: + -/etc/init.d/funcwebd restart + +recombuild: install_harder restart + +clean_rpms: + -rpm -e funcweb + +new-rpms: bumprelease rpms + +pychecker: + -for d in $(PYDIRS); do ($(MAKE) -C $$d pychecker ); done +pyflakes: + -for d in $(PYDIRS); do ($(MAKE) -C $$d pyflakes ); done + +money: clean + -sloccount --addlang "makefile" $(TOPDIR) $(PYDIRS) $(EXAMPLEDIR) $(INITDIR) + +unittest: + -nosetests -v -w funcweb/tests + +rpms: build sdist + mkdir -p rpm-build + cp dist/*.gz rpm-build/ + cp version rpm-build/ + rpmbuild --define "_topdir %(pwd)/rpm-build" \ + --define "_builddir %{_topdir}" \ + --define "_rpmdir %{_topdir}" \ + --define "_srcrpmdir %{_topdir}" \ + --define '_rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm' \ + --define "_specdir %{_topdir}" \ + --define "_sourcedir %{_topdir}" \ + -ba funcweb.spec diff --git a/funcweb/etc/funcweb.conf b/funcweb/etc/funcweb.conf new file mode 100644 index 0000000..2b73eb7 --- /dev/null +++ b/funcweb/etc/funcweb.conf @@ -0,0 +1,18 @@ +NameVirtualHost 127.0.0.1:443 + +<VirtualHost 127.0.0.1:443> + ServerName 127.0.0.1 + SSLEngine on + SSLCertificateFile /etc/pki/tls/certs/localhost.crt + SSLCertificateKeyFile /etc/pki/tls/private/localhost.key + Errorlog /var/log/httpd/funcweb-error + Customlog /var/log/httpd/funcweb-access common + UseCanonicalName Off + ServerSignature Off + AddDefaultCharset utf-8 + ProxyPreserveHost On + ProxyRequests Off + #ProxyPass /static/ ! + ProxyPass / http://127.0.0.1:51236/ + ProxyPassReverse / http://127.0.0.1:51236/ +</VirtualHost> diff --git a/funcweb/etc/prod.cfg b/funcweb/etc/prod.cfg new file mode 100644 index 0000000..91d8e3f --- /dev/null +++ b/funcweb/etc/prod.cfg @@ -0,0 +1,66 @@ +[global] +# This is where all of your settings go for your development environment +# Settings that are the same for both development and production +# (such as template engine, encodings, etc.) all go in +# funcweb/config/app.cfg + +# DATABASE (We're trying our best to avoid this) + +# driver://username:password@host:port/database + +# pick the form for your database +# sqlalchemy.dburi="postgres://username@hostname/databasename" +# sqlalchemy.dburi="mysql://username:password@hostname:port/databasename" +# sqlalchemy.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite" + +# If you have sqlite, here's a simple default to get you started +# in development +# sqlalchemy.dburi="sqlite:///devdata.sqlite" +base_url_filter.on = True +base_url_filter.use_x_forwarded_host = True + +# SERVER + +# Some server parameters that you may want to tweak +server.socket_port=51236 + +# Enable the debug output at the end on pages. +# log_debug_info_filter.on = False + +server.socket_host="127.0.0.1" +server.environment="production" +autoreload.package="funcweb" + +# Auto-Reload after code modification +# autoreload.on = True + +# Set to True if you'd like to abort execution if a controller gets an +# unexpected parameter. False by default +tg.strict_parameters = True + +# LOGGING +# Logging configuration generally follows the style of the standard +# Python logging module configuration. Note that when specifying +# log format messages, you need to use *() for formatting variables. +# Deployment independent log configuration is in funcweb/config/log.cfg +[logging] + +[[loggers]] +[[[funcweb]]] +level='DEBUG' +qualname='funcweb' +handlers=['error_out'] + + +[[[access]]] +level='INFO' +qualname='turbogears.access' +handlers=['error_out'] +propagate=0 + +[[[identity]]] +level='INFO' +qualname='turbogears.identity' +handlers=['error_out'] +propagate=0 + diff --git a/funcweb/funcweb.spec b/funcweb/funcweb.spec new file mode 100644 index 0000000..54c41a1 --- /dev/null +++ b/funcweb/funcweb.spec @@ -0,0 +1,138 @@ + +%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} + +%define is_suse %(test -e /etc/SuSE-release && echo 1 || echo 0) + +%define version 0.1 +Summary: Web GUI for FUNC API +Name: funcweb +Source1: version +Version: %(echo `awk '{ print $1 }' %{SOURCE1}`) +Release: %(echo `awk '{ print $2 }' %{SOURCE1}`)%{?dist} +License: GPLv2+ +Group: Applications/System +Source0: %{name}-%{version}.tar.gz + +#packages that are required +Requires: python >= 2.3 +Requires: func >= 0.20 +Requires: certmaster >= 0.1 +Requires: mod_ssl >= 2.0 +Requires: httpd >= 2.0 +Requires: TurboGears >= 1.0.4.2 + +#the build requires +BuildRequires: python-devel +BuildRequires: TurboGears >= 1.0.4.2 +%if %is_suse +BuildRequires: gettext-devel +%else +%if 0%{?fedora} >= 8 +BuildRequires: python-setuptools-devel +%else +BuildRequires: python-setuptools +%endif +%endif +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot +BuildArch: noarch +Url: https://hosted.fedoraproject.org/projects/func/ +%description + +FuncWeb is the Web GUI management tool for commandline based tool func. + +%prep +%setup -q + +%build +%{__python} setup.py build + +%install +test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT +%{__python} setup.py install --prefix=/usr --root=$RPM_BUILD_ROOT + +%clean +rm -fr $RPM_BUILD_ROOT + +%files +%defattr(-, root, root, -) +%if 0%{?fedora} >= 8 +%dir %{python_sitelib}/funcweb*egg-info +%{python_sitelib}/funcweb*egg-info/* +%endif + +#creating the directory structure +%dir %{python_sitelib}/funcweb/ +%dir %{python_sitelib}/funcweb/config +%dir %{python_sitelib}/funcweb/templates +%dir %{python_sitelib}/funcweb/tests +%dir %{python_sitelib}/funcweb/static +%dir %{python_sitelib}/funcweb/static/css +%dir %{python_sitelib}/funcweb/static/images +%dir %{python_sitelib}/funcweb/static/javascript +%dir %{python_sitelib}/funcweb/identity +%dir %{_sysconfdir}/%{name} +%config(noreplace) %{_sysconfdir}/httpd/conf.d/funcweb.conf +%config(noreplace) %{_sysconfdir}/%{name}/prod.cfg + +#adding the server startup shutdown thing +/etc/init.d/funcwebd + +#the python files for funcweb +%{python_sitelib}/funcweb/*.py* +%{python_sitelib}/funcweb/Makefile +%{python_sitelib}/funcweb/config/*.py* +%{python_sitelib}/funcweb/config/*.cfg +%{python_sitelib}/funcweb/templates/*.py* +%{python_sitelib}/funcweb/templates/*.kid +%{python_sitelib}/funcweb/templates/*.html +%{python_sitelib}/funcweb/templates/Makefile +%{python_sitelib}/funcweb/tests/*.py* +%{python_sitelib}/funcweb/tests/Makefile +%{python_sitelib}/funcweb/static/css/*.css +%{python_sitelib}/funcweb/static/css/Makefile +%{python_sitelib}/funcweb/static/images/*.png +%{python_sitelib}/funcweb/static/images/*.ico +%{python_sitelib}/funcweb/static/images/*.gif +%{python_sitelib}/funcweb/static/images/Makefile +%{python_sitelib}/funcweb/static/javascript/*.js +%{python_sitelib}/funcweb/static/javascript/Makefile +%{python_sitelib}/funcweb/identity/*.py* +%{python_sitelib}/funcweb/identity/Makefile +/usr/bin/funcwebd +%doc README + +%post +# for suse +if [ -x /usr/lib/lsb/install_initd ]; then + /usr/lib/lsb/install_initd /etc/init.d/funcwebd +# for red hat distros +elif [ -x /sbin/chkconfig ]; then + /sbin/chkconfig --add funcwebd +# or, the old fashioned way +else + for i in 2 3 4 5; do + ln -sf /etc/init.d/funcwebd /etc/rc.d/rc${i}.d/S99funcwebd + done + for i in 1 6; do + ln -sf /etc/init.d/funcwebd /etc/rc.d/rc${i}.d/S99funcwebd + done +fi + +#before uninstall the things +%preun +if [ "$1" = 0 ] ; then + /etc/init.d/funcwebd stop > /dev/null 2>&1 + if [ -x /usr/lib/lsb/remove_initd ]; then + /usr/lib/lsb/remove_initd /etc/init.d/funcwebd + elif [ -x /sbin/chkconfig ]; then + /sbin/chkconfig --del funcwebd + else + rm -f /etc/rc.d/rc?.d/???funcwebd + fi +fi + + +%changelog +* Sat Jul 05 2008 Denis Kurov <makkalot@gmail.com> - 0.1 +- The first RPM for funcweb with new dynamic widget stuff + diff --git a/funcweb/funcweb/Makefile b/funcweb/funcweb/Makefile new file mode 100755 index 0000000..d2a3a2b --- /dev/null +++ b/funcweb/funcweb/Makefile @@ -0,0 +1,6 @@ +clean:: + @rm -fv *.pyc *~ .*~ *.pyo + @find . -name .\#\* -exec rm -fv {} \; + @rm -fv *.rpm + -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done + diff --git a/funcweb/funcweb/commands.py b/funcweb/funcweb/commands.py index 043ce1f..154309a 100644 --- a/funcweb/funcweb/commands.py +++ b/funcweb/funcweb/commands.py @@ -11,6 +11,7 @@ pkg_resources.require("TurboGears") import turbogears import cherrypy +import func.utils as futils cherrypy.lowercase_api = True @@ -30,17 +31,25 @@ def start(): # is probably installed and we'll look first for a file called # 'prod.cfg' in the current directory and then for a default # config file called 'default.cfg' packaged in the egg. - if len(sys.argv) > 1: + if exists("/etc/funcweb/prod.cfg"): + print "I use the production ito the etc !" + configfile = "/etc/funcweb/prod.cfg" + + elif len(sys.argv) > 1: + print "We got something from the sys" configfile = sys.argv[1] elif exists(join(setupdir, "setup.py")): + print "I use the dev one into the dev dir" configfile = join(setupdir, "dev.cfg") elif exists(join(curdir, "prod.cfg")): + print "I use the prod one into the cur dir" configfile = join(curdir, "prod.cfg") else: try: configfile = pkg_resources.resource_filename( pkg_resources.Requirement.parse("funcweb"), "config/default.cfg") + print "That is another default conf" except pkg_resources.DistributionNotFound: raise ConfigurationError("Could not find default configuration.") @@ -48,5 +57,12 @@ def start(): modulename="funcweb.config") from funcweb.controllers import Root - - turbogears.start_server(Root()) + + if exists("/etc/funcweb/prod.cfg"): + futils.daemonize("/var/run/funcwebd.pid") + #then start the server + try: + turbogears.start_server(Root()) + except Exception,e: + print "Exception occured :",e + sys.exit(1) diff --git a/funcweb/funcweb/controllers.py b/funcweb/funcweb/controllers.py index f2c030f..b2748a6 100644 --- a/funcweb/funcweb/controllers.py +++ b/funcweb/funcweb/controllers.py @@ -28,14 +28,33 @@ def validate_decorator_updater(validator_value=None): class Root(controllers.RootController): - + #preventing the everytime polling and getting + #func = Overlord("name") thing + func_cache={ + 'fc_object':None,#the fc = Overlord() thing, + 'glob':None, + 'minion_name':None, + 'module_name':None, + 'modules':None, + 'minions':None, + 'methods':None + } #will be reused for widget validation @expose(template="funcweb.templates.minions") @identity.require(identity.not_anonymous()) def minions(self, glob='*'): """ Return a list of our minions that match a given glob """ - return dict(minions=Minions(glob).get_all_hosts()) + #make the cache thing + if self.func_cache['glob'] == glob: + minions = self.func_cache['minions'] + else: + #we dont have it it is for first time so lets pull it + minions=Minions(glob).get_all_hosts() + self.func_cache['glob']=glob + self.func_cache['minions']=minions + + return dict(minions=minions) index = minions # start with our minion view, for now @@ -46,47 +65,106 @@ class Root(controllers.RootController): If only the minion name is given, it will display a list of modules for that minion. If a module is supplied, it will display a list of - methods. If a method is supplied, it will display a method execution - form. + methods. """ - fc = Overlord(name) - if not module: # list all modules - #just list those who have get_method_args - modules = fc.system.list_modules() - display_modules = [] - #FIXME slow really i know ! - for module in modules.itervalues(): - for mod in module: - #if it is not empty - if getattr(fc,mod).get_method_args()[name]: - display_modules.append(mod) + #if we have it in the cache + if self.func_cache['minion_name'] == name: + fc = self.func_cache['fc_object'] + else: + fc = Overlord(name) + self.func_cache['fc_object']=fc + self.func_cache['minion_name']=name + #reset the children :) + self.func_cache['module_name']=None + self.func_cache['modules']=None + self.func_cache['methods']=None + + #should also reset the other fields or not ? + + + if not module: + if not self.func_cache['modules']: + modules = fc.system.list_modules() + display_modules = [] + + for module in modules.itervalues(): + for mod in module: + #if it is not empty + if getattr(fc,mod).get_method_args()[name]: + display_modules.append(mod) + + #put it into the cache to make that slow thing faster + self.func_cache['modules']=display_modules + + else: + #print "Im in the cache" + #just list those who have get_method_args + display_modules = self.func_cache['modules'] + modules = {} modules[name]=display_modules return dict(modules=modules) else: # a module is specified - if method: # minion.module.method specified; bring up execution form - return dict(minion=name, module=module, method=method, - tg_template="funcweb.templates.method") - else: # return a list of methods for specified module - modules = getattr(fc, module).list_methods() + if not method: # return a list of methods for specified module + #first check if we have it into the cache + if self.func_cache['module_name'] == module and self.func_cache['methods']: + modules = self.func_cache['methods'] + #print "Im in the cache" + + else: + self.func_cache['module_name']= module + #display the list only that is registered with register_method template ! + registered_methods=getattr(fc,module).get_method_args()[name].keys() + modules = getattr(fc, module).list_methods() + for mods in modules.itervalues(): + from copy import copy + cp_mods = copy(mods) + for m in cp_mods: + if not m in registered_methods: + mods.remove(m) + + #store into cache if we get it again + self.func_cache['methods'] = modules + #display em return dict(modules=modules, module=module, tg_template="funcweb.templates.module") + else: + return "Wrong place :)" @expose(template="funcweb.templates.method_args") @identity.require(identity.not_anonymous()) def method_display(self,minion=None,module=None,method=None): + """ + That method generates the input widget for givent method. + """ global global_form - fc = Overlord(minion) + if self.func_cache['minion_name'] == minion: + fc = self.func_cache['fc_object'] + else: + fc = Overlord(minion) + self.func_cache['fc_object']=fc + self.func_cache['minion_name']=minion + #reset the children :) + self.func_cache['module_name']=module + self.func_cache['modules']=None + self.func_cache['methods']=None + + #get the method args method_args = getattr(fc,module).get_method_args() if not method_args.values(): - print "Not registered method here" + #print "Not registered method here" return dict(minion_form = None,minion=minion,module=module,method=method) minion_arguments = method_args[minion][method]['args'] + #the description of the method we are going to display + if method_args[minion][method].has_key('description'): + description = method_args[minion][method]['description'] + else: + description = None if minion_arguments: wlist_object = WidgetListFactory(minion_arguments,minion=minion,module=module,method=method) wlist_object = wlist_object.get_widgetlist_object() @@ -104,14 +182,17 @@ class Root(controllers.RootController): del wlist_object del minion_arguments - return dict(minion_form =minion_form,minion=minion,module=module,method=method) + return dict(minion_form =minion_form,minion=minion,module=module,method=method,description=description) else: - return dict(minion_form = None,minion=minion,module=module,method=method) + return dict(minion_form = None,minion=minion,module=module,method=method,description = description) @expose(template="funcweb.templates.login") def login(self, forward_url=None, previous_url=None, *args, **kw): + """ + The login form for not registered users + """ from cherrypy import request, response if not identity.current.anonymous \ and identity.was_login_attempted() \ @@ -157,20 +238,34 @@ class Root(controllers.RootController): @identity.require(identity.not_anonymous()) def post_form(self,**kw): """ - Data processing part + Data processing part for methods that accept some inputs. + Method recieves the method arguments for minion method then + orders them into their original order and sends the xmlrpc + request to the minion ! """ if kw.has_key('minion') and kw.has_key('module') and kw.has_key('method'): #assign them because we need the rest so dont control everytime #and dont make lookup everytime ... + #the del statements above are important dont remove them :) minion = kw['minion'] del kw['minion'] module = kw['module'] del kw['module'] method = kw['method'] del kw['method'] - - #everytime we do that should be a clever way for that ??? - fc = Overlord(minion) + + if self.func_cache['minion_name'] == minion: + fc = self.func_cache['fc_object'] + else: + fc = Overlord(minion) + self.func_cache['fc_object']=fc + self.func_cache['minion_name']=minion + #reset the children :) + self.func_cache['module_name']=module + self.func_cache['modules']=None + self.func_cache['methods']=None + + #get again the method args to get their order : arguments=getattr(fc,module).get_method_args() #so we know the order just allocate and put them there @@ -196,7 +291,17 @@ class Root(controllers.RootController): arguments so they provide only some information,executed by pressing only the link ! """ - fc = Overlord(minion) + if self.func_cache['minion_name'] == minion: + fc = self.func_cache['fc_object'] + else: + fc = Overlord(minion) + self.func_cache['fc_object']=fc + self.func_cache['minion_name']=minion + #reset the children :) + self.func_cache['module_name']=module + self.func_cache['modules']=None + self.func_cache['methods']=None + result = getattr(getattr(fc,module),method)() return str(result) @@ -204,5 +309,8 @@ class Root(controllers.RootController): @expose() def logout(self): + """ + The logoout part + """ identity.current.logout() raise redirect("/") diff --git a/funcweb/funcweb/identity/Makefile b/funcweb/funcweb/identity/Makefile new file mode 100755 index 0000000..ac39036 --- /dev/null +++ b/funcweb/funcweb/identity/Makefile @@ -0,0 +1,5 @@ +clean:: + @rm -fv *.pyc *~ .*~ *.pyo + @find . -name .\#\* -exec rm -fv {} \; + @rm -fv *.rpm + -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done diff --git a/funcweb/funcweb/static/css/Makefile b/funcweb/funcweb/static/css/Makefile new file mode 100755 index 0000000..d2a3a2b --- /dev/null +++ b/funcweb/funcweb/static/css/Makefile @@ -0,0 +1,6 @@ +clean:: + @rm -fv *.pyc *~ .*~ *.pyo + @find . -name .\#\* -exec rm -fv {} \; + @rm -fv *.rpm + -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done + diff --git a/funcweb/funcweb/static/css/expanding_form.css b/funcweb/funcweb/static/css/expanding_form.css new file mode 100644 index 0000000..d7924c7 --- /dev/null +++ b/funcweb/funcweb/static/css/expanding_form.css @@ -0,0 +1,6 @@ +li.appendableformfieldlist ul { + list-style-type: none; +} +li.appendableformfieldlist ul li { + display: inline; +} diff --git a/funcweb/funcweb/static/images/Makefile b/funcweb/funcweb/static/images/Makefile new file mode 100755 index 0000000..d2a3a2b --- /dev/null +++ b/funcweb/funcweb/static/images/Makefile @@ -0,0 +1,6 @@ +clean:: + @rm -fv *.pyc *~ .*~ *.pyo + @find . -name .\#\* -exec rm -fv {} \; + @rm -fv *.rpm + -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done + diff --git a/funcweb/funcweb/static/images/loading.gif b/funcweb/funcweb/static/images/loading.gif Binary files differindex 83729ec..794a549 100644 --- a/funcweb/funcweb/static/images/loading.gif +++ b/funcweb/funcweb/static/images/loading.gif diff --git a/funcweb/funcweb/static/javascript/Makefile b/funcweb/funcweb/static/javascript/Makefile new file mode 100755 index 0000000..d2a3a2b --- /dev/null +++ b/funcweb/funcweb/static/javascript/Makefile @@ -0,0 +1,6 @@ +clean:: + @rm -fv *.pyc *~ .*~ *.pyo + @find . -name .\#\* -exec rm -fv {} \; + @rm -fv *.rpm + -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done + diff --git a/funcweb/funcweb/static/javascript/expanding_form.js b/funcweb/funcweb/static/javascript/expanding_form.js new file mode 100644 index 0000000..2c1b67f --- /dev/null +++ b/funcweb/funcweb/static/javascript/expanding_form.js @@ -0,0 +1,135 @@ + +/* Based on dynwidget by Randall Smith */ + +var ExpandingForm = { + + li_count: null, + li_template: null, + + removeItem: function(node_id) { + this_node = document.getElementById(node_id); + parent_node = this_node.parentNode; + list_items = ExpandingForm.getChildNodesByAttribute(parent_node, 'tagname', 'TR') + ExpandingForm.updateVars(parent_node.parentNode); + if (list_items.length == 1) { + alert('This item cannot be removed.') + } + else { + parent_node.removeChild(this_node); + } + }, + + getChildNodesByAttribute: function(pnode, identifier, value) { + new_nodes = new Array; + nodes = pnode.childNodes; + for (node_id=0, len=nodes.length; node_id<len; node_id++) { + node = nodes[node_id]; + if (identifier == 'tagname') { + if (node && node.tagName == value) { + new_nodes.push(node); + } + } + else if (node && node.getAttribute(identifier) == value) { + new_nodes.push(node); + } + } + return new_nodes; + }, + + updateVars: function(parent_node){ + tbody = ExpandingForm.getChildNodesByAttribute(parent_node, 'tagname', 'TBODY')[0]; + list_items = ExpandingForm.getChildNodesByAttribute(tbody, 'tagname', 'TR'); + ExpandingForm['li_template'] = list_items[0]; + ExpandingForm['li_count'] = list_items.length + }, + + addItem: function(parent_id) { + var parent_node = document.getElementById(parent_id); + ExpandingForm.updateVars(parent_node); + tbody = ExpandingForm.getChildNodesByAttribute(parent_node, 'tagname', 'TBODY')[0]; + list_items = ExpandingForm.getChildNodesByAttribute(tbody, 'tagname', 'TR'); + li_clone = ExpandingForm.li_template.cloneNode(true); + // Fix the labels. +/* labels = li_clone.getElementsByTagName('LABEL') + for (node_id=0, len=labels.length; node_id<len; node_id++) { + label = labels[node_id]; + // Why am I having to check for the node type? + if (label.nodeType == 1) { + label.setAttribute('for', label.getAttribute('for').replace( + '_0_', '_' + ExpandingForm.li_count + '_')); + } + } +*/ // Fix the input values. + inputs = li_clone.getElementsByTagName('INPUT') + for (node_id=0, len=inputs.length; node_id<len; node_id++) { + input = inputs[node_id]; + if (input.nodeType == 1) { + input.setAttribute('id', input.getAttribute('id').replace( + '_0_', '_' + ExpandingForm.li_count + '_')); + if (input.getAttribute('name')) + { + input.setAttribute('name', input.getAttribute('name').replace( + '-0', '-' + ExpandingForm.li_count)); + } + if (input.getAttribute('type') == 'button') + { + input.setAttribute('value', input.getAttribute('value')) + } + else + { + input.value = ''; + } + } + } + inputs = li_clone.getElementsByTagName('SELECT') + for (node_id=0, len=inputs.length; node_id<len; node_id++) { + input = inputs[node_id]; + if (input.nodeType == 1) { + input.setAttribute('id', input.getAttribute('id').replace( + '_0_', '_' + ExpandingForm.li_count + '_')); + input.setAttribute('name', input.getAttribute('name').replace( + '-0', '-' + ExpandingForm.li_count)); + input.value = ''; + } + } + li_clone.setAttribute('id', li_clone.getAttribute('id').replace( + '_0', '_' + ExpandingForm.li_count)) + // Add a remove link. + child_tds = li_clone.getElementsByTagName('td'); + last_td = child_tds[child_tds.length-1]; + a_clone = last_td.getElementsByTagName('A')[0]; + href_text = "javascript:ExpandingForm.removeItem('" + li_clone.getAttribute('ID') + "')"; + a_clone.setAttribute('href', href_text); + /* + link_li = document.createElement('LI'); + link_a = document.createElement('A'); + href_text = "javascript:ExpandingForm.removeItem('" + li_clone.getAttribute('ID') + "')"; + link_a.setAttribute('href', href_text); + /* + text = 'Remove (-)'; + link_text = document.createTextNode(text); + link_a.appendChild(link_text); + link_li.appendChild(link_a); + ul.appendChild(link_li); + */ + // Finally. + tbody = ExpandingForm.getChildNodesByAttribute(parent_node, 'tagname', 'TBODY')[0]; + tbody.appendChild(li_clone); + // Focus + li_clone.getElementsByTagName('INPUT')[0].focus() + ExpandingForm['li_count'] = ExpandingForm.li_count + 1; + } +} + +// Not specifically part of appendable_form_field, but used by the customised version of this form +function typeChanged(obj) { + var parent = obj.parentNode.parentNode.parentNode; + var preference = parent.getElementsByTagName("input")[1]; + var type = obj.value; + if (type == 'MX') { + preference.style.visibility = "visible"; + } + else { + preference.style.visibility = "hidden"; + } +} diff --git a/funcweb/funcweb/templates/Makefile b/funcweb/funcweb/templates/Makefile new file mode 100755 index 0000000..d2a3a2b --- /dev/null +++ b/funcweb/funcweb/templates/Makefile @@ -0,0 +1,6 @@ +clean:: + @rm -fv *.pyc *~ .*~ *.pyo + @find . -name .\#\* -exec rm -fv {} \; + @rm -fv *.rpm + -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done + diff --git a/funcweb/funcweb/templates/master.html b/funcweb/funcweb/templates/master.html index 073adc1..0ac8ebb 100644 --- a/funcweb/funcweb/templates/master.html +++ b/funcweb/funcweb/templates/master.html @@ -21,7 +21,10 @@ <style type="text/css" media="screen"> @import url("/static/css/style.css"); </style> - + + <link media="screen" href="/static/css/expanding_form.css" type="text/css" rel="stylesheet"/> + <script src="../static/javascript/expanding_form.js" type="text/javascript"></script> + <script type="text/javascript"> jQuery._$ = MochiKit.DOM.getElement; var myj = jQuery.noConflict(); diff --git a/funcweb/funcweb/templates/method_args.html b/funcweb/funcweb/templates/method_args.html index 2e7d4bd..870d562 100644 --- a/funcweb/funcweb/templates/method_args.html +++ b/funcweb/funcweb/templates/method_args.html @@ -5,6 +5,7 @@ <body> <div py:if="minion_form" id="men"> + <p py:if="description">Description : ${description}</p> ${ET(minion_form.display(displays_on='genshi'))} </div> <div py:if="not minion_form" id="men"> diff --git a/funcweb/funcweb/templates/minions.html b/funcweb/funcweb/templates/minions.html index 6a39f54..1db0f38 100644 --- a/funcweb/funcweb/templates/minions.html +++ b/funcweb/funcweb/templates/minions.html @@ -10,7 +10,7 @@ <ul> <li><h2>minions</h2></li> <li py:for="minion in minions"> - <a onclick="jQuery('#col3').hide();myj('#col4').hide();myj('#col5').hide();getElement('col2').innerHTML=toHTML(IMG({src:'../static/images/loading.gif',width:'80',height:'80'}));myj('#col2').hide().load('/minion/${minion}').show('slow');" href="#">${minion}</a> + <a onclick="jQuery('#col3').hide();myj('#col4').hide();myj('#col5').hide();getElement('col2').innerHTML=toHTML(IMG({src:'../static/images/loading.gif',width:'100',height:'100'}));myj('#col2').hide().load('/minion/${minion}').show('slow');" href="#">${minion}</a> </li> </ul> </div> diff --git a/funcweb/funcweb/templates/repeater_form.kid b/funcweb/funcweb/templates/repeater_form.kid new file mode 100644 index 0000000..606b30a --- /dev/null +++ b/funcweb/funcweb/templates/repeater_form.kid @@ -0,0 +1,30 @@ +<div class="expanding_form" xmlns:py="http://purl.org/kid/ns#"> +<table id="${field_id}"> +<thead><tr> + <th py:for="field in fields"> + <span class="fieldlabel" py:content="field.label" /> + </th> + </tr></thead> + <tbody> + <tr py:for="repetition in repetitions" + class="${field_class}" + id="${field_id}_${repetition}"> + + <td py:for="field in fields"> + <span py:content="field.display(value_for(field), + **params_for(field))" /> + <span py:if="error_for(field)" class="fielderror" + py:content="error_for(field)" /> + <span py:if="field.help_text" class="fieldhelp" + py:content="field_help_text" /> + </td> + <td> + <a + href="javascript:ExpandingForm.removeItem('${field_id}_${repetition}')">Remove (-)</a> + </td> + + </tr> + </tbody> +</table> +<a id="doclink" href="javascript:ExpandingForm.addItem('${field_id}');">Add ( + )</a> +</div> diff --git a/funcweb/funcweb/tests/Makefile b/funcweb/funcweb/tests/Makefile new file mode 100755 index 0000000..ac39036 --- /dev/null +++ b/funcweb/funcweb/tests/Makefile @@ -0,0 +1,5 @@ +clean:: + @rm -fv *.pyc *~ .*~ *.pyo + @find . -name .\#\* -exec rm -fv {} \; + @rm -fv *.rpm + -for d in $(DIRS); do ($(MAKE) -C $$d clean ); done diff --git a/funcweb/funcweb/tests/test_client_rendering.py b/funcweb/funcweb/tests/test_client_rendering.py new file mode 100644 index 0000000..f457da3 --- /dev/null +++ b/funcweb/funcweb/tests/test_client_rendering.py @@ -0,0 +1,45 @@ +from funcweb.widget_validation import WidgetSchemaFactory +from funcweb.widget_automation import WidgetListFactory,RemoteFormAutomation,RemoteFormFactory +from func.overlord.client import Overlord, Minions +import socket +import func.utils + +class TestClientWidgetRender(object): + minion = None + + def test_all_minions(self): + minions =Minions("*").get_all_hosts() + for m in minions: + self.minion = m + self.remote_widget_render() + + def remote_widget_render(self): + print "\n******testing minion : %s**********"%(self.minion) + fc = Overlord(self.minion) + modules = fc.system.list_modules() + display_modules={} + + print "Getting the modules that has exported arguments" + for module in modules.itervalues(): + for mod in module: + #if it is not empty + exported_methods = getattr(fc,mod).get_method_args()[self.minion] + if exported_methods: + print "%s will be rendered"%(mod) + display_modules[mod]=exported_methods + + #do the rendering work here + for module,exp_meths in display_modules.iteritems(): + for method_name,other_options in exp_meths.iteritems(): + minion_arguments = other_options['args'] + if minion_arguments: + wlist_object = WidgetListFactory(minion_arguments,minion=self.minion,module=module,method=method_name) + wlist_object = wlist_object.get_widgetlist_object() + #print wlist_object + wf = WidgetSchemaFactory(minion_arguments) + schema_man=wf.get_ready_schema() + minion_form = RemoteFormAutomation(wlist_object,schema_man) + print "%s.%s.%s rendered"%(self.minion,module,method_name) + + + diff --git a/funcweb/funcweb/tests/test_widget_automation.py b/funcweb/funcweb/tests/test_widget_automation.py index d02de93..36fc387 100644 --- a/funcweb/funcweb/tests/test_widget_automation.py +++ b/funcweb/funcweb/tests/test_widget_automation.py @@ -2,19 +2,18 @@ import unittest import turbogears from turbogears import testutil -from funcweb.widget_automation import WidgetListFactory,RemoteFormAutomation,RemoteFormFactory -from funcweb.widget_validation import WidgetSchemaFactory +from funcweb.widget_automation import * +from funcweb.widget_validation import * class TestWidgetListFactory(unittest.TestCase): def setUp(self): self.widget_factory = WidgetListFactory(self.get_test_default_args(),minion="myminion",module="mymodule",method="my_method") - - - def tearDown(self): - pass def test_default_args(self): + """ + Test to check the default args if they were assigned + """ compare_with = self.get_test_default_args() widget_list=self.widget_factory.get_widgetlist() @@ -22,6 +21,10 @@ class TestWidgetListFactory(unittest.TestCase): for argument_name,argument_options in compare_with.iteritems(): assert widget_list.has_key(argument_name) == True + #test the label + assert pretty_label(argument_name) == getattr(widget_list[argument_name],'label') + #print getattr(widget_list[argument_name],'label') + #print "The argument name is :",argument_name #because some of them dont have it like boolean if argument_options.has_key('default'): @@ -32,9 +35,47 @@ class TestWidgetListFactory(unittest.TestCase): if argument_options.has_key("options"): assert argument_options['options'] == getattr(widget_list[argument_name],"options") - - #that should be enough + + def test_add_specialized_list(self): + """ + Testing the internals of the special list widget + """ + test_list_data = self.get_test_default_args()['list_default'] + widget_list_object = self.widget_factory.get_widgetlist_object() + #not very efficient but works + #hash_widget_object should be a widgets.RepeatingFieldSet + list_widget_object = [h_obj for h_obj in widget_list_object if getattr(h_obj,'name')=='list_default'][0] + + assert isinstance(list_widget_object.fields[0],widgets.TextField) == True + assert getattr(list_widget_object.fields[0],'name') == 'listfield' + assert getattr(list_widget_object.fields[0],'label') == 'List Field' + + + def test_add_specialized_hash(self): + """ + Testing the internals of the special hash widget + """ + test_hash_data = self.get_test_default_args()['hash_default'] + widget_list_object = self.widget_factory.get_widgetlist_object() + #not very efficient but works + #hash_widget_object should be a widgets.RepeatingFieldSet + hash_widget_object = [h_obj for h_obj in widget_list_object if getattr(h_obj,'name')=='hash_default'][0] + + #print hash_widget_object.fields + #check the key data + assert isinstance(hash_widget_object.fields[0],widgets.TextField) == True + assert getattr(hash_widget_object.fields[0],'name') == 'keyfield' + assert getattr(hash_widget_object.fields[0],'label') == 'Key Field' + #check the value data + assert isinstance(hash_widget_object.fields[1],widgets.TextField) == True + assert getattr(hash_widget_object.fields[1],'name') == 'valuefield' + assert getattr(hash_widget_object.fields[1],'label') == 'Value Field' + + def test_get_widgetlist_object(self): + """ + Test the final widgetlist object + """ compare_with = self.get_test_default_args() widget_list_object = self.widget_factory.get_widgetlist_object() @@ -43,7 +84,7 @@ class TestWidgetListFactory(unittest.TestCase): all_fields = [getattr(field,"name") for field in widget_list_object] #print all_fields for argument_name in compare_with.keys(): - print argument_name + #print argument_name assert argument_name in all_fields #print getattr(widget_list_object,argument_name) @@ -58,11 +99,14 @@ class TestWidgetListFactory(unittest.TestCase): def test_remote_form_factory(self): from turbogears.view import load_engines load_engines() - + + schema_factory = WidgetSchemaFactory(self.get_test_default_args()) + schema_validator=schema_factory.get_ready_schema() + # WidgetsList object widget_list_object = self.widget_factory.get_widgetlist_object() #print widget_list_object - remote_form = RemoteFormFactory(widget_list_object).get_remote_form() + remote_form = RemoteFormFactory(widget_list_object,schema_validator).get_remote_form() #it is a key,value dict widget_list=self.widget_factory.get_widgetlist() @@ -75,6 +119,22 @@ class TestWidgetListFactory(unittest.TestCase): #print remote_form.render() + + def test_pretty_label(self): + """ + Testing the label converter util method + """ + test_strings = ('service_name','some__other','cool-arg','somenormal','someweir*1*2*3*3') + #print pretty_label(test_strings[0]) + assert pretty_label(test_strings[0]) == 'Service Name' + #print pretty_label(test_strings[1]) + assert pretty_label(test_strings[1]) == 'Some Other' + #print pretty_label(test_strings[2]) + assert pretty_label(test_strings[2]) == 'Cool Arg' + #print pretty_label(test_strings[3]) + assert pretty_label(test_strings[3]) == 'Somenormal' + #print pretty_label(test_strings[4]) + assert pretty_label(test_strings[4]) == 'Someweir*1*2*3*3' def get_test_default_args(self): return { @@ -107,14 +167,16 @@ class TestWidgetListFactory(unittest.TestCase): 'type':'hash', 'default':'default hash', 'optional':False, - 'description':'default description' + 'description':'default description', + 'validator':'^[0-9]*$' }, 'list_default':{ 'type':'list', 'default':'default list', 'optional':False, - 'description':'default description' + 'description':'default description', + 'validator':'^[0-9]*$' }, #will be converted to dropdown diff --git a/funcweb/funcweb/tests/test_widget_validation.py b/funcweb/funcweb/tests/test_widget_validation.py index c800d41..373ce0b 100644 --- a/funcweb/funcweb/tests/test_widget_validation.py +++ b/funcweb/funcweb/tests/test_widget_validation.py @@ -18,23 +18,22 @@ class TestWidgetValidator(unittest.TestCase): #do better test here for argument_name,arg_options in self.get_string_params().iteritems(): - #print argument_name - assert hasattr(schema_man,argument_name)==True + #print getattr(schema_man,'fields') + assert getattr(schema_man,'fields').has_key(argument_name) == True + current_schema_object = getattr(schema_man,'fields')[argument_name] #not very efficient but it si just a test :) - if argument_name != 'string_mix': - for arg,value in arg_options.iteritems(): - #print getattr(schema_man,argument_name) - if conversion_schema.has_key(arg): - if hasattr(getattr(schema_man,argument_name),conversion_schema[arg]): - #print arg,value - #couldnt find a way to test it !?? - if arg != 'validator': - assert getattr(getattr(schema_man,argument_name),conversion_schema[arg])==value - #print getattr(getattr(schema_man,argument_name),conversion_schema[arg]) - else: - #just print it to see what is inside because the test will be very hardcoded otherwise - #print getattr(schema_man,argument_name) + if argument_name == 'string_mix': continue + if arg_options.has_key('max_length'): + assert getattr(current_schema_object,'max') == arg_options['max_length'] + if arg_options.has_key('min_length'): + assert getattr(current_schema_object,'min') == arg_options['min_length'] + if arg_options.has_key('validator'): + assert getattr(current_schema_object,'regex') + if arg_options.has_key('optional'): + #print " ",argument_name," : ",getattr(schema_man,argument_name) + assert not getattr(current_schema_object,'not_empty') == arg_options['optional'] + print "Happy tests !" def test_int_validator(self): @@ -43,25 +42,26 @@ class TestWidgetValidator(unittest.TestCase): for argument_name,arg_options in self.get_int_params().iteritems(): #print argument_name - assert hasattr(schema_man,argument_name)==True + assert getattr(schema_man,'fields').has_key(argument_name)==True + current_schema_object = getattr(schema_man,'fields')[argument_name] #print " ",argument_name," : ",getattr(schema_man,argument_name) #if the argument includes some range if arg_options.has_key('range'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert getattr(getattr(schema_man,argument_name),'max') == arg_options['range'][1] - assert getattr(getattr(schema_man,argument_name),'min') == arg_options['range'][0] + assert getattr(current_schema_object,'max') == arg_options['range'][1] + assert getattr(current_schema_object,'min') == arg_options['range'][0] if arg_options.has_key('min'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert getattr(getattr(schema_man,argument_name),'min') == arg_options['min'] + assert getattr(current_schema_object,'min') == arg_options['min'] if arg_options.has_key('max'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert getattr(getattr(schema_man,argument_name),'max') == arg_options['max'] + assert getattr(current_schema_object,'max') == arg_options['max'] if arg_options.has_key('optional'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert not getattr(getattr(schema_man,argument_name),'not_empty') == arg_options['optional'] + assert not getattr(current_schema_object,'not_empty') == arg_options['optional'] print "Happy test!" @@ -72,20 +72,21 @@ class TestWidgetValidator(unittest.TestCase): for argument_name,arg_options in self.get_float_params().iteritems(): #print argument_name - assert hasattr(schema_man,argument_name)==True + assert getattr(schema_man,'fields').has_key(argument_name)==True + current_schema_object = getattr(schema_man,'fields')[argument_name] #print " ",argument_name," : ",getattr(schema_man,argument_name) if arg_options.has_key('min'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert getattr(getattr(schema_man,argument_name),'min') == arg_options['min'] + assert getattr(current_schema_object,'min') == arg_options['min'] if arg_options.has_key('max'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert getattr(getattr(schema_man,argument_name),'max') == arg_options['max'] + assert getattr(current_schema_object,'max') == arg_options['max'] if arg_options.has_key('optional'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert not getattr(getattr(schema_man,argument_name),'not_empty') == arg_options['optional'] + assert not getattr(current_schema_object,'not_empty') == arg_options['optional'] print "Happy test!" @@ -98,12 +99,14 @@ class TestWidgetValidator(unittest.TestCase): for argument_name,arg_options in testing_data.iteritems(): #print argument_name #should all the argument names really - assert hasattr(schema_man,argument_name)==True + assert getattr(schema_man,'fields').has_key(argument_name)==True + current_schema_object = getattr(schema_man,'fields')[argument_name] + #print " ",argument_name," : ",getattr(schema_man,argument_name) if arg_options.has_key('optional'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert not getattr(getattr(schema_man,argument_name),'not_empty') == arg_options['optional'] + assert not getattr(current_schema_object,'not_empty') == arg_options['optional'] print "Happy test!" @@ -121,16 +124,18 @@ class TestWidgetValidator(unittest.TestCase): for argument_name,arg_options in testing_data.iteritems(): #print argument_name #should all the argument names really - assert hasattr(schema_man,argument_name)==True #print " ",argument_name," : ",getattr(schema_man,argument_name) - + assert getattr(schema_man,'fields').has_key(argument_name)==True + current_schema_object = getattr(schema_man,'fields')[argument_name] + + if arg_options.has_key('validator'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert getattr(getattr(schema_man,argument_name),'regex_string') == arg_options['validator'] + assert getattr(current_schema_object,'regex_string') == arg_options['validator'] if arg_options.has_key('optional'): #print " ",argument_name," : ",getattr(schema_man,argument_name) - assert not getattr(getattr(schema_man,argument_name),'not_empty') == arg_options['optional'] + assert not getattr(current_schema_object,'not_empty') == arg_options['optional'] print "Happy test!" @@ -191,14 +196,15 @@ class TestWidgetValidator(unittest.TestCase): #test default mv = MinionListValidator() - assert mv.to_python(['fedora','debian','others']) == ['fedora','debian','others'] + [{'listfield': u'listone'}, {'listfield': u'listtwo'}] + assert mv.to_python([{'listfield': u'listone'}, {'listfield': u'listtwo'}]) == ['listone','listtwo'] del mv #test with regex mv = MinionListValidator(regex_string='^[A-Z]+$',not_empty=True) - assert mv.to_python(['FEDORA','MACOSX']) == ['FEDORA','MACOSX'] + assert mv.to_python([{'listfield': u'LISTONE'}, {'listfield': u'LISTTWO'}]) == ['LISTONE','LISTTWO'] self.assertRaises(validators.Invalid,mv.to_python,[]) - self.assertRaises(validators.Invalid,mv.to_python,['hey_error']) + self.assertRaises(validators.Invalid,mv.to_python,[{'listfield':'hey_error'}]) self.assertRaises(validators.Invalid,mv.to_python,{}) del mv @@ -210,15 +216,15 @@ class TestWidgetValidator(unittest.TestCase): #test default mv = MinionHashValidator() - assert mv.to_python({'fedora':1,'debian':2,'others':3}) == {'fedora':1,'debian':2,'others':3} + assert mv.to_python([{'keyfield':'keyvalue','valuefield':'valuehere'}]) == {'keyvalue':'valuehere'} del mv #test with regex mv = MinionHashValidator(regex_string='^[A-Z]+$',not_empty=True) - assert mv.to_python({'FEDORA':'FEDORA','MACOSX':'MACOSX'}) == {'FEDORA':'FEDORA','MACOSX':'MACOSX'} + assert mv.to_python([{'keyfield':'FEDORA','valuefield':'MACOSX'}]) == {'FEDORA':'MACOSX'} self.assertRaises(validators.Invalid,mv.to_python,{}) - self.assertRaises(validators.Invalid,mv.to_python,{'hey_error':12}) - self.assertRaises(validators.Invalid,mv.to_python,"hwy") + self.assertRaises(validators.Invalid,mv.to_python,[{'keyfield':12,'valuefield':123}]) + self.assertRaises(TypeError,mv.to_python,"hwy") del mv print "Happy testing !" @@ -305,7 +311,7 @@ class TestWidgetValidator(unittest.TestCase): 'default':'hey', 'optional':False, 'description':'default regex list', - 'validator':'^[A-Z]$' + 'validator':'^[A-Z]+$' }, } @@ -321,7 +327,7 @@ class TestWidgetValidator(unittest.TestCase): 'default':{'hey':12}, 'optional':False, 'description':'default regex hash', - 'validator':'^[A-Z]$' + 'validator':'^[A-Z]+$' }, } diff --git a/funcweb/funcweb/widget_automation.py b/funcweb/funcweb/widget_automation.py index 5d299bd..351603b 100644 --- a/funcweb/funcweb/widget_automation.py +++ b/funcweb/funcweb/widget_automation.py @@ -26,9 +26,9 @@ class WidgetListFactory(object): 'default_value':"TextField", }, 'hash':{ - 'default_value':"TextArea"}, + 'type':"RepeatingFieldSet"}, 'list':{ - 'default_value':"TextArea"} + 'type':"RepeatingFieldSet"} } #will contain the input widget created in that class @@ -83,11 +83,11 @@ class WidgetListFactory(object): #adding the hidden fields (that part wass adde later can be made more generic) if self.minion: - self.__widget_list['minion']= getattr(widgets,'HiddenField')(name="minion",value=self.minion,default=self.minion) + self.__widget_list['minion']= getattr(widgets,'HiddenField')(name="minion",default=self.minion) if self.module: - self.__widget_list['module']= getattr(widgets,'HiddenField')(name="module",value=self.module,default=self.module) + self.__widget_list['module']= getattr(widgets,'HiddenField')(name="module",default=self.module) if self.method: - self.__widget_list['method']= getattr(widgets,'HiddenField')(name="method",value=self.method,default=self.method) + self.__widget_list['method']= getattr(widgets,'HiddenField')(name="method",default=self.method) @@ -112,6 +112,68 @@ class WidgetListFactory(object): self.__widget_list[argument_name]=temp_object del temp_object + def __add_specialized_hash(self,argument,argument_name): + """ + Specialized option adder for hash, we need it to be diffferent + because the hash and list objects uses an advanced type of widgets + which make them to be able to add, remove fields during using the + web UI. It uses the RepeatingFieldSet which is able to contain the + other normal input widgets. It will have two fields (TextFields) + one for key : keyfield and other for value : valuefield + Also the validator addition is a little bit different and should + be done in that method also ... + + @param : argument : the argument options, + @param : argument_name : the name of the argument also the name of the widget + @return : Nothing + """ + hash_repeat_data = { + 'template':"funcweb.templates.repeater_form",#may change that if someone doesnt like my design :) + 'fields': [ + widgets.TextField(name="keyfield",label="Key Field"), + widgets.TextField(name="valuefield",label="Value Field") + ], + } + + #create the RepeatingFieldSet object and add it to global list like you do for others + temp_object = getattr(widgets,self.__convert_table[argument['type']]['type'])(**hash_repeat_data) + #print temp_object.fields + #add the common options + self.__add_commons_to_object(temp_object,argument,argument_name) + #add a new entry to final list + self.__widget_list[argument_name]=temp_object + del temp_object + + + + + def __add_specialized_list(self,argument,argument_name): + """ + Very similar to __add_specialized_hash except it has one field + that is repeated so that provides a dynamic numbers of fields into + the web UI. + + TODO : combine the 2 methods into a one generic they are very similar + @param : argument : the argument options, + @param : argument_name : the name of the argument also the name of the widget + @return : Nothing + """ + list_repeat_data = { + 'template':"funcweb.templates.repeater_form",#may change that if someone doesnt like my design :) + 'fields' : [ + widgets.TextField(name="listfield",label="List Field") + ], + } + + #create the RepeatingFieldSet object and add it to global list like you do for others + temp_object = getattr(widgets,self.__convert_table[argument['type']]['type'])(**list_repeat_data) + #add the commno options + self.__add_commons_to_object(temp_object,argument,argument_name) + #add a new entry to final list + self.__widget_list[argument_name]=temp_object + del temp_object + + def __add_commons_to_object(self,object,argument,argument_name): """ As it was thought all input widgets have the same @@ -125,6 +187,7 @@ class WidgetListFactory(object): """ #firstly set the name of the argument setattr(object,"name",argument_name) + setattr(object,"label",pretty_label(argument_name)) #print "The argument name is :",argument_name #print "The argument options are :",argument @@ -198,7 +261,7 @@ class RemoteFormAutomation(CoreWD): validator = validator_schema, name = "minion_form", update = "col5", - before='getElement(\'loading\').innerHTML=toHTML(IMG({src:\'../static/images/loading.gif\',width:\'80\',height:\'80\'}));', + before='getElement(\'loading\').innerHTML=toHTML(IMG({src:\'../static/images/loading.gif\',width:\'100\',height:\'100\'}));', on_complete='getElement(\'loading\' ).innerHTML=\'Done!\';', ) @@ -212,7 +275,7 @@ class RemoteFormFactory(object): #some values that may want to change later name = "minion_form", update = "col5", - before='getElement(\'loading\').innerHTML=toHTML(IMG({src:\'../static/images/loading.gif\',width:\'80\',height:\'80\'}));', + before='getElement(\'loading\').innerHTML=toHTML(IMG({src:\'../static/images/loading.gif\',width:\'100\',height:\'100\'}));', on_complete='getElement(\'loading\' ).innerHTML=\'Done!\';', submit_text = "Send Minion Form" action = "/post_form" @@ -261,3 +324,22 @@ class RemoteLinkFactory(CoreWD): ) +############################################################################################# +def pretty_label(name_to_label): + """ + Simple util method to show the labels better + without __ things and other ugly looking stuff + """ + tmp = None + split_tokens = ('__','_','-') + for st in split_tokens: + tmp = name_to_label.split(st) + if len(tmp)>1: + break + + if tmp : + name_to_label = " ".join([s.capitalize() for s in tmp]) + else: + name_to_label = name_to_label.capitalize() + + return name_to_label diff --git a/funcweb/funcweb/widget_validation.py b/funcweb/funcweb/widget_validation.py index 0347fa6..9b67e7f 100644 --- a/funcweb/funcweb/widget_validation.py +++ b/funcweb/funcweb/widget_validation.py @@ -266,6 +266,8 @@ class MinionListValidator(validators.FancyValidator): """ #will add more beautiful validation here after #integrate the complex widgets for lists and dicts + #print "Im in the list validator the value i recieved is : ",value + if self.not_empty: if len(value)==0: raise validators.Invalid('Empty list passed when not_empty is set',value,state) @@ -274,9 +276,13 @@ class MinionListValidator(validators.FancyValidator): tmp = [] if type(tmp) != type(value): value = list(value) - value = [list_value.strip() for list_value in value] - - return value + + #concert the data to proper format + final_list = [] + for hash_data in value: + final_list.extend(hash_data.values()) + + return final_list def validate_python(self,value,state): import re @@ -303,17 +309,26 @@ class MinionHashValidator(validators.FancyValidator): """ #will add more beautiful validation here after #integrate the complex widgets for lists and dicts + #print "Im in hash validator the value i recieved is ",value + if self.not_empty: if len(value)==0: raise validators.Invalid('Empty hash passed when not_empty is set',value,state) + + #concert the data to proper format + final_hash = {} + for hash_data in value: + final_hash[hash_data['keyfield']] = hash_data['valuefield'] + + - #check the type firstly + #check the type firstly tmp = {} - if type(tmp) != type(value): - raise validators.Invalid('The value passed to MinionHashValidator should be a dict object',value,state) + if type(tmp) != type(final_hash): + raise validators.Invalid('The value passed to MinionHashValidator should be a dict object',final_hash,state) #print value - return value + return final_hash def validate_python(self,value,state): #print value diff --git a/funcweb/init-scripts/funcwebd b/funcweb/init-scripts/funcwebd new file mode 100755 index 0000000..01ee280 --- /dev/null +++ b/funcweb/init-scripts/funcwebd @@ -0,0 +1,115 @@ +#!/bin/sh +# +# funcwebd Fedora Unified Network Control +################################### + +# LSB header + +### BEGIN INIT INFO +# Provides: funcwebd +# Required-Start: network +# Required-Stop: +# Default-Start: 3 4 5 +# Default-Stop: 0 1 2 6 +# Short-Description: Fedora Unified Network Control +# Description: Crazy simple, secure remote management. +### END INIT INFO + +# chkconfig header + +# chkconfig: - 99 99 +# description: Crazy simple, secure remote management. +# +# processname: /usr/bin/funcwebd + +# Sanity checks. +[ -x /usr/bin/funcwebd ] || exit 0 + +SERVICE=funcwebd +PROCESS=funcwebd +DAEMON=/usr/bin/funcwebd +CONFIG_ARGS="--daemon" + + +FuncStatus() +{ + ps wt? | grep "$DAEMON" 2>&1 > /dev/null + if [ "x$?" = "x0" ]; then + RVAL=0 + echo "$DAEMON is running" + else + RVAL=3 + echo "$DAEMON is not running" + fi +} + +if [ -f /lib/lsb/init-functions ]; then + . /lib/lsb/init-functions + alias START_DAEMON=start_daemon + alias STATUS=FuncStatus + alias LOG_SUCCESS=log_success_msg + alias LOG_FAILURE=log_failure_msg + alias LOG_WARNING=log_warning_msg +elif [ -f /etc/init.d/functions ]; then + . /etc/init.d/functions + alias START_DAEMON=daemon + alias STATUS=status + alias LOG_SUCCESS=success + alias LOG_FAILURE=failure + alias LOG_WARNING=passed +else + echo "Error: your platform is not supported by $0" > /dev/stderr + exit 1 +fi + + +RETVAL=0 + +start() { + echo -n $"Starting funcweb server" + START_DAEMON $PROCESS $CONFIG_ARGS + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE + return $RETVAL +} + +stop() { + echo -n $"Stopping funcweb server: " + killproc $PROCESS + RETVAL=$? + echo + if [ $RETVAL -eq 0 ]; then + rm -f /var/lock/subsys/$SERVICE + rm -f /var/run/$SERVICE.pid + fi +} + +restart() { + stop + start +} + +# See how we were called. +case "$1" in + start|stop|restart) + $1 + ;; + status) + STATUS $PROCESS + RETVAL=$? + ;; + condrestart) + [ -f /var/lock/subsys/$SERVICE ] && restart || : + ;; + reload) + echo "can't reload configuration, you have to restart it" + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|reload}" + exit 1 + ;; +esac +exit $RETVAL + diff --git a/funcweb/setup.py b/funcweb/setup.py index c634983..40c2e00 100644 --- a/funcweb/setup.py +++ b/funcweb/setup.py @@ -14,6 +14,14 @@ if os.path.isdir('locales'): package_data.update(find_package_data(where='locales', exclude=('*.po',), only_in_packages=False)) +#adding to the virtual part of the apache +etcpath = "/etc/httpd/conf.d" +#having a manual part for funcweb may add more things there in the future +self_etcpath = "/etc/funcweb" +#the init path for starting and stoping the server ! +initpath = "/etc/init.d" + +#the setup part setup( name="funcweb", version=version, @@ -22,10 +30,8 @@ setup( author_email=email, url=url, license=license, - install_requires=[ "TurboGears >= 1.0.4.2", - "SQLAlchemy>=0.3.10", ], zip_safe=False, packages=packages, @@ -66,8 +72,8 @@ setup( test_suite='nose.collector', entry_points = { 'console_scripts': [ - 'start-funcweb = funcweb.commands:start', - ], + 'funcwebd = funcweb.commands:start', + ], 'turbogears.identity.provider' : [ 'pam = funcweb.identity.pamprovider:PAMIdentityProvider' @@ -79,5 +85,9 @@ setup( }, # Uncomment next line and create a default.cfg file in your project dir # if you want to package a default configuration in your egg. - #data_files = [('config', ['default.cfg'])], + data_files = [ + (etcpath,['etc/funcweb.conf']), + (self_etcpath,['etc/prod.cfg']), + (initpath,['init-scripts/funcwebd']) + ], ) diff --git a/funcweb/version b/funcweb/version new file mode 100644 index 0000000..084a74c --- /dev/null +++ b/funcweb/version @@ -0,0 +1 @@ +0.1 1 diff --git a/test/unittest/test_client.py b/test/unittest/test_client.py index eae7746..8d65176 100644 --- a/test/unittest/test_client.py +++ b/test/unittest/test_client.py @@ -388,8 +388,40 @@ class TestIptablesPort(BaseTest): # doesnt have an inventory, so er... -akl +class TestEchoTest(BaseTest): + module = "echo" + + def test_run_string(self): + result=self.overlord.echo.run_string("heyman") + self.assert_on_fault(result) + + def test_run_int(self): + result=self.overlord.echo.run_int(12) + self.assert_on_fault(result) + def test_run_float(self): + result=self.overlord.echo.run_float(12.0) + self.assert_on_fault(result) + def test_run_options(self): + result=self.overlord.echo.run_options("hehehh") + self.assert_on_fault(result) + + def test_run_list(self): + result=self.overlord.echo.run_list(['one','two','three']) + self.assert_on_fault(result) + + def test_run_hash(self): + result=self.overlord.echo.run_hash({'one':1,'two':2}) + self.assert_on_fault(result) + + def test_run_boolean(self): + result=self.overlord.echo.run_hash(True) + self.assert_on_fault(result) + + + + class TestSystem(BaseTest): module = "system" |