From fe11dd5f51916ca4ee38219bf23259f0c3a73569 Mon Sep 17 00:00:00 2001 From: Deon George Date: Fri, 14 Jan 2011 01:45:19 +1100 Subject: [PATCH] Initial checking from CVS --- INSTALL | 70 ++ LICENSE | 341 ++++++ VERSION | 1 + config/config.php | 144 +++ config/config.php.example | 144 +++ contrib/http.phpTSMadmin.conf | 12 + docs/CREDITS | 15 + htdocs/cmd.php | 61 + htdocs/common.php | 16 + htdocs/css/style.css | 862 ++++++++++++++ htdocs/devclass.info.php | 319 +++++ htdocs/gantt.activity.php | 116 ++ htdocs/help.php | 70 ++ htdocs/image.backupevents.php | 57 + htdocs/image.dbbackuphistory.php | 83 ++ htdocs/image.occupancy.php | 37 + htdocs/image.schedule.gantt.php | 88 ++ htdocs/image.server.stats.php | 62 + htdocs/images/ajax-spinner.gif | Bin 0 -> 2037 bytes htdocs/images/debug-cache.png | Bin 0 -> 648 bytes htdocs/images/error.png | Bin 0 -> 1891 bytes htdocs/images/favicon.ico | Bin 0 -> 1366 bytes htdocs/images/home-big.png | Bin 0 -> 1084 bytes htdocs/images/index.php | 55 + htdocs/images/info.png | Bin 0 -> 733 bytes htdocs/images/key.png | Bin 0 -> 519 bytes htdocs/images/logo-small.png | Bin 0 -> 23763 bytes htdocs/images/logout.png | Bin 0 -> 829 bytes htdocs/images/minus.png | Bin 0 -> 98 bytes htdocs/images/plus.png | Bin 0 -> 102 bytes htdocs/images/server.png | Bin 0 -> 1424 bytes htdocs/images/timeout.png | Bin 0 -> 608 bytes htdocs/images/trash-big.png | Bin 0 -> 1282 bytes htdocs/images/uid.png | Bin 0 -> 654 bytes htdocs/images/warning.png | Bin 0 -> 2295 bytes htdocs/index.php | 133 +++ htdocs/js/app_ajax.js | 111 ++ htdocs/js/menu_hide.js | 18 + htdocs/library.info.php | 166 +++ htdocs/login.php | 39 + htdocs/login_form.php | 90 ++ htdocs/logout.php | 28 + htdocs/node.detail.php | 297 +++++ htdocs/node.occupancy.php | 92 ++ htdocs/node.summary.php | 126 ++ htdocs/node.thruput.php | 21 + htdocs/purge_cache.php | 35 + htdocs/schedule.gantt.php | 17 + htdocs/server.db.php | 145 +++ htdocs/server.stats.php | 103 ++ htdocs/show_cache.php | 90 ++ htdocs/summary.gantt.php | 189 +++ htdocs/volume.info.php | 160 +++ htdocs/volume.inventory.php | 95 ++ index.php | 11 + lib/common.php | 283 +++++ lib/config_custom.php | 75 ++ lib/config_default.php | 337 ++++++ lib/ds.php | 501 ++++++++ lib/ds_tsm.php | 712 +++++++++++ lib/functions.custom.php | 330 ++++++ lib/functions.php | 1018 ++++++++++++++++ lib/functions.tsm.php | 330 ++++++ lib/menu.php | 49 + lib/menu_html.php | 165 +++ lib/page.php | 504 ++++++++ lib/page_pta.php | 21 + lib/session_functions.php | 185 +++ lib/tsm_classes.php | 1906 ++++++++++++++++++++++++++++++ tools/unserialize.php | 15 + 70 files changed, 10950 insertions(+) create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 VERSION create mode 100644 config/config.php create mode 100644 config/config.php.example create mode 100755 contrib/http.phpTSMadmin.conf create mode 100644 docs/CREDITS create mode 100644 htdocs/cmd.php create mode 100644 htdocs/common.php create mode 100644 htdocs/css/style.css create mode 100644 htdocs/devclass.info.php create mode 100644 htdocs/gantt.activity.php create mode 100644 htdocs/help.php create mode 100644 htdocs/image.backupevents.php create mode 100644 htdocs/image.dbbackuphistory.php create mode 100644 htdocs/image.occupancy.php create mode 100644 htdocs/image.schedule.gantt.php create mode 100644 htdocs/image.server.stats.php create mode 100644 htdocs/images/ajax-spinner.gif create mode 100644 htdocs/images/debug-cache.png create mode 100644 htdocs/images/error.png create mode 100644 htdocs/images/favicon.ico create mode 100644 htdocs/images/home-big.png create mode 100644 htdocs/images/index.php create mode 100644 htdocs/images/info.png create mode 100755 htdocs/images/key.png create mode 100644 htdocs/images/logo-small.png create mode 100644 htdocs/images/logout.png create mode 100644 htdocs/images/minus.png create mode 100644 htdocs/images/plus.png create mode 100644 htdocs/images/server.png create mode 100644 htdocs/images/timeout.png create mode 100644 htdocs/images/trash-big.png create mode 100644 htdocs/images/uid.png create mode 100755 htdocs/images/warning.png create mode 100644 htdocs/index.php create mode 100644 htdocs/js/app_ajax.js create mode 100644 htdocs/js/menu_hide.js create mode 100644 htdocs/library.info.php create mode 100644 htdocs/login.php create mode 100644 htdocs/login_form.php create mode 100644 htdocs/logout.php create mode 100644 htdocs/node.detail.php create mode 100644 htdocs/node.occupancy.php create mode 100644 htdocs/node.summary.php create mode 100644 htdocs/node.thruput.php create mode 100644 htdocs/purge_cache.php create mode 100644 htdocs/schedule.gantt.php create mode 100644 htdocs/server.db.php create mode 100644 htdocs/server.stats.php create mode 100644 htdocs/show_cache.php create mode 100644 htdocs/summary.gantt.php create mode 100644 htdocs/volume.info.php create mode 100644 htdocs/volume.inventory.php create mode 100644 index.php create mode 100644 lib/common.php create mode 100644 lib/config_custom.php create mode 100644 lib/config_default.php create mode 100644 lib/ds.php create mode 100644 lib/ds_tsm.php create mode 100644 lib/functions.custom.php create mode 100644 lib/functions.php create mode 100644 lib/functions.tsm.php create mode 100644 lib/menu.php create mode 100644 lib/menu_html.php create mode 100644 lib/page.php create mode 100644 lib/page_pta.php create mode 100644 lib/session_functions.php create mode 100644 lib/tsm_classes.php create mode 100644 tools/unserialize.php diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..34cefb4 --- /dev/null +++ b/INSTALL @@ -0,0 +1,70 @@ +$Header: /cvsroot/phptsmadmin/phpTSMadmin/INSTALL,v 1.5 2008/01/15 10:50:41 wurley Exp $ + +* Requirements + + phpTSMadmin is a PHP5 application. It wont work on PHP4. If you are using CentOS 4 or RedHat + Enterprise Linux v4, then you can get PHP5 from the centosplus repository. + + The requirements below are those used by RedHat Enterprise Linux v5.0 system. It is quite + possible that other versions of these components, or other distributions of Linux will also + work as well. + + If you encounter any problems with other versions, then drop us an email with + the error messages, what you did and what you expected, and if we need to make some code changes + to get it to work, then we'll take a look. + + * Apache 2.0.x + * PHP 5.1.x + * TSM Client 5.x (our tests were done on a 5.4.1 TSM server). + +* Additional Requirements that are optional. + + We also use a fantastic tool called JpGraph, that takes care of the Gantt charts. It is recommended that + you download and install that tool. You can get JpGraph at: + + http://www.aditus.nu/jpgraph + + You'll need at least v2.x our testing was with 2.3. + + Once downloaded, untar jpgraph in the libs directory, then update your config.php to reflect the path. + +* Installation + + These steps assume that you have installed, configured and running: + * Linux server, + * Apache installed, configured and running, + * PHP installed, configured if necessary and running with apache, + * TSM client installed, configured and working. + * JpGraph (if you choose to use it - which you should). + + 1. Download the latest version. + You can download phpTSMadmin from sourceforge. http://phpTSMadmin.sf.net + + 2. Unpack the tar files in an appropriate directory. + cd /var/www/phpTSMadmin (or any other directory that you choose) + tar xzf phpTSMadmin-XXXX.tgz + + 3. Configure your webserver. + Find the sample apache config file in the contrib directory. Place that in + your /etc/httpd/conf.d directory. + + Edit the sample apache config file as appropriate and restart your web server. + + Also make sure that in your php.ini configuration file, you have set "memory_limit" to + at leat 16M. If this is set too low, then your grantt charts will fail to be created + and your apache error log will have "Allowed memory size of X bytes exhausted" error + messages. + + 4. Copy config.php.example in the config directory to config/config.php and edit to + suite your environment. + Edit the phpTSMadmin's config.php file, making sure you define: + * TSM client configuration + * Jpgraph path + + 5. Make the following directories. + mkdir htdocs/tmp; chmod 777 htdocs/tmp + + 6. Then, point your browser to your phpTSMadmin URL. + +* For help + Drop me an email phptsmadmin-devel@lists.sourceforge.net. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..022e560 --- /dev/null +++ b/LICENSE @@ -0,0 +1,341 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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. + + + Copyright (C) + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. + + , 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 Library General +Public License instead of this License. + diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..921489c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +$Name: $ diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..64eaeb0 --- /dev/null +++ b/config/config.php @@ -0,0 +1,144 @@ +custom variable to do so. + * For example, the default for defining the language in config_default.php + * + * $this->default->appearance['lang'] = array( + * 'desc'=>'Language', + * 'default'=>'auto'); + * + * to override this, use $config->custom->appearance['lang'] = 'en'; + * + * This file is also used to configure your TSM server connections. + * + * You must specify at least one TSM server there. You may add + * as many as you like. You can also specify your language, and + * many other options. + * + * NOTE: Commented out values in this file prefixed by //, represent the + * defaults that have been defined in config_default.php. + * Commented out values prefixed by #, dont reflect their default value, you can + * check config_default.php if you want to see what the default is. + * + * DONT change config_default.php, you changes will be lost by the next release + * of PTA. Instead change this file - as it will NOT be replaced by a new + * version of phpTSMadmin. + */ + +/*********************************************/ +/* Useful important configuration overrides */ +/*********************************************/ + +/* If you are asked to put this application in debug mode, this is how you do it: */ +# $config->custom->debug['level'] = 255; +# $config->custom->debug['syslog'] = true; +# $config->custom->debug['file'] = '/tmp/app_debug.log'; + +/* phpTSMadmin can encrypt the content of sensitive cookies if you set this + to a big random string. */ +// $config->custom->session['blowfish'] = ''; + +/* The language setting. If you set this to 'auto', phpTSMadmin will attempt + to determine your language automatically. Otherwise, available lanaguages + are: 'ct', 'de', 'en', 'es', 'fr', 'it', 'nl', and 'ru' + Localization is not complete yet, but most strings have been translated. + Please help by writing language files. See lang/en.php for an example. */ +// $config->custom->appearance['language'] = 'auto'; + +/* Our local timezone + This is to make sure that when we ask the system for the current time, we + get the right local time. If this is not set, all time() calculations will + assume UTC if you have not set PHP date.timezone. */ +// $config->custom->appearance['timezone'] = null; +# $config->custom->appearance['timezone'] = 'Australia/Melbourne'; + +/* Path to JPGraph + You need to download JPGraph to display the graphical charts. You can get + it from http://www.aditus.nu/jpgraph. */ +// $config->custom->lib['jpgraph'] = LIBDIR.'JpGraph'; + +/* Path to a temp directory that is serviced by the webserver. This directory + is where some JPGraph pictures are created. */ +// $config->custom->image['path'] = HTDOCDIR.'tmp/'; + +/* Browser URL path to the image['path'] directory. */ +// $config->custom->image['pathurl'] = 'tmp/'; + +/*********************************************/ +/* Commands */ +/*********************************************/ + +/* Command availability ; if you don't authorize a command the command + links will not be shown and the command action will not be permitted. + For better security, set also ACL in your ldap directory. */ + +// $config->custom->commands['all'] = array( +// 'home' => true, +// 'login' => true, +// 'logout' => true, +// 'register' => true +// ); + +/*********************************************/ +/* Appearance */ +/*********************************************/ + +/* If you want to choose the appearance of the tree, specify a class name which + inherits from the Tree class. */ +// $config->custom->appearance['menu'] = "menu_html"; + +/*********************************************/ +/* Define your servers in this section */ +/*********************************************/ + +$servers = new Datastore; + +/* A convenient name that will appear in the tree viewer and throughout + phpTSMadmin to identify this LDAP server to users. */ + +/* $servers->NewServer('tsm') must be called before each new TSM server + declaration. It will return a number, which is the instance number. It must + be used in the setValue declaration. */ +$servers->NewServer('tsm'); + +/* A name for your TSM server, it will appear in on the page for the TSM server + being used. If you have multiple TSM servers, it will be the name in the + drop down list. */ +$servers->setValue('server','name','TSM Server'); + +/* The stanza used to connect to this TSM server, when calling dsmadmc. This is + the value for the SErvername attribute in your dsm.opt file. */ +// $servers->setValue('server','stanza',null); + +/* Path to your dsmadmc binary. */ +// $servers->setValue('system','dsmadmc','/opt/tivoli/tsm/client/ba/bin/dsmadmc'); + +/* Filename for the error dsmadmc error log */ +// $servers->setValue('system','errorlog','/tmp/pta-tsm-errorlog.log'); + +/* Age of tapes to consider for alerting (to rotate them) */ +// $servers->setValue('system','tapeage',180); + +/************************************************************************** + * If you want to configure additional TSM servers, do so below. Remove * + * the commented lines and use this section as a template for all your * + * other servers. * + **************************************************************************/ + +/* +$servers->NewServer('tsm'); +$servers->setValue('server','name','Another TSM Server'); +*/ +?> diff --git a/config/config.php.example b/config/config.php.example new file mode 100644 index 0000000..64eaeb0 --- /dev/null +++ b/config/config.php.example @@ -0,0 +1,144 @@ +custom variable to do so. + * For example, the default for defining the language in config_default.php + * + * $this->default->appearance['lang'] = array( + * 'desc'=>'Language', + * 'default'=>'auto'); + * + * to override this, use $config->custom->appearance['lang'] = 'en'; + * + * This file is also used to configure your TSM server connections. + * + * You must specify at least one TSM server there. You may add + * as many as you like. You can also specify your language, and + * many other options. + * + * NOTE: Commented out values in this file prefixed by //, represent the + * defaults that have been defined in config_default.php. + * Commented out values prefixed by #, dont reflect their default value, you can + * check config_default.php if you want to see what the default is. + * + * DONT change config_default.php, you changes will be lost by the next release + * of PTA. Instead change this file - as it will NOT be replaced by a new + * version of phpTSMadmin. + */ + +/*********************************************/ +/* Useful important configuration overrides */ +/*********************************************/ + +/* If you are asked to put this application in debug mode, this is how you do it: */ +# $config->custom->debug['level'] = 255; +# $config->custom->debug['syslog'] = true; +# $config->custom->debug['file'] = '/tmp/app_debug.log'; + +/* phpTSMadmin can encrypt the content of sensitive cookies if you set this + to a big random string. */ +// $config->custom->session['blowfish'] = ''; + +/* The language setting. If you set this to 'auto', phpTSMadmin will attempt + to determine your language automatically. Otherwise, available lanaguages + are: 'ct', 'de', 'en', 'es', 'fr', 'it', 'nl', and 'ru' + Localization is not complete yet, but most strings have been translated. + Please help by writing language files. See lang/en.php for an example. */ +// $config->custom->appearance['language'] = 'auto'; + +/* Our local timezone + This is to make sure that when we ask the system for the current time, we + get the right local time. If this is not set, all time() calculations will + assume UTC if you have not set PHP date.timezone. */ +// $config->custom->appearance['timezone'] = null; +# $config->custom->appearance['timezone'] = 'Australia/Melbourne'; + +/* Path to JPGraph + You need to download JPGraph to display the graphical charts. You can get + it from http://www.aditus.nu/jpgraph. */ +// $config->custom->lib['jpgraph'] = LIBDIR.'JpGraph'; + +/* Path to a temp directory that is serviced by the webserver. This directory + is where some JPGraph pictures are created. */ +// $config->custom->image['path'] = HTDOCDIR.'tmp/'; + +/* Browser URL path to the image['path'] directory. */ +// $config->custom->image['pathurl'] = 'tmp/'; + +/*********************************************/ +/* Commands */ +/*********************************************/ + +/* Command availability ; if you don't authorize a command the command + links will not be shown and the command action will not be permitted. + For better security, set also ACL in your ldap directory. */ + +// $config->custom->commands['all'] = array( +// 'home' => true, +// 'login' => true, +// 'logout' => true, +// 'register' => true +// ); + +/*********************************************/ +/* Appearance */ +/*********************************************/ + +/* If you want to choose the appearance of the tree, specify a class name which + inherits from the Tree class. */ +// $config->custom->appearance['menu'] = "menu_html"; + +/*********************************************/ +/* Define your servers in this section */ +/*********************************************/ + +$servers = new Datastore; + +/* A convenient name that will appear in the tree viewer and throughout + phpTSMadmin to identify this LDAP server to users. */ + +/* $servers->NewServer('tsm') must be called before each new TSM server + declaration. It will return a number, which is the instance number. It must + be used in the setValue declaration. */ +$servers->NewServer('tsm'); + +/* A name for your TSM server, it will appear in on the page for the TSM server + being used. If you have multiple TSM servers, it will be the name in the + drop down list. */ +$servers->setValue('server','name','TSM Server'); + +/* The stanza used to connect to this TSM server, when calling dsmadmc. This is + the value for the SErvername attribute in your dsm.opt file. */ +// $servers->setValue('server','stanza',null); + +/* Path to your dsmadmc binary. */ +// $servers->setValue('system','dsmadmc','/opt/tivoli/tsm/client/ba/bin/dsmadmc'); + +/* Filename for the error dsmadmc error log */ +// $servers->setValue('system','errorlog','/tmp/pta-tsm-errorlog.log'); + +/* Age of tapes to consider for alerting (to rotate them) */ +// $servers->setValue('system','tapeage',180); + +/************************************************************************** + * If you want to configure additional TSM servers, do so below. Remove * + * the commented lines and use this section as a template for all your * + * other servers. * + **************************************************************************/ + +/* +$servers->NewServer('tsm'); +$servers->setValue('server','name','Another TSM Server'); +*/ +?> diff --git a/contrib/http.phpTSMadmin.conf b/contrib/http.phpTSMadmin.conf new file mode 100755 index 0000000..6303f4d --- /dev/null +++ b/contrib/http.phpTSMadmin.conf @@ -0,0 +1,12 @@ +# phpTSMadmin + + AllowOverride AuthConfig Limit + + + + ServerName phptsmadmin.mydomain.net + DocumentRoot /var/www/phpTSMadmin + + ErrorLog /var/log/httpd/phptsmadmin.mydomain.net-error_log + TransferLog /var/log/httpd/phptsmadmin.mydomain.net-access_log + diff --git a/docs/CREDITS b/docs/CREDITS new file mode 100644 index 0000000..2f481fb --- /dev/null +++ b/docs/CREDITS @@ -0,0 +1,15 @@ +$Header: /cvsroot/phptsmadmin/phpTSMadmin/docs/CREDITS,v 1.2 2004/10/03 05:35:28 wurley Exp $ + +---------------------------- +| phpTSMadmin contributors | +============================ + +Developers: + * Deon George (wurley@users.sf.net) + +Other OS software: + * This script uses the template class from phplib - all credit to those developers. + For more information on phplib, check out their website http://phplib.shonline.de + * JPGraph is used to draw the gantt charts, you'll need to get it from http://www.aditus.nu/jpgraph/ + +If you would like to assist, contribute, develop, help then drop me an email. diff --git a/htdocs/cmd.php b/htdocs/cmd.php new file mode 100644 index 0000000..e8d7aa6 --- /dev/null +++ b/htdocs/cmd.php @@ -0,0 +1,61 @@ +getIndex()); + +if ($app['script_cmd']) + include $app['script_cmd']; + +# Capture the output and put into the body of the page. +$www['body'] = new block(); +$www['body']->SetBody(ob_get_contents()); +$www['page']->block_add('body',$www['body']); +ob_end_clean(); + +if ($www['meth'] == 'ajax') + $www['page']->show(get_request('frame','REQUEST',false,'BODY'),true); +else + $www['page']->display(); +?> diff --git a/htdocs/common.php b/htdocs/common.php new file mode 100644 index 0000000..29f3ebb --- /dev/null +++ b/htdocs/common.php @@ -0,0 +1,16 @@ + diff --git a/htdocs/css/style.css b/htdocs/css/style.css new file mode 100644 index 0000000..4804d40 --- /dev/null +++ b/htdocs/css/style.css @@ -0,0 +1,862 @@ +/* $Header: /cvsroot/phptsmadmin/phpTSMadmin/htdocs/css/style.css,v 1.5 2009/04/19 04:00:59 wurley Exp $ */ + +/* Global Page */ +table.page { + font-weight: normal; + color: #000000; + + font-family: bitstream vera sans,luxi sans,verdana,geneva,arial,helvetica,sans-serif; + background-color: #FFFFFF; + font-size: 13px; + empty-cells: hide; +} + +/* Global Page - Defaults */ +/* A HREF Links */ +table.page a { + color: #0000AA; + text-decoration: none; +} + +table.page a:hover { + text-decoration: none; +} + +table.page a img { + border: 0px; +} + +/* Global Page - Logo & Title */ +table.page tr.head { + text-align: center; + color: #FFFFFF; + background-color: #001188; + font-weight: bold; + font-size: 11px; + height: 25px; +} + +table.page tr.head img.logo { + vertical-align: middle; + text-align: center; + + width: 100px; + height: 60px; +} + +table.page tr.pagehead td.imagetop { + width: 100%; + vertical-align: bottom; +} + +/* Global Page - Control Line */ +table.page tr.control td { + border-top: 1px solid #AAAACC; + border-bottom: 1px solid #AAAACC; +} + +/* Global Page - Control Line Menu Items */ +table.page table.control { + table-layout: fixed; + width: 100%; +} + +table.page table.control td { + border-top: 0px; + border-bottom: 0px; + width: 30px; + padding: 0px; + padding-top: 2px; + padding-bottom: 2px; + text-align: left; + vertical-align: top; + font-size: 11px; + font-weight: bold; +} + +table.page table.control img { + width: 24px; + height: 24px; +} + +table.page table.control a { + color: #000000; +} + +table.page table.control a:hover { + text-decoration: none; + background-color: #FFFFFF; + color: #0000AA; +} + +table.page table.control td.spacer { + width: 20%; +} + +table.page table.control td.logo { + text-align: right; + width: 10%; +} + +table.page table.control td.logo img.logo { + vertical-align: middle; + text-align: right; + + width: 100px; + height: 50px; +} + +/* Global Page - Menu */ +table.page td.menu { + border-right: 1px solid #AAAACC; + vertical-align: top; + background-color: #FCFCFE; + width: 10%; +} + +/* Global Page - Main Body */ +table.page td.body { + vertical-align: top; + width: 100%; + background-color: #FCFCFE; +} + +/* Global Page - Main Body System Message */ +table.page table.sysmsg { + border-bottom: 2px solid #AAAACC; + width: 100%; +} + +table.page table.sysmsg td.head { + font-size: small; + text-align: left; + font-weight: bold; +} + +table.page table.sysmsg td.body { + font-weight: normal; +} + +table.page table.sysmsg td.icon { + text-align: center; + vertical-align: top; +} + +/* Global Page - Main Body */ +table.page table.body { + font-weight: normal; + background-color: #FCFCFE; + width: 100%; +} + +table.page table.body h3.title { + text-align: center; + margin: 0px; + padding: 10px; + color: #FFFFFF; + background-color: #000088; + border: 1px solid #000000; + font-weight: normal; + font-size: 150%; +} + +table.page table.body h3.subtitle { + text-align: center; + margin: 0px; + margin-bottom: 15px; + font-size: 75%; + color: #FFFFFF; + border-bottom: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + background: #000088; + padding: 4px; + font-weight: normal; +} + +table.page table.body td.spacer { + border-top: 2px solid #AAAACC; + padding: 0px; + font-size: 5px; +} + +table.page table.body td.head { + font-weight: bold; +} + +table.page table.body td.foot { + font-size: small; + border-top: 1px solid #AAAACC; + border-bottom: 1px solid #AAAACC; +} + +/* Global Page Footer */ +table.page tr.foot td { + border-top: 1px solid #AAAACC; + font-weight: bold; + font-size: 12px; + text-align: right; +} + +/* Global Page - Other Layouts */ +/* Server Select */ +table.page table.server_select { + font-weight: bold; + font-size: 13px; + color: #000000; +} + +/* Individual table layouts */ +/* Menu */ +table.menu { +} + +table.menu tr.server td.icon { + vertical-align: top; +} + +table.menu tr.server td.name { + padding-right: 10px; + vertical-align: top; +} + +table.menu tr.server td { + padding-top: 5px; + font-size: 18px; + text-align: left; + padding-right: 0px; + white-space: nowrap; +} + +table.menu tr.links td { + white-space: nowrap; +} + +table.menu tr.links td td.ds_option { + vertical-align: top; + text-align: center; + padding-top: 0px; + padding-bottom: 0px; + padding-left: 3px; + padding-right: 3px; +} + +table.menu tr.links td td.ds_option img { + height: 22px; + width: 22px; +} + +table.menu tr.links a { + color: #000000; + text-decoration: none; + font-size: 11px; +} + +table.menu tr.links a:hover { + text-decoration: none; + background-color: #FFFFFF; + color: #000000; +} + +table.menu tr.option td.expander { + text-align: center; + width: 22px; + max-width: 22px; + min-width: 22px; + white-space: nowrap; +} + +table.menu tr.option td.icon { + text-align: center; + width: 22px; + max-width: 22px; + min-width: 22px; + white-space: nowrap; +} + +table.menu tr.option td.item a { + font-size: 13px; + color: #000000; +} + +table.menu tr.option td.item a:hover { + font-size: 13px; + color: #841212; + background-color: #FFF0C0; + text-decoration: none; +} + +table.menu tr.option td.item span.count { + font-size: 13px; + color: #000000; +} + +table.menu td.links a { + color: #0000AA; + text-align: center; +} + +table.menu td.create a { + font-size: 13px; + color: #000000; +} + +table.menu td.create a:hover { + font-size: 13px; + color: #841212; + background-color: #FFF0C0; + text-decoration: none; +} + +table.menu td.links a:hover { + text-decoration: none; + color: blue; +} + +table.menu td.links a img { + width: 22px; + height: 22px; +} + +table.menu td.blank { + font-size: 1px; +} + +table.menu td.spacer { + width: 22px; +} + +table.menu td.logged_in { + font-size: 10px; + white-space: nowrap; +} + +table.menu td.logged_in a { + font-size: 11px; +} + +table.menu td.logged_in a:hover { + color: #841212; + background-color: #FFF0C0; + text-decoration: none; +} + +/* Tree Global Defaults */ +table.menu tr td { + padding: 0px; +} + +table.menu a { + text-decoration: none; + color: #000000; +} + +table.menu a:hover { + text-decoration: underline; + color: blue; +} + +/* Standard Form */ +table.forminput { + background-color: #F9F9FA; + padding: 10px; + border: 1px solid #AAAACC; +} + +table.forminput td.title { + text-align: center; + font-weight: bold; +} + +table.forminput td.subtitle { + text-align: center; + font-weight: normal; + font-size: small; +} + +table.forminput tr td.heading { + font-weight: bold; +} + +table.forminput td.small { + font-size: 80%; +} + +table.forminput td.top { + vertical-align: top; +} + +table.forminput input.val { + width: 350px; + border: 1px solid #AAAACC; +} + +table.forminput input.roval { + width: 350px; + border: none; +} + +table.forminput td.icon { + width: 16px; + text-align: center; +} + +table.forminput td.icon img { + border: 0px; +} + +table.forminput td.label { + text-align: left; + font-size: 13px; +} + +/* Menu on top of entry form */ +table.menu { + font-size: 14px; +} + +table.menu td.icon { + width: 16px; + text-align: center; +} + +/* Edit */ +div.add_value { + font-size: 12px; + margin: 0px; + padding: 0px; +} + +/* Edit Entry */ +table.entry { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; +} + +table.entry input { + margin: 1px; +} + +table.entry input.value { + color: #000000; + font-size: 14px; + width: 350px; + background-color: #FFFFFF; +} + +table.entry div.helper { + text-align: left; + white-space: nowrap; + background-color: #FFFFFF; + color: #888; + font-size: 14px; + font-weight: normal; +} + +table.entry input.roval { + font-size: 14px; + width: 350px; + background-color: #FFFFFF; + color: #000000; + border: none; +} + +table.entry textarea.value { + font-size: 14px; + width: 350px; + background-color: #FFFFFF; + color: #000000; +} + +table.entry textarea.roval { + font-size: 14px; + width: 350px; + background-color: #FFFFFF; + color: #000000; + border: none; +} + +table.entry tr td { + padding: 4px; + padding-right: 0px; +} + +table.entry tr td.heading { + border-top: 3px solid #C0C0C0; + font-weight: bold; +} + +table.entry tr td.note { + text-align: right; + background-color: #E0E0E0; +} + +table.entry tr td.title { + background-color: #E0E0E0; + vertical-align: top; + font-weight: bold; +} + +table.entry tr td.title a { + text-decoration: none; + color: #000000; +} + +table.entry tr td.title a:hover { + text-decoration: underline; + color: #016; +} + +table.entry tr td.value { + text-align: left; + vertical-align: middle; + padding-bottom: 10px; + padding-left: 50px; +} + +/** When an attr is updated, it is highlighted to indicate such */ +table.entry tr.updated td.title { + border-top: 1px dashed #AAAA88; + border-left: 1px dashed #AAAA88; + background-color: #999988; +} + +table.entry tr.updated td.note { + border-top: 1px dashed #AAAA88; + border-right: 1px dashed #AAAA88; + background-color: #999988; +} + +/** An extra row that sits at the bottom of recently modified attrs to encase them in dashes */ +table.entry tr.updated td.bottom { + border-top: 1px dashed #AAAA88; +} + +/** Formatting for the value cell when it is the attribute that has been recently modified */ +table.entry tr.updated td.value { + border-left: 1px dashed #AAAA88; + border-right: 1px dashed #AAAA88; +} + +/* Need to prevent sub-tables (like the one in which jpegPhotos are displayed) + * from drawing borders as well. */ +table.entry tr.updated td table td { + border: 0px; +} + +table.entry tr.noinput { + background: #E0E0E0; +} + +span.hint { + font-size: small; + font-weight: normal; + color: #888; +} + +/* Login Box */ +#login { + background: url('../images/uid.png') no-repeat 0 1px; + background-color: #FAFAFF; + color: #000000; + padding-left: 17px; +} + +#login:focus { + background-color: #F0F0FF; + color: #000000; +} + +#login:disabled { + background-color: #DDDDFF; + color: #000000; +} + +#password { + background: url('../images/key.png') no-repeat 0 1px; + background-color: #FAFAFF; + color: #000000; + padding-left: 17px; +} + +#password:focus { + background-color: #F0F0FF; + color: #000000; +} + +#password:disabled { + background-color: #DDDDFF; + color: #000000; +} + +#generic { + background-color: #FAFAFF; + color: #000000; + padding-left: 17px; +} +#generic:focus { + background-color: #F0F0FF; + color: #000000; +} + +#generic:disabled { + background-color: #DDDDFF; + color: #000000; +} + +/* After input results */ +div.execution_time { + font-size: 75%; + font-weight: normal; + text-align: left; +} + +table.result { + width: 100%; + vertical-align: top; + empty-cells: show; + border: 1px solid #AAAACC; + border-spacing: 0px; + background-color: #F2F2FF; +} + +table.result td.titlel { + text-align: left; + font-weight: bold; +} + +table.result td.titlec { + text-align: center; + font-weight: bold; +} + +table.result td.titler { + text-align: right; + font-weight: bold; +} + +table.result tr.heading { + vertical-align: top; +} + +table.result tr.list_title { + background-color: #FFFFFF; +} + +table.result tr.list_title td.icon { + text-align: center; + vertical-align: top; +} + +table.result tr.list_item { + background-color: #FFFFFF; +} + +table.result tr.list_item td.blank { + width: 25px; +} + +table.result tr.list_item td.heading { + vertical-align: top; + color: gray; + width: 10%; + font-size: 12px; +} + +table.result tr.list_item td.value { + color: #000000; + font-size: 12px; +} + +table.result_table { + border: 1px solid #AAAACC; + border-collapse: collapse; + empty-cells: show; +} + +table.result_table td { + font-size: 12px; + vertical-align: top; + border: 1px solid #AAAACC; + padding: 4px; +} + +table.result_table th { + border: 1px solid #AAAACC; + padding: 10px; + padding-left: 20px; + padding-right: 20px; +} + +table.result_table tr.highlight { + background-color: #EEEBBB; +} + +table.result_table tr.highlight td { + border: 1px solid #AAAACC; + font-weight: bold; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; +} + +table.result_table td.heading { + color: #FFFFFF; + background-color: #000088; + font-size: 15px; +} + +table.result_table td.value { + color: #000000; + background-color: #E0E0E0; +} + +table.result_table tr.heading { + color: #FFFFFF; + background-color: #000088; + font-size: 15px; + font-weight: bold; +} + +table.result_table tr.heading a { + color: #FFFFFF; + font-size: 15px; + font-weight: bold; +} + +table.result_table tr.heading td { + border: 1px solid #AAAACC; + font-weight: normal; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; +} + +table.result_table td.titlel { + text-align: left; + font-weight: bold; +} + +table.result_table td.titlec { + text-align: center; + font-weight: bold; +} + +table.result_table td.titler { + text-align: right; + font-weight: bold; +} + +table.result_table tr.even { + background-color: #E0E0E0; +} + +table.result_table tr.even td { + border: 1px solid #AAAACC; + font-weight: normal; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; +} + +table.result_table tr.even td.title { + font-weight: bold; +} + +table.result_table tr.odd { + background-color: #F0F0F0; +} + +table.result_table tr.odd td { + border: 1px solid #AAAACC; + font-weight: normal; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; +} + +table.result_table tr.odd td.title { + font-weight: bold; +} + +table.result_table ul.list { + margin: 5px; + margin-left: 0px; + padding-left: 20px; +} + +table.result_table ul.list li { + margin-left: 0px; + padding-left: 0px; +} + +table.result_table ul.list li small { + font-size: 75%; + color: #707070; +} + +table.result_table ul.list li small a { + color: #7070C0; +} + +/* Error Dialog Box */ +table.error { + width: 500px; + border: 1px solid #AA0000; + background-color: #FFF0F0; +} + +table.error th { + background-color: #AA0000; + border: 0px; + color: #FFFFFF; + font-size: 14px; + font-weight: bold; + text-align: center; + vertical-align: middle; + width: 100%; +} + +table.error th.img { + vertical-align: middle; + text-align: center; +} + +table.error td { + border: 0px; + background-color: #FFF0F0; + padding: 2px; + text-align: left; + vertical-align: top; +} + +/* Popup Window */ +div.popup h3.subtitle { + text-align: center; + margin: 0px; + margin-bottom: 15px; + color: #FFFFFF; + border-bottom: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + background: #000088; + padding: 4px; + font-weight: normal; +} + +span.good { + color: green; +} + +span.bad { + color: red; +} + +span.value { + font-weight: bold; +} diff --git a/htdocs/devclass.info.php b/htdocs/devclass.info.php new file mode 100644 index 0000000..603a8ee --- /dev/null +++ b/htdocs/devclass.info.php @@ -0,0 +1,319 @@ +'; +printf('',get_request('cmd','REQUEST')); +printf('',$app['server']->getIndex()); + +echo ''; +echo ''; + +if (isset($_REQUEST['devclass']) && trim($_REQUEST['devclass'])) + foreach ($devclasses->getDevClasses() as $devc => $devclass) { + if ($_REQUEST['devclass'] != $devclass->getName()) + continue; + + echo ''; + printf(_(''),$devc); + + printf('', + $devclass->access, + $devclass->type); + + if (trim($devclass->library)) { + printf('', + $devclass->library); + + printf('', + count($drives->getDrives($devclass->library))); + } + + if (trim($devclass->dir)) + printf('', + $devclass->dir,$devclass->capacity); + + echo '
Device Class%s 
Type'. + classValue(_('%s %s media is used for this device class.'),'value'). + ' 
Library'. + classValue(_('%s library is used to store this media.'),'value'). + ' 
 '. + classValue(_('%s drives defined in this library.'),'value'). + ' 
Local Directory'. + classValue(_('%s directory will contain %s volumes for this device class.'),'value'). + ' 
'; + echo '
'; + printf(_('Storage Pools using device class %s.').'
',$devc); + + $stgcount = 0; + foreach ($stgps->getStoragePools() as $stgp => $stgpool) { + # Ignore storage pools not of this device class. + if ($stgpool->devclass != $devc) continue; + + echo ''; + # Title + echo "\n\n"; + printf('', + $stgpool->getName(),$stgpool->type,$stgpool->access); + + # Utlisation and Migration details + echo "\n\n"; + switch ($stgpool->type) { + case 'PRIMARY' : + switch ($devclass->access) { + case 'Sequential' : + printf('', + $stgpool->utilisation,$stgpool->migratable); + break; + + case 'Random' : + printf('', + $stgpool->utilisation,$stgpool->migratable); + break; + + default : + printf('', + $stgpool->utilisation); + break; + + default : + printf('', + $stgpool->maxsize/1024/1024); + + if ($stgpool->reclaim) + printf('', + $stgpool->reclaim,($stgpool->reclaimstgp ? $stgpool->reclaimstgp : $stgp)); + + if ($stgpool->reclaimrunning) + printf(''); + + if ($stgpool->reuse) + printf('', + $stgpool->reuse); + + # Migration details + if ($stgpool->nextstgp) + printf('', + $stgpool->nextstgp,$stgpool->migr['hi'],$stgpool->migr['low']); + + if ($stgpool->migr['running']) + printf('', + $stgpool->migr['mb'],$stgpool->migr['sec']); + + # Collocation Information + if ($stgpool->collocate) + printf('', + $stgpool->collocate); + + # Disk Caching. + if ($stgpool->cache) + printf('', + $stgpool->cache); + + $stgcount++; + echo ''; + + # Volume information. + echo "\n\n"; + switch ($devclass->type) { + case 'DISK' : + if ($volumes->getStoragePoolVolumes($stgpool->getName())) + foreach ($volumes->getStoragePoolVolumes($stgpool->getName()) as $volume) { + + printf('', + $volume->getName(),$volume->utilisation, + $volume->status['volume'], + $volume->access); + } + break; + + default : + + # Scratch Media + $temp = ''; + if (count($volumes->getStoragePoolScratchVolumes($stgpool->getName()))) + $temp .= sprintf('', + count($volumes->getStoragePoolScratchVolumes($stgpool->getName())),$stgpool->maxscratch); + + if (count($volumes->getStoragePoolDefinedVolumes($stgpool->getName()))) + $temp .= sprintf('', + count($volumes->getStoragePoolDefinedVolumes($stgpool->getName()))); + + if ($temp) { + printf('', + _('Media SCRATCH Status')); + echo $temp; + echo ''; + $temp = ''; + } + + # Media Access + foreach (array('FULL','FILLING','PENDING','EMPTY') as $status) { + if (count($volumes->getStoragePoolVolumesStatus($stgpool->getName(),$status))) + $temp .= sprintf('', + count($volumes->getStoragePoolVolumesStatus($stgpool->getName(),$status)),$status); + } + if ($temp) { + printf('', + _('Media FILLING Status')); + echo $temp; + echo ''; + $temp = ''; + } + + foreach (array('READWRITE','READONLY','UNAVAILABLE','OFFSITE','DESTROYED') as $status) { + if (count($volumes->getStoragePoolVolumesAccess($stgpool->getName(),$status))) + $temp .= sprintf('', + count($volumes->getStoragePoolVolumesAccess($stgpool->getName(),$status)),$status); + } + if ($temp) { + printf('', + _('Media ACCESS Status')); + echo $temp; + echo ''; + $temp = ''; + } + + # Utilisation + $stat['count'] = 0; + $stat['util'] = 0; + $stat['reclaim'] = 0; + $stat['cap'] = 0; + foreach ($volumes->getStoragePoolVolumes($stgpool->getName()) as $volume) { + $stat['util'] += $volume->utilisation; + $stat['reclaim'] += $volume->reclaim; + $stat['cap'] += $volume->estcap; + $stat['count']++; + } + if ($stat['count'] > 0) { + printf('', + _('Media Stats')); + printf('', + $stat['util']/$stat['count']); + printf('', + $stat['reclaim']/$stat['count']); + printf('', + $stat['cap']/$stat['count']); + } + break; + + } + echo ''; + + # Nodes with data in this pool + echo "\n\n"; + $total = 0; + $nodedata = ''; + $counter = 0; + foreach ($occupancy->getStoragePoolTotals($stgpool->getName()) as $node => $typedetails) { + $node = $nodes->getNode($node); + + foreach ($typedetails as $type => $details) { + $nodedata .= sprintf('', + ($counter++%2==0?'even':'odd'), + $node->getName(),$type, + (isset($detail['files']) ? number_format($detail['files']) : ' '), + (count($node->getTapeVolumes($stgpool->getName(),$type)) ? count($node->getTapeVolumes($stgpool->getName(),$type)) : ' '), + isset($details['physical']) ? number_format($details['physical']) : ' '); + + $total += $details['physical']; + } + } + + if ($nodedata) { + echo ''; + echo ''; + } + echo '
%s - %s (%s) 
 '. + classValue(_('%s%% storage capacity utilised with %s%% volumes used.'), + 'value'). + ' 
 '. + classValue(_('%s%% storage capacity utilised with %s%% migratable data.'), + 'value'). + ' 
Unknown access strategy %s.',$devclass->access); + } + break; + + case 'COPY' : + printf('
 '. + classValue(_('%s%% storage capacity utilised.'),'value'). + ' 
%s %s.', + _('Unknown pooltype'),$stgpool->type); + } + + # Max object size + echo "\n\n"; + if ($stgpool->maxsize) + printf('
 '. + classValue(_('%3.2fMB Maximum Object Size.'),'value'). + ' 
 '. + classValue(_('%s%% wasted space is required before reclaimation. Reclamation will use %s'), + 'value'). + ' 
 '. + _('Reclamation IS currently running.'). + ' 
 '. + classValue(_('%s days must pass before volumes will be reused.'),'value'). + ' 
 '. + classValue(_('Migration to %s will occur at %s%% (stopping at %s%%)'),'value'). + ' 
 '. + classValue(_('Migration IS currently running. %sMB Migrated %ss'),'value'). + ' 
 '. + classValue(_('%s Collocation.'),'value'). + ' 
 '. + classValue(_('%s Migration caching.'),'value'). + ' 
 
 '. + classValue(_('Vol %s (%s%% %s) - %s.'),'value'). + ' 
 '. + classValue(_('%s scratch media in use (of %s).'),'value'). + ' 
 '. + classValue(_('%s defined media in use.'),'value'). + ' 
%s
 
 '. + classValue(_('%s media %s.'),'value'). + ' 
%s
 
 '. + classValue(_('%s media %s.'),'value'). + ' 
%s
 
%s
 '. + classValue(_('%3.2f%% average utilisation.'),'value'). + ' 
 '. + classValue(_('%3.2f%% average wasted space.'),'value'). + ' 
 '. + classValue(_('%3.0f MB average capacity.'),'value'). + ' 
 
%s%s%s %s%s MB
'."\n\n"; + echo ''; + printf('', + _('Nodes with data in this storage pool.')); + printf('', + _('Node'),_('Type'),_('Volumes'),_('MB')); + echo $nodedata; + printf('', + _('Total'),number_format($total)); + echo '
%s
%s%s%s%s
%s%s MB
'."\n\n"; + echo '
 
 

'."\n\n"; + } + + if (! $stgcount) + printf('%s
',_('There are no storage pools using this DEVCLASS.')); + + } +?> diff --git a/htdocs/gantt.activity.php b/htdocs/gantt.activity.php new file mode 100644 index 0000000..080f01f --- /dev/null +++ b/htdocs/gantt.activity.php @@ -0,0 +1,116 @@ +'Expire Inventory'); +$startActivities['812'] = array('success'=>'Expire Inventory'); +$startActivities['2562'] = array('start'=>'Event Records Delete'); +$startActivities['2564'] = array('success'=>'Event Records Delete'); +$startActivities['2102'] = array('start'=>'Activity Log Delete'); +$startActivities['2102'] = array('success'=>'Activity Log Delete'); +$startActivities['2280'] = array('start'=>'DB Backup'); +$startActivities['4550'] = array('success'=>'DB Backup'); +$startActivities['406'] = array('start'=>'Session'); +$startActivities['403'] = array('success'=>'Session'); + +# Admin schedules +$tsmActivitys = TSMQuery($_REQUEST['server_id'],"select date(DATE_TIME) as DATE,time(DATE_TIME) as TIME,MSGNO from ACTLOG where ORIGINATOR='SERVER' and DATE_TIME \> '$reportStart' and MSGNO in (".implode(",",array_keys($startActivities)).")"); + +foreach ($tsmActivitys as $tsmActivity) { + + $datetime = $tsmActivity['DATE']." ".(preg_replace('/(.*:.*):.*/',"$1",$tsmActivity['TIME'])); + $msgNum = $tsmActivity['MSGNO']; + # Do we know this message number? + if (isset($startActivities[$msgNum])) { + + # New Event. + if (isset($startActivities[$msgNum]['start'])) { + + $activity[$startActivities[$msgNum]['start']][]['start'] = $datetime; + + } else if (isset($startActivities[$msgNum]['success'])) { + $item = (isset($activity[$startActivities[$msgNum]['success']]) ? + count($activity[$startActivities[$msgNum]['success']])-1 : 0); + + # Is there a start event. + if (! isset($activity[$startActivities[$msgNum]['success']][$item]['start'])) { + $activity[$startActivities[$msgNum]['success']][$item]['start'] = $datetime; + } + $activity[$startActivities[$msgNum]['success']][$item]['end'] = $datetime; + + } else { + print "Che? Unknown index??"; + } + } else { + } + +} +// A new graph with automatic size +$graph = new GanttGraph(0,0,"auto"); + +$item = 0; + +# Admin Schedules +foreach ($activity as $tsmSchedAdmin => $value) { + + foreach ($value as $instance) { + +printf ("Drawing [%s] [%s] [%s] [%s]
",$item,$tsmSchedAdmin,$instance['start'],($instance['end'] ? $instance['end'] : $instance['start'])); + $activity = new GanttBar($item,$tsmSchedAdmin,$instance['start'],($instance['end'] ? $instance['end'] : $instance['start'])); + $item++; +$graph->Add($activity); + } +} + +$graph->SetMarginColor('white'); +$graph->SetFrame(false); + +// We want to display day, hour and minute scales +$graph->ShowHeaders(GANTT_HDAY | GANTT_HHOUR | GANTT_HMIN); + +$graph->hgrid->Show(); +$graph->hgrid->SetRowFillColor('darkred@0.85'); +$graph->hgrid->line->SetColor('red@0.85'); +$graph->hgrid->line->Show(false); + +// A new activity on row '0' +// Setup hour format +$graph->scale->hour->SetIntervall(1); +$graph->scale->hour->SetStyle(HOURSTYLE_H24); +$graph->scale->minute->SetIntervall(30); + +// Display the Gantt chart +$graph->Stroke(); + +die(); +# Block Title +$blockTitle = _('Server Status for').' '.TSMServerName($_REQUEST['server_id']); + +$blockBody = ''; + +# Client Details +$blockBody .= ''; + +#$blockBody .= sprintf('', +# count($tsmClientsTotal)); + +$blockBody .= ''; + +# End +$blockBody .= '
 Clients 
 '. +# classValue(_('%s clients registered to this TSM server.'),'value'). +# ' 
 
'; + +$left = ''; # Use default left blocks. +$centre = buildBlock("centre",$blockTitle,$blockBody); +$right = ''; # Use default right blocks. +print buildPage($left,$centre,$right); + +?> diff --git a/htdocs/help.php b/htdocs/help.php new file mode 100644 index 0000000..412f571 --- /dev/null +++ b/htdocs/help.php @@ -0,0 +1,70 @@ +getValue('server','name')); + +$blockBody['help'] = '
'; +$blockBody['help'] .= sprintf('',get_request('cmd','REQUEST')); +$blockBody['help'] .= ''; + +$blockBody['help'] .= ''; +$blockBody['help'] .= ''; +$blockBody['help'] .= ''; +$blockBody['help'] .= ''; +$blockBody['help'] .= ''; +$blockBody['help'] .= '
'; +$blockBody['help'] .= sprintf('',$app['server']->getIndex()); +$blockBody['help'] .= ''; +$blockBody['help'] .= ' 
'; +$blockBody['help'] .= '
'; + +if (isset($form['command'])) { + $helpCommand = $help->getCommand($form['command']); + + if (get_request('raw','REQUEST')) { + $blockBody['help'] .= '
'.implode('
',$helpCommand->raw).'
'; + + } else { + $blockBody['help'] .= '
'; + $blockBody['help'] .= ''; + + $blockBody['help'] .= sprintf('',$helpCommand->getTitle()); + $blockBody['help'] .= sprintf('',$helpCommand->getDescription()); + + $blockBody['help'] .= sprintf('',_('Privileges Required')); + $blockBody['help'] .= sprintf('',$helpCommand->getClass()); + + $blockBody['help'] .= sprintf('',_('Syntax')); + $blockBody['help'] .= sprintf('',$helpCommand->getSyntax()); + + $blockBody['help'] .= sprintf('',_('Parameters')); + $blockBody['help'] .= sprintf('',$helpCommand->getParameters()); + + $blockBody['help'] .= ''; + $blockBody['help'] .= sprintf('',_('Related Commands')); + $blockBody['help'] .= sprintf('',$helpCommand->getRelated()); + + $blockBody['help'] .= '

%s

%s
%s
%s
%s
%s
%s
%s
 
%s
%s
'; + } +} + +# End +render_page($blockTitle,$blockBody); +?> diff --git a/htdocs/image.backupevents.php b/htdocs/image.backupevents.php new file mode 100644 index 0000000..a214b09 --- /dev/null +++ b/htdocs/image.backupevents.php @@ -0,0 +1,57 @@ + $details) { + foreach ($_SESSION['graph']['backupevent']['legend'] as $key => $count) { + @$_SESSION['graph']['backupevent']['data'][$sched][$key] += 0; + } + ksort($_SESSION['graph']['backupevent']['data'][$sched]); +} + +$graph_row = 2; +$xcounter = 1; +$ycounter = 1; +$numrows = round(count($_SESSION['graph']['backupevent']['data'])/$graph_row); + +$graph = new PieGraph(500,30+180*$numrows,'auto'); + +foreach ($_SESSION['graph']['backupevent']['data'] as $sched => $details) { + ksort($details); + + $newsched = preg_replace('/-/','_',$sched); + $$newsched = new PiePlot(array_values($details)); + $$newsched->SetCenter(120+(($xcounter%$graph_row)*160),120+(($ycounter-1)*180)); + $$newsched->SetGuideLines(false,false); + $$newsched->SetGuideLinesAdjust(1.0); + $$newsched->SetSize(60); + $$newsched->SetTheme('sand'); + $$newsched->value->SetFormat('%2.1f%%'); + $$newsched->title->Set($sched); + $$newsched->title->SetMargin(10); + + if ($xcounter == 1) + $$newsched->SetLegends(array_keys($details)); + + $graph->Add($$newsched); + if (! ($xcounter%$graph_row)) + $ycounter++; + + $xcounter++; +} + +$graph->legend->SetFillColor('azure'); +$graph->SetMarginColor('azure2'); +$graph->title->Set(sprintf('Schedule Backup Status for %s',$app['server']->GetStatusDetail('SERVER_NAME'))); + +# Display the chart +unset($_SESSION['graph']['backupevent']); +$graph->Stroke(); +?> diff --git a/htdocs/image.dbbackuphistory.php b/htdocs/image.dbbackuphistory.php new file mode 100644 index 0000000..3f8008e --- /dev/null +++ b/htdocs/image.dbbackuphistory.php @@ -0,0 +1,83 @@ +GetDBBackupDetail('history'); +if (! $history) + exit; + +$graph = new Graph(400,200,'auto'); +$graph->SetScale('textlin'); + +$fulldata = array(); +$incrdata = array(); +$xtitles = array(); + +foreach ($app['server']->GetDBBackupDetail('history') as $index => $backupdetails) { + switch ($backupdetails['type']) { + case 'FULL' : + $fulldata[] = $backupdetails['size']; + $incrdata[] = 0; + break; + + case 'INCR' : + $fulldata[] = 0; + $incrdata[] = $backupdetails['size']; + break; + } + $xtitles[] = preg_replace('/ /',"\n",tsmDate($backupdetails['date'],'daytime')); + $maxdata[] = $app['server']->GetDBDetail('USABLE_PAGES'); + $curdata[] = $app['server']->GetDBDetail('USED_PAGES'); +} + +$cplot = new LinePlot($maxdata); +$cplot->SetLegend('Max DB Size'); +$cplot->SetColor('red'); +$cplot->SetWeight(2); +if (count($maxdata) == 1) + $cplot->mark->SetType(MARK_IMG_MBALL,'red',0.5); +$graph->Add($cplot); + +$dplot = new LinePlot($curdata); +$dplot->SetLegend('Cur Size'); +$dplot->SetColor('lightred'); +$dplot->SetWeight(2); +$graph->Add($dplot); + +$aplot = new BarPlot($fulldata); +$aplot->SetLegend('Full'); +$aplot->SetFillColor('blue'); +$aplot->value->Show(); +$aplot->value->SetFormat('%5.0f'); +$graph->Add($aplot); + +$bplot = new BarPlot($incrdata); +$bplot->SetLegend('Incremental'); +$bplot->SetFillColor('lightblue'); +$bplot->value->Show(); +$bplot->value->SetFormat('%5.0f'); +$graph->Add($bplot); + +$graph->img->SetMargin(60,110,20,40); +$graph->legend->Pos(0.01,0.80,'right','center'); +$graph->legend->SetFillColor('azure'); +$graph->xaxis->SetTickLabels($xtitles); +$graph->xaxis->SetLabelAngle(90); +$graph->xgrid->Show(true); +$graph->yaxis->title->Set('Database Pages \'000s'); +$graph->yaxis->SetLabelFormatCallback('labelformat'); +$graph->yaxis->SetTitleMargin(45); +$graph->SetMarginColor('azure2'); +$graph->title->Set(sprintf('Database Backups %s',$app['server']->GetStatusDetail('SERVER_NAME'))); + +# Display the Gantt chart +$graph->Stroke(); + +function labelformat($label) { + return number_format($label/1000); +} + +?> diff --git a/htdocs/image.occupancy.php b/htdocs/image.occupancy.php new file mode 100644 index 0000000..ef1f867 --- /dev/null +++ b/htdocs/image.occupancy.php @@ -0,0 +1,37 @@ +SetLegends(array_keys($_SESSION['graph']['occupancy'])); +# It seems the guide lines break the grpah if there are large number of points. +//$plot->SetGuideLines(false,false); +//$plot->SetGuideLinesAdjust(1.0); +$plot->value->SetFormat('%2.1f%%'); +$plot->SetCenter(200,100); +$plot->SetSize(60); +$plot->SetTheme('sand'); +$graph->Add($plot); + +$graph->legend->SetFillColor('azure'); +$graph->SetMarginColor('azure2'); +$graph->title->Set(sprintf('Storage Used by Last Access for %s',$app['server']->GetStatusDetail('SERVER_NAME'))); + +# Display the chart +unset($_SESSION['graph']['occupancy']); +$graph->Stroke(); +?> diff --git a/htdocs/image.schedule.gantt.php b/htdocs/image.schedule.gantt.php new file mode 100644 index 0000000..a236b63 --- /dev/null +++ b/htdocs/image.schedule.gantt.php @@ -0,0 +1,88 @@ +query("select * from ADMIN_SCHEDULES where DAYOFWEEK in ('ANY','".$dayOfWeek."')",null,'SCHEDULE_NAME'); +$tsmSchedClients = $app['server']->query("select * from CLIENT_SCHEDULES where DAYOFWEEK in ('ANY','".$dayOfWeek."')",null,'SCHEDULE_NAME'); + +# A new graph with automatic size +$graph = new GanttGraph(0,0,"auto"); + +# Only show the current date. +$graph->SetDateRange($todayDate,$todayDate); + +$graph->SetMarginColor('#eeeeff'); +$graph->SetFrame(true,'#eeeeff',0); + +# We want to display day, hour and minute scales +$graph->ShowHeaders(GANTT_HDAY | GANTT_HHOUR | GANTT_HMIN); + +# Setup hour format +$graph->scale->hour->SetIntervall(1); +$graph->scale->hour->SetStyle(HOURSTYLE_H24); +$graph->scale->minute->SetIntervall(30); +$graph->hgrid->Show(); +$graph->hgrid->SetRowFillColor('darkred@0.85'); + +$item = 0; + +# Admin Schedules +if ($tsmSchedAdmins) { + foreach ($tsmSchedAdmins as $tsmSchedAdmin) { + + if ($tsmSchedAdmin['DURUNITS'] == 'HOURS') { + $activity = new GanttBar($item,$tsmSchedAdmin['SCHEDULE_NAME']. + ($tsmSchedAdmin['DESCRIPTION'] ? ' ('.$tsmSchedAdmin['DESCRIPTION'].')' : null), + strftime('%Y-%m-%d',time()).' '.$tsmSchedAdmin['STARTTIME'], + $tsmSchedAdmin['DURATION']/24); + + if ($tsmSchedAdmin['ACTIVE'] == "NO") { + $activity->SetColor('gray'); + $activity->SetPattern(GANTT_SOLID,'gray',50); + } + + $item++; + $graph->Add($activity); + } + } +} + +$item++; + +# ClientSchedules +if ($tsmSchedClients) { + foreach ($tsmSchedClients as $tsmSchedAdmin) { + + if ($tsmSchedAdmin['DURUNITS'] == 'HOURS') { + $activity = new GanttBar($item,$tsmSchedAdmin['SCHEDULE_NAME']. + ($tsmSchedAdmin['DESCRIPTION'] ? ' ('.$tsmSchedAdmin['DESCRIPTION'].')' : null), + strftime('%Y-%m-%d',time()).' '.$tsmSchedAdmin['STARTTIME'], + $tsmSchedAdmin['DURATION']/24); + + $item++; + $graph->Add($activity); + } + } +} + +# Show the current time. +$vline = new GanttVLine (strftime('%Y-%m-%d %H:%M',time()),"", "darkred",5); +$graph->Add($vline); + +# Display the Gantt chart +$graph->Stroke(); +?> diff --git a/htdocs/image.server.stats.php b/htdocs/image.server.stats.php new file mode 100644 index 0000000..857fba8 --- /dev/null +++ b/htdocs/image.server.stats.php @@ -0,0 +1,62 @@ +SetScale('datlin',0,(isset($_SESSION['graph'][$activity]['mode']) ? $_SESSION['graph'][$activity]['mode']*2 : 0)); + +ksort($_SESSION['graph'][$activity]['data']); + +$counter = 0; +foreach ($_SESSION['graph'][$activity]['data'] as $process => $detail) { + $color = $counter%count($color_array); + + $$process = new LinePlot(array_values($detail),array_keys($detail)); + $$process->SetLegend($process); + $$process->SetColor($color_array[$color]); + $$process->mark->SetType(MARK_IMG_MBALL,$color,0.3); + $graph->Add($$process); + $counter++; +} + +$graph->xaxis->SetLabelAngle(90); +$graph->img->SetMargin(60,160,20,80); +$graph->legend->Pos(0.01,0.50,'right','center'); +$graph->legend->SetFillColor('azure'); +$graph->yaxis->SetLabelFormatCallback('labelformat'); +$graph->yaxis->SetTitleMargin(45); +$graph->SetMarginColor('azure2'); + +switch($activity) { + case 'mediawait' : + $graph->yaxis->title->Set('Wait Time (mins)'); + $graph->title->Set(sprintf('Media Wait Time %s',$app['server']->GetStatusDetail('SERVER_NAME'))); + break; + + case 'thruput' : + $graph->yaxis->title->Set('Thruput (MB/s)'); + $graph->title->Set(sprintf('Data Thruput %s',$app['server']->GetStatusDetail('SERVER_NAME'))); + break; + + default : + $graph->yaxis->title->Set('Unknown'); + $graph->title->Set(sprintf('Unknown Report %s',$app['server']->GetStatusDetail('SERVER_NAME'))); +} +# Display the chart +$graph->Stroke(); +unset($_SESSION['graph'][$activity]); + +function labelformat($label) { + return sprintf('%3.1f',$label/60); +} +?> diff --git a/htdocs/images/ajax-spinner.gif b/htdocs/images/ajax-spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..1ed786f2ece49ec5db07dee13a56ef38025b628c GIT binary patch literal 2037 zcmY*ZcTf|19{+AO8xjIZfItFCFrkL3BodGwflvety+|>T03uy{D35o<4X`9Q3=bSU zojZMs3Qw_)j=f_!)B!!mUKqwc>ezd^4c;H{$Ifr|uTTHRC8&buXgI)uM*u&6{`~Rd zM}L3+_wV1IK7G1x-@ebEKY#i1<^B8j-@bj@wr$&s7cWknII(;8?sMnPJ$(4^-Me>t z_wN1p@#D#pCr3v|A3b`6qUeJM5ANT;KRi7A^5x65YuBDSb?WNXs}mCwVPRo|gM+(v z?RxX(&Gzlv_w3no`}XavTesf4dGq@9>xT~?_VDnybm`KK8#fLfJh*P%x(gRB?AWp6 z>({T(pFdAaOMCY0SzBA%hYueT6BF;;xnpHzb@}q;O`A4(d3im4{J5Z?;ONn#0|NsW zFJ9cgfB);(ugAv5a&vP_OG`&aMz(C(^6J&A@$vBk2M!!Re*DRkCvV@rJ%9duQ&ZFC z&6|%MJC>4?a{Bb?vuDqqIdg`~z*4Ws1>(;I2=4ORL zQCC-|R4OYgD_5;rwQSk44I4IW+_=%t&(GD>Rj=1yxpHM_Xh`ytnG&0k9<5Zz%KT@c z2mnYvQ>hsF`jQ_R5(mKIydH2saldkg!Gzlvi9nFeu<gyfnCs8|SGO1R0M3N~Sp zx@MoynP5Rh(303ldPHFemMT%$+lXy}A1JR?IwL6=E`apW=@Z-0R!QO^@j_&{6#;i|5R1o$ zJ1J~xCDQ$1cs9`DW9Z!)D)^+%&Ug81908UkMXX;jkp>#b+SE}d{`0S>>Ef(`Ns6oa zB~H-LX25y1!BDgW%?nC0$RettYz8zsuz>9Z!g8TH$kU;rwYWrSTkjuYCH9utgFU6b z*wzBKaG6IjEXYSpAo5j4&c(t zwi-c(Q6YX2N-v2q(mgN`>wuCTV&XSRUA4h-f8g+uV2k_=o;2wb`7N)&+7T-C+CT_Bu4KrjWmK>x0AbH2rmR&7+q6fX+R0o&}V!t?TP+g0{x`8PSP{7CvnEPL^Hzx^T2Eus2 zi$U6ZM7}+l%$}afWn#%|+MPTsC236GKi9-ad*Z9;Ymg?jSO$L1s$w4BvDzu_kH9>z zTWC{K?jx4~H3oGGt3}I}LWNw@H)C>9GF4^|x(5n#+Va}kr_cb>{a+K%>B+@JNrC8? zJOUkD3fe*NdA-rJupqo?FfRK}k zOm!l7Dj$dsL|kS`3FL3^@^7axrU9d*@79yf!+bu3kXbziU!v&TO3p#w{y_lYG5ZPzDh;giB_lkGp((nqZ12&%LTrgm4vY= z{ffd0B$DiUKReJ9>?{#_KVrMyhiu_A8fNd!&DWTZ4+9S|1lJHkLv-^}a0IC{WI9N! zQTY-}db!%OH^;l2ZeajnA&Q6Uv=#0KZB9;^^lzMOi^0~bS~m!&K#e6hia79(ItV9M SXP~*+AU!zMlD%~Wg#Hic*k=_0 literal 0 HcmV?d00001 diff --git a/htdocs/images/debug-cache.png b/htdocs/images/debug-cache.png new file mode 100644 index 0000000000000000000000000000000000000000..87243bc6857bf3e08a516dbe8bb44b9ddd322fb0 GIT binary patch literal 648 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 literal 0 HcmV?d00001 diff --git a/htdocs/images/home-big.png b/htdocs/images/home-big.png new file mode 100644 index 0000000000000000000000000000000000000000..d40900d98abae5328e1c2700891e8d7e902097a4 GIT binary patch literal 1084 zcmV-C1jGA@P)WFU8GbZ8({Xk{QrNlj1+3MgYKATls8GayP~Yjt8ECu(VJ zZDC_4AX9W@X>Mh5Co}I@000AjNklFZBx>O9&-t_^ipF8m|jYaF(Ot?Kvbn*dD~bk zMX%kp_uYebRjpQSlN>rQush$*{O3P2-)soKoCXSgHZEU&pBIsP_rAF(^atPH-zS|; z1K>CgSFc{-#`OBZ@z-v-Ri~n#~qxPiGk$9VPR|5`Ub zPXz%0QmGWGss>nia{taPS{i&Gh1+-UcZ?1!FTdUWeqwThCx3{80aR6`*=*uCPS+t4 ziFD6jdj(!j*;JIU&u?vQz5ATWoa9(49t1E=lUOWRyrg z-~R-_vP>>qSnN=7)c{3{2A`m&@_twMzgz_~LJ$TrM|o>bzq40D4Q)+XGo{bL-{}KD)V! zLvXU{Ppbi&qIOt^dgGT$B?LjB(P-c}4yI|cxw%Q9@aRQX#T6gG!NGwqd($-0G>u3k z;$!4E4zXAaQ50ENSm;mGC%zKdw#~-I2BIi3Jv~h}n`LBVq|2yR4-XG%wOaqlBG7|! zxlA^jWqW&@+1XiItrnsv`j|P6Ln4tN6bd~*z|71HTU%SKtgH}^$4MrW3=a2w-h*GZ?-G@DHf z!@x96EX(STWBn6@s0ZK$k4B^Dy3Wzj5td~k%QCvIqpE5*ux;DD7dP~842}cT>vbxX z&JJlb8Yqf_q9|0WRb*MFR;!_D8ufY|S(ba_i~==48C0qSFavz*9zL&r;{MOaykY5; zkgEwG1;pKV!EL'; +echo 'Application Icons'; +echo ''; + +echo ''; +echo '

Application Icons

'; +echo '
'; +echo '
'; + +$dir = opendir('.'); +while (($file = readdir($dir)) !== false) { + if ($file == '.' || $file == '..') + continue; + + if (! preg_match('/\.png$/',$file)) + continue; + + $files[filesize($file).'_'.$file] = $file; +} + +sort($files); + +$cell_style = 'color: #888; text-align:center; padding: 10px; padding-bottom: 20px; vertical-align: bottom;'; + +printf('
The %s icons used by this application.
',count($files)); +echo ''; + +$counter = 0; +foreach ($files as $file) { + if ($counter % 6 == 0) { + if ($counter) + echo ''."\n"; + flush(); + echo ''; + } + + $counter++; + printf('',$cell_style,htmlspecialchars($file),htmlspecialchars($file),$file); +} +echo ''; +echo '

%s
'; + +echo '
'; +echo ''; +echo ''; +?> diff --git a/htdocs/images/info.png b/htdocs/images/info.png new file mode 100644 index 0000000000000000000000000000000000000000..88598999024753b36ef90492ff2c5e78dd963df1 GIT binary patch literal 733 zcmV<30wVp1P)LnxVyTa%M7lDe+V0@wuLmL4WM%%0)lyH|UAZ=8=r8YSE2FinnBDn-Y~AEu_J zUOoqq0pJu3ti()5M#g$ZN5{It;Si(rH(Lk+pT9<&K65FUh=1$MWD+v~3frdQP+#8= z?d!XKvAWtrs;VUF@i19Ym@LaomKbGJqPTVYev_goX8|Z>fd2l0{Y_0#iBjT`Boa{+ zw#(;}S_1*4+3%O@s0^ujPn&)C$cY00yxV~lvxr8UYaPd>j^p|nqaCtL0*o;#%S?B; zlan2)am#T+p>Pm@yb~be@gJIDID%4e%d)7~>&p{DyoO@(&kD4aPGi$0h?2prU~1&{(9X?0P#IOr<#@tP2Ga3=}4qk z)&OYu6WqTQux0Z;?rGJtke2(PGL1YHC3RagPg P00000NkvXXu0mjf$~;YF literal 0 HcmV?d00001 diff --git a/htdocs/images/key.png b/htdocs/images/key.png new file mode 100755 index 0000000000000000000000000000000000000000..2ecb08d79515353e524f96f9d5b5b7449a882599 GIT binary patch literal 519 zcmV+i0{H!jP)6nzXdEwY9aAlarX3n6k36o}QksudlecxR;lgsHmu} zuCBJWwwalkot>Snt*x-Iu&1Y|qN1YK*4Dzp!lk99prD|eo14GCzq-1*p`oGG)z!PZ zyQ-z%gf8z+1cac5qobqj z?CjCe(a_M)E0p>R00001bW%=J06^y0W&i*HrAb6VRCwBD&)XV8Koo{yVoEASDWpNh zprV~k+4 zFuhy}>|)2|&HUHTl0vy2h~8_7C_m5Ls6Zqm2Ln002ov JPDHLkV1kZM49@@n literal 0 HcmV?d00001 diff --git a/htdocs/images/logo-small.png b/htdocs/images/logo-small.png new file mode 100644 index 0000000000000000000000000000000000000000..4a7558122c918599463e011493fb0c38a4e4427c GIT binary patch literal 23763 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
6*dC_oz8}9 z2;+DuKJKE>$a&*%Uby5=%i^O?y@$ARi^Rj~r47B6suDR?6C_=Ul0g~t4Vjn;bik(% z9R^)>d6>BufbMmD^o9taF0z3dflsG5B6lZor$#%cXw*5y**_`WkUvt~h`T$j6&428 z$1D#wCVIkaJF~wO+5We3Z>slZDBQLipL&nJ9mqh|S<+uRh5cM;_Q_ZdiD7ySr28ruo`?f|s@s2d+F`8iyZ@0T0%lj!`Ld~G%+ zH49l(DuCVGI3|b&j5wd0ZJ?R>bn>sRP-dMHi^Z1!?&42%)8a7i+}978;BL214|PR= zr`c5aEGv+KqUIwHGuYWP;lr$8OiQd?!ob$QPgkS?-HWd((?GIhDmm=wsku~ltN36W zvYY>!w5a*mea;vmZWC)o$7OXgp<t_VQMP*xvVBSxrhCskS#OU z;ADrtvWb=gJL2U+)xJ-@FDDr0xqDycC-%s*w)chY{J^2bq5shxFsbsPAD9h4wrxP$ zlX9OpXaw5XgJ=@~6|LMTQB-2zscQ6QV3and)f$Dm*->=#j>_!^l}qXjSa8hIVh(ad z>&c1_xTNVwr-5YM4k)ilxspi?uI)g4rAlgwz+Lq{5dt2t!F~Um4tRd|3 z7y!%;zwGWy4}_#D%@!WGmBeOL*#9$OCb~0PBETsI_%=?E3+H|b2!X9zW}K@Th$_fF z&n0`Lcy@ymh~Op8fyVPvCOXz-8mVc|^BF&+)F)z@PR zCjb3wi{66FT)D%J+06?9z@n#e>pSQhcHogRAZ9}}BLF4jWaWN9q9*qmzyL0zN6k(_ zTxNdnYJrrFKmi4?MjdI!gIeD4?zM4j;NPbd3H$jojGsq^j@j}!tUg%I+Nx|zgw&(& z2QNSv4uX>IAa~PamIOQ~y&9t|6)a7TP7(`S+a-YnV|-=LAqs4wH~*f298T_AQis_< z>B4+|!~x$9j*PWCUGGy2`+X`Pi!WF>r>oM%%3jw@ z7s=ZmV-Nn)4$>|bhiiue(PINFd#Jf{d233QVD3b#QMqQU>7{888)2*p+yMGxENW8b zc{FE7RIxi-*ac0otcjJ`+Jg<>LT=^0sQlm>g0uX!d;m3E#Ik8Q7e8y7p*Z?IXw$IQ zBk@G;Ak=0t^?l3TpSszxOi6UB?>VruioVAv;7cgqy9E-9) zOl@`-(3aOs<=STREt1C34g5D)+`loq04BW~Z*Wko{oTo?x!<$+_UQIIhkpuoTWr5j z&;H2Yv-j7YBx}Yd^zgslN^TpdprmFkL`VZAGibr5AsZ9%ij!}FDf#k0q`(HkyI5Bc zf|385augNPT5*SY3;n;Rr8K`Ee4B?32{_~ly{mUu5~mel^}i*Q3u!n%^RT4w03w+I zBmUt2=V^NdMDzf3Lk~6R&*H?oQDlTMgdsU0s5nFHXLOUh{MZ6z@D-61TQ%}}=0Axo ztZBq$xn_^;#az5%-l^#@-T`;s>TekzP7wt48znXPp^90)*#hJ6V%CyyB!i^JkxLFl zZ-OTpW+D}&n!u?=;7WIgEgjvNT=4BLu=CNOXona>)oRng$A8Wu6*2RQQ9by;`CX_# z;I-gw4a!cim-+=BhUBNbL#)wUynH}{Ek*jJr$LFcEviBhCz-epkQ=P8| zG6X)-%V?2*IkpV5gmWZUoy%h$d5kklCAtcM_vjoYAV>Yx&oua4HKfBeY}Ee|qD~Nn zN=sspgThN6xfXhP1S0~1;kUjJ4nS3LyqgS=P>uD6t;7nOlEIiZ-lU-OhRiyZ#w4(d z8w8#0j7ohak1$?v3r*A|(#W}rK|tH1#GQ&(hL8(z|MbR;E5Ps=kpZ1xmXT?ya?e#{ zh|2`cpr%+{VM!rCyRp;z5n#1usS*DGo{X5!UxD|^*MGWzS0V>3ZwgTNZBGOZBJ{Tj zOEn%$%r9*P*?Q6)EoA_pndf-;{KP)XV?SyCDAKE<{#T$|24=DV8AgZQcc1jq!U}t( zmjSpL^8V$Deji18w*K;4d*^g72mAZ%frT$+xcZlZEgB>5(05`H@01(V&jIY~4e1Ig zAo!C=8HnNUC$SlX_U_qs(s?5>FfUPp@ z(nkZke{E8>9ia77>mv?6gypT01sAIV;SuMvw52tGTfAY|51=~7#bnA8#x0;QcZ%R` zn3lMBg4k`wrnN}V{qnuIv>rb&b&l{iex&>qr7^#KTuh=N7==oebHWO`mcw6YgcO%J z82!7_`!C-lDh67p1@mq(A?qgZYA5(}x;6E)RL=OA*w^(%KfBQi}VW(3Xgg72pLn{W(x+p~NB+IfQVX%7- zxX47*pOa)+SPm)q_`!U}@DF#vT41t2jm%jsh4yp){T}x@Ww)3HtM7VVm`~+4X-{L; zZrE0u{jB!C$;~d6BI(wCxZ<*XCrrx&B!?{&A{P4E67rEz3unhwf@;KSV>OyVl10#= zpf_=<-FDn)CTYotq5ME~s!_HCKrF!*!|h?7&Qhjf&(h4`Mdyf}BWRNDe(J8C@)jWc zOs0y4?biRb+aznE>91Qf*g19A^2{Ietm^(0!4Z1+WmdoR9C(NFED7no&aWS7gFMVU zq$6qJLyawcC#bypA{3MV)7me7gt5VdKZM};0p>YGY@pW0mzF@3thod6gIS0Hrz3SO zF109%KqsonmICiPTxznv*e|LN4pV;9D^phoMm~B*s*=YAY+AYOpuG0>vMP#~)>B5StWDodAr&z$%<=wT0GJ$S z$dHuwZKv>_frClEaE!hpn@^gX4#d;gVUI3x&5yr&p3wS>#)jjDb0(aQ@h}*U6L%#O0p4K3Rk;!nq!Z4qQoy~=Q_y3*LLyX3 z*O_^tGqsw_<9>wNkjKQZ9RT7qmg62k3UuLgHlQD-X;6O!cPp|>OfY?*qkJrg? z+JC>lS#Y#UTM(NcUJ#FCbv}IL#Q_$3iLaOd@NTkW5eX14IbAt5fM&bdIpJNvyuz~8 z_NY3iP1m;1!tT^9`ZP7BW9;{uT{=5iC!_rUtr8yPp9eVN)@)TzfM3!q*Q^141ZMC5 zei(3R?>fqFBcS-w99MB0Xv}VCk+Xnar_a>i0c<1f)z(*mpIfp6i_eLtpG!T}ZI^yo z2kkul2kH|up!KR^N4=wexU03xQTLSq?-8$wXAh7or5VyWfS{g#kJkerWkdiu9H^dN zDXA;~v=&+?R{&NnHnHYfz%|$XsLdoGc}wEGDeNN!2c6$LbpFBw(064Ln6QnD^erJ2p3jkVgu-B9W!bieT-d2Dh$Z^Csz_3|o zpf3Q-AbJe#4~W&0-I6wd$wJe9mX`s}Q_fRn5TG5V_0iV?j1%L_Tm(2Ts0g$LR2y5z zHZKI)-1E2dp9G||Bnv-B0V128PTjy?{s*BK-2eSmmR*b)^0;YIW(Bmm>cex<(w zScNV)1dvC_RTKg6(gh#H`GAqmq}0*}Fzhtsu)P7Dot9(p0?0+=8}jxLU7ZO);7jeY z?m|$=kohWyQjfHz!h^(EK(&gtsvikWv|s}4U{If62yqC&cjIs1?+0v4h4agb0Ef#{k{~-dH{W zakx0pya&Jp7@q1T{BHa2fbS5$Fa8GsqoXDgixW&!^^{E0Bq2a;Ej6j@2@q`6+d5l7 zY8L1FqZCk&tZ$Vc0w_MSLOZQnU*D6uQiE?dsIGp9c()7}9n+W}+~EMvu3}Mb6&4G; znwKg@0fOVkwX%hPA-D5JkMV$Xv`eMy?J8SQrnt-^xaG6jS-DAD=j|@*rk$(j0o-ZS zB+d?i9b&TFXcM5=s2tZcEcvuJ1jH^$h!$jNN!$%Jj53jegYxJoLDw~k-s`OcjGVe@eZ=L#%%$fHP41K9pEzD2V5Tjwv2~vCP1y> z8nG@K|NqO#vKtS9i9>^UE2o4HaVJ$VJ!}E@Ly<8UDTiAD5(LUbm2mkpY6b5La1Q3DQ#6^I; z2P5J;p!-aB=172+*}AskYm@EPkd z&0@_Tq8Po^1gqh;sl|4h>eh`lj{(#(?m8|&*I_v)1kffJrjcSmWvU#}^c>*&aiTZ| zfK*ax+|vL;Fp$^*XdCICR0Pm;N3&Qw31~TLxMSV~Xb+ID%$5LxQNmDRG~lR|#@8$d zY|TaA>lwgevDIal4VNN&$9LWn*j)IS_ba!VV?oToH-us{Kr5*a#18-u_$hp2K)=k$ zo%a~v$BE-ai)zkz+j$RY>()}?_Rf8skM8g0c2&E6^g0E!IqCPbUD(wV(tD1TjuRWZ z$ld+Vx!40_nU&mpD?oQ|a%xcm++E*JW;p{6(FqUoj{_bP)2=eaV@WPtm?(!tys5X+=}t={X#i!KoaK~#j&k7*mi<=J?l%zCwLDxiOD z9$~T#U@ox1tU16WSUa1p23RW#+V~wS^AY+Mb<+KmUaUKRWzAP>@P6QN_nemdRl!G5Lt;7g6)91MKxZT2N+J$zp#0LezmSpCjr#fs$6v;VDgK} zNwXk8xJSI!>@uL8s>w4H0!%scjXeaAzEpSe3Sj7|@1m6enxRU|mR|r#8?mjVC*U~T z$=r1gpbgUnGNFKKpo*v243KY06?q;I9}*9fQGnvQ;*ugn_WNlDg#JGdSRC*n2Cxkq z*hK*6F1>6Q(IH_C!&u8Wy@`-?Jg=yE;v4E?MNt@=YfCpDB&h`adN`xcw4gpSI zoaT8=e)`kqR|k$?^eRsxf||osJ*K%O)M%oY;;eZM@xJIO;9$ZNW#<8+VZ7zK0qAf1 z(`1n4jz=^9*yDOZ(!ynPIZSra?bLy(Pot70lf^ehvkgk)ZTz{)lK}r2RLV~P$C5Q@ z?gGMn!d#=F0G1NNunm9^R+tHJCXzka$AIe<<5l%D01r#^+?H6t<&K$DF&}VEFdZY` z0(cx33$ls;_jn0WumkW-@SWPBSL)raQ@W=PTX-`tBdYLcy(JKw6{74S&Ff&<-e$IK z`>#LfLYL__rGQS_cWV1X0I}EiscR;{vlb9CvpaDI-FJPTDa!cl9_-QH{m$RbJ=$Vy z>XbFWD7Vq=#%)f?9WZ22@z{uKHZopFVtX0gw>h9sQh)+LUf0}qBLQBF>n-~`0O!2) zSMd&j|3NxSYy@z5#*NZ7fH2GZt*axzwe0O4qz0JX!@WYS0a;R9cz*Q!)u*(ytEZ#Sg*Ia}TjXhGq@)kPyb!+?=>gK6!{>$r zuwhj6FhJ>|*e@T!@tVS&I(^3U=j;8l{5y1v`y7-JpY}fQIgnKTy)m;8kk_t2-()F;(lZ^!{^ZddncbO)q%GQC9!;J(>g-!TAi7PvHf+5jD2cT5a% zR7hIm6th}gfTotF(B|QQ=76SAM*_-*){pXffK-zf#4UN?Z=;0e{{3G<9{3;uSi%HS z0IkqK4ImwrFaf9z#1)ncSZ<)Gf@y$MMnyGwfJ1zvh6xWEir~f8Adb8$){2DU;_BPB zyX<-dPPl#2bGT1Pi0p8VR;wkIk+ta|GdnL0Pyd`rK0}zn;=_*opSkvF=7Uf34g4z7 zfsOZ?=ku}O?#!xoUD|gFO@FwiF{^P#BZ*D5ygG;a?trAXBwKp8yy33)cE_m7?AwKY zh3d5JZD+{Vklm#d0jslS->ufk&5ZUN>#c6*P0?m*=jAj79%8_@HGBq0bJB~Q2$)|X zE%LVlW`jj?<~y^EVKA6u%!FV%8$mP^(I#JwQ;hE_J30ls4C6}6)@$O=dXuVO3md#;O-;2#X3y7_x-@314v!Y9u+C~(5Z(XX9fiJ$9R7dfyFr(8BAZw{Cg zXcbH}cd8gAyhV9S=O~M1pM_@t>-Jv5{p<{8c1rt|PFGIYzfe3axwe*V8BjTB!=#GL z)irIZcUN_M&^C3kxZnG=OH&%$X?^QS;<;s^{dlLnHKk@tEmTfc%Ns*XLsG&vS2Zgn za%n37$sVap{8ABGe!lE%P<-Op*S{17WzQH=ubnOvNag_Ww4N^W0;~hAB5WP1FL-`R&)wf1B{SUU;W2zwkzZe}3Z7)uoX|GfN3cjZv1#WMdm> zbY&WQ)eUAK7O{`G4n(7nCvIy}oNQBPpOKxS=sR({nUHaLdU+C{VFRml+8bF?> z7L(@y$tsDt={Ue_okhC$A|P*ZZc6zWfa%R%CPtCoZH)q6gzrrsY2C}Vo!j2+D+QUN zM5`woSG?~0Y}@}Au(IpcaBa7d?t{y(m$l}6 zX2m5rB`ZoeNcLLc^U;X{8jjX4YB&i9W(#d>wLkNDc3hUos?zno-1qVzUGn=+8 zbW8g<_S$Fv~Yv&p}V8Kr!ekg@igk;#Wzh<7poY{ zQoAN%#N{o#|eG8!|f=Zr`!K7;rr0sBodr?0Eg` zUE%AbIqREeiUnaRfXovmS$zXKTrsQ3k^%nt?&Kf#FwQ`Vh znsTLbLV?6Q#{7c$=f4l^PisHUecJ|LhoFMo*FIajNFCf_zbl+tLPZg?j9xq6a;|W_ zcxB_2^h>wTa;ariv+K__6DNB%o)xOQ90v?jIo>8kK-(3P8F6a>+gBz=q~gFY_Cs9W z2W+~3*U(>|i(8xv9rA)!cW==xb*gl>acrL|G&mTp!k|mxmUE{N-2XTsO$bOZPYC|w zC#!kCm72%Dl`WMWQ#;mojK}{nrGnDgWj26WTQk(^Nw$iq|-b)K_F&(j~&EGmpId0Fh3^(-p6Q`->N{^@kG-)i5Xo#$s1=blBfErpg_%=-NMjqiBvzcj}9zfJDoU)-l?OTNXzPiDd9p-1CKbW;?eP32p@~H3=z&Nr&+T~`; zyNKrzR->+_7w~TIivQ6cD&AEXD&qi1QOLHjjf#2xdDxpx`}o-&S(6j)xQuo!b{XP4 zn%?R`dfEl%9@t;)T|p&(jy8-KHdef%6-e8fR-3jNkXA|?q{Y1U**ktjW+(aIC?8!^ zmmKH!4lMf|0j52&zi0ILOvaz=)D$)+;wrT~tSNlb@cg3h=?_v5#=RO^t{bScR~?wp z+bZ1Zv(15)zLuA));Vrj_gUwv`>Z~Z;_JJ{uSei&0N+pxSkHPt?fr(Aa*wRI(=ivL z16e1YJAb3NKfu|^xx&o`I%Wr_b)$d|8G$#$4gnv@_eWj~26$8Xxq<;iWY^WB8Y0CU)vx40GlW^wbYy`Y^xx|DlUe1@6#XA zIqd4@`M~>AkiEHCa+}0QpZ%k@d^-^v`R0s8FZ)J^D-Petk3FQlhDQ$nI^8SS+qT*=U<+BzJof8!yHtk|A8Ko`=8k%h@=WXBPZQdv-THjP zRp8>`3-6*jpV=`vV2b;=vn)Hw6k_&4$DTzJ)CiMT+Gei zPi$`YzdjWI_xQIb>i%sH>`%Yz=;Pn)SLo6JEC`x=af;uZnGClAvbJp+l*mP>j^nX<^{*U$F@O!@o2snP=Qzb-0;3v&*_moF=cnkq(A!iM}Pm{ z+J}(*US9h5*ZsTCJ^sUg{;fa#;a~si`}&98KlK0ob2)ZDYk%4U)$$s$I!W^2_25^V zU)WEYet~zc^w{RPk5ZJ$jY++l8} z0b)Oqh+2SM1wJwXmO*FEet?~gb)5P?Tm4`B#b5ka{4JNjb>;?h3jsl$@S1Qd!1kuE z>Pi8uV@=uZ02_?~tUJK=rGL>E0enmT5yS3ai{8#*aKdi07?cylF z`+%1~_nAHR^yf0uj7-D>c|!on5qf%QN5Fa@UtD$>ko6RU-)OM|BJu)i~owh&9FL637Ol~0GL*qjxtZX@7Axi|J&j2)1MT*ER4*|3W*f% z=Lc8L)je`3bF6Apn^^C#%`wh1;7UQ>om{W18tnPAJMstJ=69RbeK?HW@Gup!*j9EmCn9TyAvc)0qLKu{xAOGFa9h3 zmURCk8?XFTnWcUK5Lbw;s0gUZ^~wp$F75MNKTB1EQV1;UVtA(EtDd07*qoM6N<$f&~daRR910 literal 0 HcmV?d00001 diff --git a/htdocs/images/logout.png b/htdocs/images/logout.png new file mode 100644 index 0000000000000000000000000000000000000000..393598b9f30951de2b8d48f569104eb6c69ebd65 GIT binary patch literal 829 zcmV-D1H$}?P)QQFHeTbIxjvNAl}x>?!!$)qY<>uMc3Zm!KqUxb0S(0u`n&OWR~W7 zs%7cbQ{ru{G&dh1olfV+j~_pg1BiuMS(>s-^kFj1v*C}cQ-(j%77WtTa*XTOJLv80 zqr1C{&dx4G2}%o=SS2%%_164dpg)nIr>6_Qe+%8+U3r^w6xz`n2SkB>;Le@f1OnU1 zWZWXVQi9S7Q568a(I|6&h&sT&74e%uBr-@%O%1M_DLv5)1H%BzGO@%e1ze6qN}SFW zh(CV(kYI2puDe)vhTp&aK(v5iAWAT;-?<~FFCHz)q^lPOvLApx;Q8}W>g(&vM*gcU zFE8f~J;T5h6H&7E9`vnz8yXsLUAM&Ux-O|yiiL#*w5_lmjcqGzTVt&U0PF|emJ^P} zVvLQwXK>^J^O+QZ+U-3VS!P@cil)z4*}Q$iB}d>0za literal 0 HcmV?d00001 diff --git a/htdocs/images/plus.png b/htdocs/images/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..f66b1a80934c34bf818efa50824d96bc138f8e04 GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4^3?%3Nf7cA+1o(uwUbyq1uDc(|YY$R;1f(Q9 vT^vIyZY6W@mL@P9X5;u4)tJz%5Fp1e;Rpx2`?oLpKs5}Wu6{1-oD!M<(yph*Sa;x_0SXT zUqJ1>y;LoiN+5wyMb(N>mVhB7b<^0f<7M`(&o|VTgP{_i^y*t?=6SyNd6(hn^#_kn zA}~=91X2ox0!+f%?e2B9Yme4Dqmbh0=6}{nJbM?$iJ~YKOQo5`6DO2RCd2cBu#m}S zQ@W;Gf3Ds68U$BHab8JqVr6BuP%K`~DTRtpKBXtJUN&DKpCit(U9R8P$aWmEU6-V2 zoF>a<`u(@t9|Qbbm^UDKB|))RoR3fB4_Vjhk^_6bcS#flG7Xa_ zj^iV;d>UA(0p3d`$|=fQx#DDI&vHpHEz%8>4Bf;n9kLu7H-|wY&-Fm)ksOw&+w1w3 zX>x)jDAyO1Kt7^Ecw7)axTKoodlmuAEcdyccwF-9;mJ3FJ~3sa_;A`Lh0 zsYW0TaEP~vECk$#fs;JUsYH~ba)f%lULd2cst-Z1J0KY&NFI%1vD2Wftu1Qo>`=4W90pUAB$8#>mm?8x>&X*a@Aduw<`Gy67I-m60Kk*F zY0lXQmQpNIKA$HvHCkR;Btev@)9q5Hvloyd_o=zl;I`{^A(2Q3s-~{0eKq3S_NO4& zf{lB?s#eM6ptV5`3Mvu_Q+8tFxfIZu?L{$5$z(FfnvIPOq^uL9Y+u)@-&Yy#aEyAu zADf@gCsQe@TrT^$T#jSe;`^*x{UH4u7u^%2EFGJTCaphwK#w1;!!r@Z77l`pBJqdl zSPTgc9zMMI_Nh~!LH|ijRsC%MNs`2|mJpR?f^|$pIp3nd46F*VYg#f==J-7>4qKzSjCK97I+Yi#b|zMiF%z5;?W7@ zyMO;)A5#5C>l~L^7e2N+pWGWmZVcm{d%LcB>soRYLHg zAI^lJ_ps;d4<7sg!Y`1jfBlCYOlVd62i%=|b9VN^d#kJGs@3YzLaC(Sjqp~z9%x^! zRH%VeZMAl3x7DKB;~K54t-0%W?|$yu_BZ>{Mq)5{fZN=?fA8KS%-`WRc=SN|z}Z+V z{=wnu^wH`YM}ps^fZ}2;yn#SJ+uZc)o14F)FMU727@fyF7=8G{dhXc5!iCdk&YUez zP0gVdDK~E3q+hOHy<+M5mmt`9;r(FyIt1*4aAkU0DNRn!MPjitwOVcBS*`XJIxGX< e-$ny?qJIJ6eZ8O%c*~gp00005=iKvnTzHQf zum#)$-9GR8XEpwTt+}~5U0q!%NfLFtU1eFOIF5CGey)1GuD;QSXzcIrD@{{nS*BjE zr?Sj)9ENl62)<>ot+&4jdzoGcXyQ}i8`H*y4|jNy`GFQIzB#DmSsxQRIjhE zdU<)#=H})|l3KM|RUF47(~U7Q#%OzcJ5Mk$Xt&!cm&*cbKCrUBzK-wvc^uF4@;Zv5 zyq1*LYinx&D?TtkJ3EV{k%k}$a)aSJ=NytK7K;G$J}@;lHbxkRd7~tZF-YR+>503$ zJ0xMPMG}{nmyC~(15EjYXp(TwVXeg&gS8fGEgKsfEG;b&hG9N^IFSe3w_2?)6B85o zzMpdd=Nw@evaql~v)QCnDq*c9O;e_)r+@jt>CMdz*4jM5pR80WG@DHp7Z-8P;hf|4 z_7>pO18S8@<>$%C2|*Cxd0x)pxC7z9ba{E1>+9=!0JPfe_NPLjP%RdVBZ6V!Fb@t6 z*xTFt27C|tPyNwoH0p&y;q%PQO#bH=7Z*G~KQlQwIU-(NU2OtiKU}7AxvZn3BRxJo uDvBaSQKW~52OS?yig}MS2qJkk7#Rdd9 zfd=H@*?7~K;8ibPZQ{X%lP4lVh!<1D;2)?lsS=>ImC7!amMwO-v;VrgGt=3b@!)Q= zr6q#C=c`~80JJMi!A7~njlzsdh-5-`%!)8m{vb;?;-Sa2pMC!MQTuR0?m zBhKN&hZ~UZfbEYedI0#mvBN{J4mp=Do^j^pW}RZO;A9pToKh+0WHV{!#~;3SP8@&D zId=45^NBtKo^2Ksn#OMc=e{`e2^}3_TrQFM`TLCjGC`=dlN&dF$1timw#l~bJ2-Lt z7=^+dqoY?icwisp$})?KOT7Eu#{dU`Zvk9QfT=Ifeo9||KZ-v@AP}LnY~%6xxqNwy z+qaVl2&ig~iODJM-c6B6^dU)Y#N$t3)pQhp3qwQCF*R{$YvIa#kvTGLrhL2+4s^5ESIuKvV`01CK&LqiTNr( z*LA#JKU=o!BAuSaG%cdhO^Bk4fq_1>>N2XDLBK>eDijMPx;AxSu2gZkL=+`JHk%`p z$!!b(xOwweQg;>@9zK8|2&}B=SeAgMm+^Wc_**1w+oD*^(i#dN$v)cJqG(!;TrS78 zYZFwe8o=y2KqL~mp3i4?Ns`3)_|J5A$ME|VHf@e0iXtH3*cO&;VHi5PR;F0Ykxu`~ z)YK&N^K(?I8fvZpkY5K-RdsA^?Aor~yG_dFDwXnI#N$s>DlMUFRVq~j+gd@>3RpFh z^uj#(oQmx@h=PD9ir6+Rmvn&h4c+D{TAY}edD}4D_AQS$>3e_=fcp>L0{~~wo*mq}HAmv9Exf#cKTpPE z6pL9BiLGe*3Wm1Cz4RhpkIdw6NlME#QmGWzuTQXTTOU(XDS%d>xGu>6d-v`n9*>dD z=DBd;9HuF-va&)touSi6@)d<$84>D7Z4i&1&D5R0>^Rw3!obaH2`iP0K@>Q z|9A-=kN2@vD;2FVjMh)#Y9!ZJCz8#s+6b0qJ+ingOX#|>>i*41uGKF_7I^>k>GRZT z1~W5%P%alKl}Z!}B}7p~6kV(hqk(PPEG^|R%^GLVUIh3MSgvoN(=-7X9UW`%egeJ%#_K+G5730Bf3s8%RZG3hUI$(Q sg22#2B6A6t2F8FHz^+F_1q{IX8}k$aK5&K;&;S4c07*qoM6N<$f{T1sjsO4v literal 0 HcmV?d00001 diff --git a/htdocs/images/uid.png b/htdocs/images/uid.png new file mode 100644 index 0000000000000000000000000000000000000000..7be48fb8cd09d6adfc48954e1907f38d3bb0c3b1 GIT binary patch literal 654 zcmV;90&)F`P)jL%;Pn)6~ zp+z7;99L)q8&6i(@;pDhvS!nc7x4PcUW2hXn?r(7S4e~~4wNS)&tLR1=iDr~xz%s_ zpCYEMNeNI9QX-`U1SCOHp6#ty*-X@(cLimD^%ZdQkQYW-ZbURRu<80K^-R;In0T@uAMdL%H@{m-*2~J%XyJ%VN zE@#($GnW&I^uH7h;s`cp6e}0&OCWbNaqnkKr2DFgq@H3 zGts7?ud1dvE>`|~(zIvbA{ONS6{SQaYpg6)B3o60TPim(N oN=PK6hSf)_xH~Spv5tS$Z>|sW3LxX?DF6Tf07*qoM6N<$f^GL0UH||9 literal 0 HcmV?d00001 diff --git a/htdocs/images/warning.png b/htdocs/images/warning.png new file mode 100755 index 0000000000000000000000000000000000000000..f32a55ac459904d1e8599c794b168022b2424dd7 GIT binary patch literal 2295 zcmVM z?#_B=f6T8tw-2mcj7<{dwMROdt9$Rv_nhxJM{^GRzl+#tb4!2RC|a(m6kB8%K582J z(}oaR5o`@ZWdxymK+{BMMsyDafedvw2u)2W8G@%mnm|ROiW=gteLbm%GXjs2Rl%xOQA!arP4xColA8u;OQuNl_92W2 zqUD1s=jlwPsAX2#W8GD##7xdW;cN~zWLiYTS1 zFXgbNJ}39_2iOa9)RywpT%U^zOZ@#-)-E)hn|{FK!i>Ts&zeDE2d(17BxmB?_GdW$W zHC4(m4DHaN;fKXw!iY}a&XIm-m`y);p6@>M{nd}HfNGjXEEZec3&Rj8CBE<9*C1g; zNNpYBk-=?5Qti~vo?*69qb(YdwWfy_$nc;L(DWtAs6pq@2sRYx|tzdjApaB`p!n$F*Hil(2z=@>mmZ6>4H6)&_abm7aIM; zBbcVS?qyBW5JK?gn{V>kYp)Rm0ekoEWn^T8WHQN#6DPQI>CzqAr9?`(%FxPMGYkw( z!_Wk6@rVS_3`27bT>}J8AW>Co)#H0v@O__i=gwhS7RQet=k?cL=k)2*96frJa=FYa zue`DfcCP|B#T=fLcS;!?07EGpKafNdG3tIm_0|ns$03nOtcj}-0x2cBu45PmK@i+2 z&sdDjn>XJnIoDk^c3oo1fYQ}*9A6Tukf^R-Z{?LJnz|<15^-XNMm2w%Y9Y7I1K;%7->omzH|T6UJjT9cc#YR;8%77&`_g=04=%Sjl6kj*>Cf>3tkU3oiYMzE?j-t`F<7Z>ka)8%rRiHQlK z(I~rj??%`44daiL6hHkpw&O8Vv6-${btjP5ffNwcR^sKj^7yMCoKJVOw-YmT%GWNF zy)usDIO|xb)oKh346twCJ~U0kFpM?qtlR@|7iP)-=K|Sk9X|~Hn(yZ-w*MAj0-C;J zK*NzY5^?RN2YUK$Z%df%3rl6XJ5u=RJ~}%)SIcolJ3BiW9UW!cwr%X+zn^$KzUCaj zG)*#pdV}JvFPJJWGi_H3i?!x>#SX@R22j`T>IVYHl~0_V_)HHINjn{nkp1XGtm*4i ztJO8$nWl;F`;3i^asK>yCMPG?#61i{3{7L{;(O%BFEMLXnXB5)Ox;e{_26Qw4G4iy zzq=c1I5M9~n)~ZgweRjqrI#HS!)YM99w6P@x9Xi~nq)E=PM$o;g$ozR=kpvna%A;9 z5{cj!Zj(9p1`CA(SMwIvYs;0K<-FZ+!_R>NPyuZ1-j>X&^WulMru*|EY7KU^lbfC* z^S8IjW-|D`PZ);OYBfyLWOQ_tL?S^?PtX56HwX0aFNp|b}wnOgl1ba!`?NF+$a<9Nj!^JjlY;nRy;Te6rg+wxPZ*qF;V zUzMTC0NED(7SP;~ffZ@`@>U?_!gO_c|Nc!6wk36R-zrgCoX2-v;{BUQrBXy95p<;} zT=*Bo5B@>r=5^*Qo5|9$yjrnJ)4BS~Kn9p?JxhQM_&_MWYH;XJ$6ih+V$c0-*N)!a zM6At|5={uqt{&RA??eNdvo~<9GLGkRtza=-w%zecX}MH)|7lhIv%q`{xZH}rw^9Y- zYYZ>ZbTWE0or<3P>5if3wyySph`ug&DK(l*m8wjYmur)D#VOm~udDT7tOZ;GYOTbh z1+MNp8EquJ>F9HYrv1Dp7TdI|yDPq{JKY(JXsJrmQ#KDBAzZlW + +If you are seeing this in your browser, +PHP is not installed on your web server!!! + + +*******************************************/ + +/** + * We will perform some sanity checking here, since this file is normally loaded first when users + * first access the application. + */ + +# The index we will store our config in $_SESSION +define('APPCONFIG','appConfig'); + +define('LIBDIR',sprintf('%s/',realpath('../lib/'))); +ini_set('display_errors',1); +error_reporting(E_ALL); + +# General functions needed to proceed. +ob_start(); +if (! file_exists(LIBDIR.'functions.php')) { + if (ob_get_level()) ob_end_clean(); + die(sprintf("Fatal error: Required file '%sfunctions.php' does not exist.",LIBDIR)); +} + +if (! is_readable(LIBDIR.'functions.php')) { + if (ob_get_level()) ob_end_clean(); + die(sprintf("Cannot read the file '%sfunctions.php' its permissions may be too strict.",LIBDIR)); +} + +if (ob_get_level()) + ob_end_clean(); + +# Make sure this PHP install has pcre +if (! extension_loaded('pcre')) + die('

Your install of PHP appears to be missing PCRE support.

Please install PCRE support before using this application.
(Dont forget to restart your web server afterwards)

'); + +require LIBDIR.'functions.php'; + +# Define the path to our configuration file. +if (defined('CONFDIR')) + $app['config_file'] = CONFDIR.'config.php'; +else + $app['config_file'] = 'config.php'; + +# Make sure this PHP install has session support +if (! extension_loaded('session')) + error('

Your install of PHP appears to be missing php-session support.

Please install php-session support before using this application.
(Dont forget to restart your web server afterwards)

','error',null,true); + +# Make sure this PHP install has gettext, we use it for language translation +if (! extension_loaded('gettext')) + system_message(array( + 'title'=>_('Missing required extension'), + 'body'=>'Your install of PHP appears to be missing GETTEXT support.

GETTEXT is used for language translation.

Please install GETTEXT support before using this application.
(Dont forget to restart your web server afterwards)', + 'type'=>'error')); + +# Make sure this PHP install has all our required extensions + +/** + * Helper functions. + * Our required helper functions are defined in functions.php + */ +if (isset($app['function_files']) && is_array($app['function_files'])) + foreach ($app['function_files'] as $file_name ) { + if (! file_exists($file_name)) + error(sprintf('Fatal error: Required file "%s" does not exist.',$file_name),'error',null,true); + + if (! is_readable($file_name)) + error(sprintf('Fatal error: Cannot read the file "%s", its permissions may be too strict.',$file_name),'error',null,true); + + ob_start(); + require $file_name; + if (ob_get_level()) ob_end_clean(); + } + +# Configuration File check +if (! file_exists($app['config_file'])) { + error(sprintf(_('You need to configure %s. Edit the file "%s" to do so. An example config file is provided in "%s.example".'),app_name(),$app['config_file'],$app['config_file']),'error',null,true); + +} elseif (! is_readable($app['config_file'])) { + error(sprintf('Fatal error: Cannot read your configuration file "%s", its permissions may be too strict.',$app['config_file']),'error',null,true); +} + +# If our config file fails the sanity check, then stop now. +if (! $config = check_config($app['config_file'])) { + $www['page'] = new page(); + $www['body'] = new block(); + $www['page']->block_add('body',$www['body']); + $www['page']->display(); + exit; + +} else { + app_session_start(); + $_SESSION[APPCONFIG] = $config; +} + +if ($uri = get_request('URI','GET')) + header(sprintf('Location: cmd.php?%s',base64_decode($uri))); + +if (! ereg('^([0-9]+\.?)+',app_version())) { + if (count($_SESSION[APPCONFIG]->untested())) + system_message(array( + 'title'=>'Untested configuration paramaters', + 'body'=>sprintf('The following parameters have not been tested. If you have configured these parameters, and they are working as expected, please let the developers know, so that they can be removed from this message.
%s',join(', ',$_SESSION[APPCONFIG]->untested())), + 'type'=>'info','special'=>true)); + + $server = $_SESSION[APPCONFIG]->getServer(get_request('server_id','REQUEST')); + if (count($server->untested())) + system_message(array( + 'title'=>'Untested server configuration paramaters', + 'body'=>sprintf('The following parameters have not been tested. If you have configured these parameters, and they are working as expected, please let the developers know, so that they can be removed from this message.
%s',join(', ',$server->untested())), + 'type'=>'info','special'=>true)); +} + +include './cmd.php'; +?> diff --git a/htdocs/js/app_ajax.js b/htdocs/js/app_ajax.js new file mode 100644 index 0000000..664da5e --- /dev/null +++ b/htdocs/js/app_ajax.js @@ -0,0 +1,111 @@ +// $Header: /cvsroot/phptsmadmin/phpTSMadmin/htdocs/js/app_ajax.js,v 1.1 2008/01/14 22:13:26 wurley Exp $ + +/** + * @package leenooksApp + */ + +// current request +var http_request = null; +var http_request_success_callback = ''; +var http_request_error_callback = ''; + +// include html into a component +function includeHTML(component, html) { + if (typeof(component) != 'object' || typeof(html) != 'string') return; + component.innerHTML = html; + + var scripts = component.getElementsByTagName('script'); + if (!scripts) return; + + // load scripts + for (var i = 0; i < scripts.length; i++) { + var scriptclone = document.createElement('script'); + if (scripts[i].attributes.length > 0) { + for (var j in scripts[i].attributes) { + if (typeof(scripts[i].attributes[j]) != 'undefined' + && typeof(scripts[i].attributes[j].nodeName) != 'undefined' + && scripts[i].attributes[j].nodeValue != null + && scripts[i].attributes[j].nodeValue != '') { + scriptclone.setAttribute(scripts[i].attributes[j].nodeName, scripts[i].attributes[j].nodeValue); + } + } + } + scriptclone.text = scripts[i].text; + scripts[i].parentNode.replaceChild(scriptclone, scripts[i]); + eval(scripts[i].innerHTML); + } +} + +// callback function +function alertHttpRequest() { + if (http_request && (http_request.readyState == 4)) { + if (http_request.status == 200) { + response = http_request.responseText; + http_request = null; + //alert(response); + if (http_request_success_callback) { + eval(http_request_success_callback + '(response)'); + } + } else { + alert('There was a problem with the request.'); + cancelHttpRequest(); + } + } +} + +function cancelHttpRequest() { + if (http_request) { + http_request = null; + if (http_request_error_callback) { + eval(http_request_error_callback + '()'); + } + } +} + +// resquest +function makeGETRequest(url, parameters, successCallbackFunctionName, errorCallbackFunctionName) { + makeHttpRequest(url, parameters, 'GET', successCallbackFunctionName, errorCallbackFunctionName); +} + +function makePOSTRequest(url, parameters, successCallbackFunctionName, errorCallbackFunctionName) { + makeHttpRequest(url, parameters, 'POST', successCallbackFunctionName, errorCallbackFunctionName); +} + +function makeHttpRequest(url, parameters, meth, successCallbackFunctionName, errorCallbackFunctionName) { + cancelHttpRequest(); + + http_request_success_callback = successCallbackFunctionName; + http_request_error_callback = errorCallbackFunctionName; + + if (window.XMLHttpRequest) { // Mozilla, Safari,... + http_request = new XMLHttpRequest(); + if (http_request.overrideMimeType) { + http_request.overrideMimeType('text/html'); + } + } else if (window.ActiveXObject) { // IE + try { + http_request = new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) { + try { + http_request = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + } + } + + if (!http_request) { + alert('Cannot create XMLHTTP instance.'); + return false; + } + + http_request.onreadystatechange = window['alertHttpRequest']; + if (meth == 'GET') url = url + '?' + parameters; + http_request.open(meth, url, true); + + http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + http_request.setRequestHeader("Content-length", parameters.length); + http_request.setRequestHeader("Connection", "close"); + + if (meth == 'GET') parameters = null; + http_request.send(parameters); +} + diff --git a/htdocs/js/menu_hide.js b/htdocs/js/menu_hide.js new file mode 100644 index 0000000..8c7d4ed --- /dev/null +++ b/htdocs/js/menu_hide.js @@ -0,0 +1,18 @@ +var current; + +/** + * Toggle the displayed menu + */ +function menu_unhide(whichLayer,old) { + if (current == null) current = old; + var oldmenu = document.getElementById('MID_'+current).style; + oldmenu.display = 'none'; + + if (document.getElementById) { + // this is the way the standards work + var newmenu = document.getElementById(whichLayer).value; + var newmenu_div = document.getElementById('MID_'+newmenu).style; + newmenu_div.display = 'block'; + } + current = newmenu; +} diff --git a/htdocs/library.info.php b/htdocs/library.info.php new file mode 100644 index 0000000..adcebf1 --- /dev/null +++ b/htdocs/library.info.php @@ -0,0 +1,166 @@ +getLibraries()) { + foreach ($libraries->getLibraries() as $library) { + + printf(_('Information on libraries %s.'),$library->getName()); + echo ''; + + # Does this library support show slots? + if (isset($library->slots)) { + + printf('', + $library->getAttr('ProductId'), + $library->getAttr('Slots'), + $library->getAttr('Drives')); + + # Show the drives + foreach ($library->getDrives() as $drive) { + printf('', + $drive->getName(), + $drive->volume ? sprintf('%s-%s',$drive->status,$drive->volume) : $drive->status); + } + echo ''; + + # Library Configuration + printf('', + $library->shared); + + printf('', + ($library->autolabel ? $library->autolabel : 'NO')); + + # Volume Details + printf('', + $library->getAttr('Slots') - count($library->slots)); + + printf('', + count($library->slotVolumes(true))+count($library->slotVolumes(false))); + + printf('', + count($library->slotVolumes(false))); + + printf('', + count($library->slotVolumes(true))); + + printf('', + count($library->getLibraryEmpty())); + + printf('', + count($library->getLibraryPending())); + + printf('', + count($library->getLibraryScratch())); + + echo ''; + + # Volumes in Library. + printf('', + _('The following volumes are currently in this library:')); + echo ''; + echo ''; + + } else { + printf('', + $library->type); + + # Show the drives + foreach ($library->getDrives() as $drive) { + printf('', + $drive->getName(), + $drive->volume ? sprintf('%s-%s',$drive->status,$drive->volume) : $drive->status); + } + echo ''; + } + echo '
'. + classValue(_('This is a %s library, with %s slots and %s drives.'),'value'). + ' 
 '. + classValue(_('Drive %s (Status %s).'),'value'). + ' 
 
 '. + classValue(_('%s shared library.'),'value'). + ' 
 '. + classValue(_('%s auto label.'),'value'). + ' 
 '. + classValue(_('%s slots with NO volumes.'),'value'). + ' 
 '. + classValue(_('%s volumes physically in this library.'),'value'). + ' 
 '. + classValue(_('%s volumes NOT checked in.'),'value'). + ' 
 '. + classValue(_('%s volumes are checked into this library.'),'value'). + ' 
  '. + classValue(_('%s EMPTY volumes.'),'value'). + ' 
  '. + classValue(_('%s PENDING volumes.'),'value'). + ' 
  '. + classValue(_('%s SCRATCH volumes.'),'value'). + ' 
 
%s
'; + echo ''; + + foreach ($library->slots as $slotnum => $slot) { + if (isset($slot['barcodelabel']) && strlen($slot['barcodelabel'])) + $volname = $slot['barcodelabel']; + elseif ($volumes->inElement($library->getName(),$slotnum)) + $volname = $volumes->inElement($library->getName(),$slotnum); + else + $volname = 'No Barcode Label'; + + if ($volname && isset($volumes->volumes[$volname]->status['library']) && $volumes->volumes[$volname]->status['library'] == 'Scratch') + echo ''; + elseif ($slot['status'] == 'Full') + echo ''; + else + echo ''; + + printf('', + $slotnum, + $slot['slot'], + $volname, + isset($volumes->volumes[$volname]->stgpool) ? $volumes->volumes[$volname]->stgpool : + (isset($volumes->volumes[$volname]->dbv['type']) ? $volumes->volumes[$volname]->dbv['type'] : ' '), + + isset($volumes->volumes[$volname]->status['volume']) ? sprintf('%s/%s',$volumes->volumes[$volname]->status['volume'],$volumes->volumes[$volname]->access) : + (isset($volumes->volumes[$volname]->dbv) ? + sprintf('%s.%s.%s',$volumes->volumes[$volname]->dbv['series'], + $volumes->volumes[$volname]->dbv['operation'],$volumes->volumes[$volname]->dbv['sequence']) : ' '), + + (isset($library->slots[$slotnum]['status']) && $library->slots[$slotnum]['status'] == 'Allocated') ? + isset($volumes->volumes[$volname]->status['library']) ? $volumes->volumes[$volname]->status['library'].'/'.$volumes->volumes[$volname]->lib['owner'] : ' ' : + _('*NOT CHECKED IN*'), + + (isset($volumes->volumes[$volname]->status['volume']) && $volumes->volumes[$volname]->status['volume'] == 'PENDING') ? + tsmDate($volumes->volumes[$volname]->pending['start'],'notime') : + isset($volumes->volumes[$volname]->utilisation) ? $volumes->volumes[$volname]->utilisation.'%' : ' ', + + isset($volumes->volumes[$volname]->stgpool) ? $stgpools->getReclaim($volumes->volumes[$volname]->stgpool).'%' : 'N/A', + + isset($volumes->volumes[$volname]->reclaim) ? $volumes->volumes[$volname]->reclaim.'%' : ' ', + + isset($volumes->volumes[$volname]->last['read']) ? tsmDate($volumes->volumes[$volname]->last['read'],'notime') : ' ', + isset($volumes->volumes[$volname]->last['write']) ? tsmDate($volumes->volumes[$volname]->last['write'],'notime') : + (isset($volumes->volumes[$volname]->dbv) ? tsmDate($volumes->volumes[$volname]->dbv['date'],'notime') : ' ')); + } + + echo '
SlotBarcode/Vol NameUsageStatus/AccessLibrary AccessUtilisationReclaimLast ReadLast Write
%s%s%s%s%s%s%s%s%s
 
'. + classValue(_('This is a %s library'),'value'). + ' 
 '. + classValue(_('Drive %s (Status %s).'),'value'). + ' 
 
'; + } +} else { + echo _('No Library'); + printf('
%s
', + _('There are no automated libraries defined to this TSM server.')); +} +?> diff --git a/htdocs/login.php b/htdocs/login.php new file mode 100644 index 0000000..33789ff --- /dev/null +++ b/htdocs/login.php @@ -0,0 +1,39 @@ +_('Authenticate to server'), + 'body'=>_('You left the password blank.'), + 'type'=>'warn'), + sprintf('cmd.php?cmd=login_form&server_id=%s',get_request('server_id','REQUEST'))); + +if ($app['server']->login($user['login'],$user['password'],'user')) + system_message(array( + 'title'=>_('Authenticate to server'), + 'body'=>_('Successfully logged into server.'), + 'type'=>'info'), + sprintf('cmd.php?server_id=%s',get_request('server_id','REQUEST'))); +else + system_message(array( + 'title'=>_('Failed to Authenticate to server'), + 'body'=>_('Invalid Username or Password.'), + 'type'=>'error'), + sprintf('cmd.php?cmd=login_form&server_id=%s',get_request('server_id','REQUEST'))); +?> diff --git a/htdocs/login_form.php b/htdocs/login_form.php new file mode 100644 index 0000000..6043590 --- /dev/null +++ b/htdocs/login_form.php @@ -0,0 +1,90 @@ +%s %s',_('Authenticate to server'),$app['server']->getName()); +echo '
'; + +# Check for a secure connection +if (! isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) != 'on') { + echo '

'; + echo ''; + printf('%s: %s.', + _('You are not using \'https\'. Web browser will transmit login information in clear text.'), + _('Warning'),_('This web connection is unencrypted')); + echo ''; + echo '
'; +} +echo '
'; + +# Login form. +echo '
'; +echo ''; +printf('',$app['server']->getIndex()); + +if (get_request('redirect','GET',false,false)) + printf('',rawurlencode(get_request('redirect','GET'))); + +echo '
'; +echo ''; + +printf('', + $app['server']->getValue('login','auth_text') ? $app['server']->getValue('login','auth_text') : + ($app['server']->getValue('login','attr') == 'dn' ? _('Login DN') : $_SESSION[APPCONFIG]->getFriendlyName($app['server']->getValue('login','attr')))); + +printf('', + $app['server']->getValue('login','attr',false) == 'dn' ? $app['server']->getValue('login','bind_id') : ''); + +echo ''; +printf('',_('Password')); +echo ''; +echo ''; + +if (method_exists($app['server'],'registerEnabled') && $app['server']->registerEnabled()) { + printf('','cmd.php?cmd=register_form',IMGDIR.'add.png',_('Register')); + echo ''; +} + +# If Anon bind allowed, then disable the form if the user choose to bind anonymously. +if ($app['server']->isAnonBindAllowed()) + printf('', + _('Anonymous')); + +printf('', + _('Authenticate')); + +echo '
%s:
 
%s:
 
 %s
 
%s
'; +echo '
'; +echo '
'; + +echo ''; + +if ($app['server']->isAnonBindAllowed() ) { +?> + + diff --git a/htdocs/logout.php b/htdocs/logout.php new file mode 100644 index 0000000..38148ba --- /dev/null +++ b/htdocs/logout.php @@ -0,0 +1,28 @@ +logout('user')) + system_message(array( + 'title'=>_('Authenticate to server'), + 'body'=>_('Successfully logged out of server.'), + 'type'=>'info'), + 'index.php'); +else + system_message(array( + 'title'=>_('Failed to Logout of server'), + 'body'=>_('Please report this error to the admins.'), + 'type'=>'error'), + 'index.php'); +?> diff --git a/htdocs/node.detail.php b/htdocs/node.detail.php new file mode 100644 index 0000000..6f089a8 --- /dev/null +++ b/htdocs/node.detail.php @@ -0,0 +1,297 @@ +getFSOccupancy($type)) + return null; + + $blockBody = ''; + switch ($type) { + case 'Bkup' : + $blockBody .= sprintf('', + 3+count($node->getStoragePools())); + break; + + case 'Arch' : + $blockBody .= sprintf('', + 3+count($node->getStoragePools())); + break; + + default: + return null; + } + + $blockBody .= ''; + $blockBody .= ''; + $blockBody .= ''; + $blockBody .= ''; + # List out the Storage Pools + foreach ($node->getStoragePools() as $stgp) + $blockBody .= sprintf('',$stgp); + $blockBody .= ''; + + $counter = 0; + foreach ($node->getFSOccupancy($type) as $fs => $object) { + $blockBody .= sprintf('',($counter++%2==0?'even':'odd')); + $blockBody .= sprintf('', + trim($object->type) ? $object->type : 'Unknown File System',$object->getName()); + + $blockBody .= sprintf('', + isset($object->backup['start']) ? tsmdate($object->backup['start'],'nomsec') : 'NO START', + isset($object->backup['end']) ? tsmdate($object->backup['end'],'nomsec') : 'NO END', + isset($object->backup['end']) ? tsmdate($object->backup['end'],'notime') : 'NO DATE', + $object->getName()); + + $blockBody .= sprintf('', + isset($object->capacity) ? number_format($object->capacity) : '?', + isset($object->util) ? $object->util : '?', + number_format($object->capacity*$object->util/100)); + + foreach ($node->getStoragePools() as $stgp) { + if (isset($object->occupancy[$type][$stgp])) + $blockBody .= sprintf('', + number_format($object->occupancy[$type][$stgp]['num'],0), + number_format($object->occupancy[$type][$stgp]['physical'],2), + count($object->getVolumeUsage($stgp)) ? sprintf(' (%s)',count($object->getVolumeUsage($stgp))) : ''); + else + $blockBody .= ''; + } + $blockBody .= ''; + } + $blockBody .= '
Backup Space Utilisation
Archive Space Utilisation
File SystemLast BackupLast Size%s
%s%s %s MB%s%s 
'; + $blockBody .= "\n\n"; + + return $blockBody; +} + +/** + * This function returns an HTML table of the VOLUMES used by a $node of $type + */ +function showVols($node,$type='Bkup') { + if (! $node->getVolumes($type)) + return null; + + $blockBody = ''; + switch ($type) { + case 'Bkup' : + $blockBody .= sprintf(''); + break; + + case 'Arch' : + $blockBody .= sprintf(''); + break; + + default: + return null; + } + + $blockBody .= ''; + $blockBody .= ''; + $blockBody .= ''; + $blockBody .= ''; + $blockBody .= ''; + + $last['stg'] = ''; + $last['fs'] = ''; + $counter = 0; + foreach ($node->getVolumes($type) as $fs => $object) { + foreach ($object->volume[$type] as $stgp => $volumes) { + foreach ($volumes as $vol => $volume) { + $blockBody .= sprintf('', + ($counter%2==0?'even':'odd'), + $object->getName() == $last['fs'] ? '' : $object->getName(), + $stgp == $last['stg'] ? '' : $stgp, + sprintf('%s (%s/%s%s)',$vol, + $volume->status['volume'],$volume->access, + trim($volume->location) ? '-'.$volume->location : '')); + + $last['stg'] = $stgp; + $last['fs'] = $object->getName(); + } + $counter++; + } + } + $blockBody .= '
Backup Volume Utilisation
Archive Volume Utilisation
File SystemStorage PoolVolume
%s%s%s
'; + $blockBody .= "\n\n"; + + return $blockBody; +} + +/** + * This function returns an HTML table of the VOLUMES by PRIMARY/COPY used by a $node of $type + */ +function showAllvols($node,$type='Bkup') { + if (! $node->getVolumes($type)) + return null; + + $blockBody = ''; + switch ($type) { + case 'Bkup' : + $blockBody .= sprintf(''); + break; + + case 'Arch' : + $blockBody .= sprintf(''); + break; + + default: + return null; + } + + $counter = 0; + foreach ($node->getPrimaryVolumes($type) as $volumename => $volume) { + $blockBody .= sprintf('', + ($counter%2==0?'even':'odd'), + ! $counter ? 'Primary' : ' ', + sprintf('%s (%s/%s%s)',$volume->getName(), + $volume->status['volume'],$volume->access, + trim($volume->location) ? '-'.$volume->location : '')); + $counter++; + } + + $counter = 0; + foreach ($node->getCopyVolumes($type) as $volumename => $volume) { + $blockBody .= sprintf('', + ($counter%2==0?'even':'odd'), + ! $counter ? 'Copy' : ' ', + sprintf('%s (%s/%s%s)',$volume->getName(), + $volume->status['volume'],$volume->access, + trim($volume->location) ? '-'.$volume->location : '')); + $counter++; + } + + $blockBody .= '
Backup Volume Utilisation
Archive Volume Utilisation
%s%s
%s%s
'; + $blockBody .= "\n\n"; + + return $blockBody; +} + +# Defaults +$nodes = objectCache('nodes'); +$mgmtclass = objectCache('mgmtclasses'); + +# List of Clients. +printf(_('Select NODE on %s'),$app['server']->getValue('server','name')); + +echo '
'; +printf('',get_request('cmd','REQUEST')); +printf('',$app['server']->getIndex()); + +echo ''; +echo ''; +echo '
'; +echo "\n"; + +if (isset($_REQUEST['NODE'])) { + echo '
'; + echo ''; + printf('', + $_REQUEST['NODE'],$app['server']->getValue('server','name')); + + $node = $nodes->nodes[$_REQUEST['NODE']]; + if ($node->url) + printf('', + $node->url,$node->hostname); + else + printf('',$node->hostname ? $node->hostname : ' '); + + printf('',$node->contact ? $node->contact : ' '); + + printf('', + $node->os,$node->level['os']); + + printf('', + $node->level['tsm_ver'],$node->level['tsm_rel'],$node->level['tsm_lvl'],$node->level['tsm_slv']); + + echo ''; + printf('', + tsmDate($node->time['registered'],'nosec')); + printf('', + tsmDate($node->time['lastacc'],'nosec'), + (time()-strtotime(tsmDate($node->time['lastacc'],'nosec')))/86400); + printf('', + tsmDate($node->time['pwset'],'nosec'), + (time()-strtotime(tsmDate($node->time['pwset'],'nosec')))/86400); + printf('', + tsmDate($node->passwd['expiry'],'nosec') ? tsmDate($node->passwd['expiry'],'nosec') : ' '); + printf('', + $node->passwd['invalid'], + $node->locked ? 'LOCKED' : 'NOT Locked'); + + echo ''; + printf('',$node->cloptset ? $node->cloptset : ' '); + printf('', + $node->compression ? 'YES' : 'NO'); + printf('',$node->txngroupmax ? $node->txngroupmax : ' '); + printf('', + $node->delete['arch'] ? 'YES' : 'NO'); + printf('', + $node->delete['back'] ? 'YES' : 'NO'); + printf('', + $node->mp['keep'] ? 'YES' : 'NO',$node->mp['max']); + + echo ''; + printf('',$node->group ? $node->group : _('Not Set')); + echo ''; + printf('',$node->domain); + + # Show the MGMTClasses that apply to this node. + printf(''); + foreach ($mgmtclass->getMgmtClasses($node->domain,'Bkup') as $object) { + if (is_object($object)) + printf('', + $object->version['EXISTS'],$object->retain['EXTRA'],$object->version['DELETED'],$object->retain['ONLY'],$object->frequency,$object->getName(), + $object->isDefaultMgmtClass() ? ' (Default)' : '', + $object->getStoragePool() ? $object->getStoragePool()->getName() : sprintf('(%s)',_('Storage Pool Doesnt Exist'))); + } + + printf(''); + foreach ($mgmtclass->getMgmtClasses($node->domain,'Arch') as $object) { + if (is_object($object)) + printf('', + $object->retain['DAYS'],$object->getName(), + $object->isDefaultMgmtClass() ? ' (Default)' : '', + $object->getStoragePool() ? $object->getStoragePool()->getName() : sprintf('(%s)',_('Storage Pool Doesnt Exist'))); + } + + echo ''; + + if ($node->getFileSystems()) { + printf('',showFSDetails($node,'Bkup')); + echo ''; + printf('',showFSDetails($node,'Arch')); + + } else { + echo ''; + } + + if (count($node->getVolumeUsage())) { + echo ''; + printf('',showAllVols($node,'Bkup')); + echo ''; + printf('',showAllVols($node,'Arch')); + + echo ''; + printf('',showVols($node,'Bkup')); + echo ''; + printf('',showVols($node,'Arch')); + } + + echo '

NODE detail for %s on %s

System Name%s
System Name%s
Contact%s
OS%s (%s)
TSM Client Version%s.%s.%s.%s

Access Information

Registered%s
Last Accessed%s (%2.0f days ago)
Last Password Change%s (%2.0f days ago)
Password Expiry%s
Invalid Password Count%s (%s)

Configuration Options

Option Set%s
Compression %s
TXN Group Max%s
Can Delete Archives%s
Can Delete Backups%s
Keep Mount Points%s (%s)

Storage Configuration

Colloc Group Name%s
 
Backup Domain%s
 Available Backup Management Classes
 %s%sStorage Pool: %s
 Available Archive Management Classes
 %s%sStorage Pool: %s

Storage Utilisation

%s
 
%s
TSM does not have any data for this host.

Volume Utilisation

%s
 
%s

Volume Utilisation by File System

%s
 
%s
'; +} +?> diff --git a/htdocs/node.occupancy.php b/htdocs/node.occupancy.php new file mode 100644 index 0000000..53ce88d --- /dev/null +++ b/htdocs/node.occupancy.php @@ -0,0 +1,92 @@ +getValue('server','name')); +$blockBody['graph'] = ''; + +$blockTitle['occ'] = sprintf(_('Client Occupancy Summary on %s'),$app['server']->getValue('server','name')); +$blockBody['occ'] = '
'; + +$counter = 0; +$grandtotal = 0; + +# Work out our stgpools with data in them. +$stgpooltotal = array(); +$blockBody['occ'] .= sprintf('', + 'Node','Last Acc','Type'); +foreach ($nodes->getNodes() as $node) { + foreach ($node->getStoragePools() as $stgp) { + if (! isset($stgpooltotal[$stgp])) { + $blockBody['occ'] .= sprintf('',$stgp); + $stgpooltotal[$stgp] = 0; + } + } +} +$blockBody['occ'] .= sprintf('','Total'); + +# Now show the nodes having data in the stgpools. +$history = array(); +foreach ($nodes->getNodes() as $node) { + foreach ($node->getOccupancy() as $type => $stgpools) { + $nodetotal = 0; + $blockBody['occ'] .= sprintf('', + ($counter++%2==0?'even':'odd'), + $node->getName(), + tsmDate($node->time['lastacc'],'notime'), + $type); + + foreach ($stgpooltotal as $stgpool => $details) { + if (isset($stgpools[$stgpool])) { + $blockBody['occ'] .= sprintf('',number_format($stgpools[$stgpool]['physical'])); + $nodetotal += $stgpools[$stgpool]['physical']; + $stgpooltotal[$stgpool] += $stgpools[$stgpool]['physical']; + + } else + $blockBody['occ'] .= sprintf('',' '); + } + + $blockBody['occ'] .= sprintf('',number_format($nodetotal)); + $grandtotal += $nodetotal; + + $oldtime = strtotime(tsmDate($node->time['lastacc'],'sec')); + $diff = round((time() - $oldtime)/86400); + @$history[$diff] += $nodetotal; + } +} + +# Store our history information for when we call the graphing php. +$_SESSION['graph']['occupancy'] = $history; +ksort($_SESSION['graph']['occupancy']); + +# Show our dbbackup graph +$blockBody['graph'] .= ''; +$blockBody['graph'] .= ''; +$blockBody['graph'] .= sprintf('', + $app['server']->getIndex()); +$blockBody['graph'] .= '
%s%s%s%s%s
%s%s%s%s%s %s
'; +$blockBody['graph'] .= sprintf('','Days Since Access','Storage Used'); +$counter = 0; +foreach ($_SESSION['graph']['occupancy'] as $age => $size) { + $blockBody['graph'] .= sprintf('', + ($counter++%2==0?'even':'odd'),$age,number_format($size),sprintf('%2.1f%%',$size/$grandtotal*100)); +} +$blockBody['graph'] .= '
%s%s%%
%s%s%s
 
'; + +$blockBody['occ'] .= sprintf('%s','TOTAL'); +foreach ($stgpooltotal as $stgpool => $total) + $blockBody['occ'] .= sprintf('%s',number_format($total)); +$blockBody['occ'] .= sprintf(' %s',number_format($grandtotal)); +$blockBody['occ'] .= ''; + +# End +render_page($blockTitle,$blockBody); +?> diff --git a/htdocs/node.summary.php b/htdocs/node.summary.php new file mode 100644 index 0000000..24d1f42 --- /dev/null +++ b/htdocs/node.summary.php @@ -0,0 +1,126 @@ +GetActlogBackupSummary($yesterDate,$todayDate); + +# Database Summary Information +$blockTitle['sched'] = sprintf(_('Client Schedule Activity on %s'),$app['server']->getValue('server','name')); +$blockBody['sched'] = ''; + +$blockBody['sched'] .= ''; + +if ($graph) { + $_SESSION['graph']['backupevent'] = $graph; + $blockBody['sched'] .= sprintf('', + $app['server']->getIndex()); +} +$blockBody['sched'] .= '
'; +$blockBody['sched'] .= sprintf('', + 'Client','Schedule','Sched Status','Sched Start'); + +$counter = 0; +$events = $app['server']->GetEvents($yesterDate,$todayDate,'ACTUAL_START'); + +if (count($events['detail'])) { + foreach ($events['detail'] as $index => $tsmEvent) { + $node = $nodes->getNode($tsmEvent['NODE_NAME']); + + $blockBody['sched'] .= sprintf('', + $tsmEvent['STATUS'] == 'Failed' ? 'highlight' : ($counter++%2==0?'even':'odd'), + $node->os, + $node->level['tsm_ver'],$node->level['tsm_rel'],$node->level['tsm_lvl'],$node->level['tsm_slv'], + $tsmEvent['NODE_NAME'], + $tsmEvent['SCHEDULE_NAME'], + $tsmEvent['RESULT'],$tsmEvent['STATUS'], + tsmDate($tsmEvent['ACTUAL_START'])); + + $key = $tsmEvent['STATUS'].' '.$tsmEvent['RESULT']; + @$graph['data'][$tsmEvent['SCHEDULE_NAME']][$key]++; + @$graph['legend'][$key]++; + } + +} else { + $graph = false; + +} +$blockBody['sched'] .= '
%s%s%s%s
%s%s%s%s
'; + +$blockTitle['backup'] = sprintf(_('Client Backup Session Summary on %s'),$app['server']->getValue('server','name')); +$blockBody['backup'] = ''; +$blockBody['backup'] .= sprintf('', + 'Client','Sched','Date','Type','Session','Inspected','Backed Up','Failed','Time','MB','Agg Rate Kb/s','Compressed'); + +$counter = 0; +$summary = $summaryInfo->getSummary($yesterDate,$todayDate); +if (! count($summary)) + $blockBody['backup'] .= sprintf('','No summary'); +else +foreach ($summary as $tsmSession) { + if (! in_array($tsmSession['ACTIVITY'],array('BACKUP','ARCHIVE'))) continue; + + # Summary Information + $client_summary = sprintf('Update: %s, Rebound: %s, Delete: %s, Expire: %s.', + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Update']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Update'] : '-'), + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Rebound']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Rebound'] : '-'), + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Delete']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Delete'] : '-'), + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Expire']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Expire'] : '-')); + + $blockBody['backup'] .= sprintf('', + ($counter++%2==0?'even':'odd'), + $tsmSession['ENTITY'], + $tsmSession['SCHEDULE_NAME'], + tsmDate($tsmSession['START_TIME'],'daytime'), + $client_summary, + $tsmSession['ACTIVITY'], + $tsmSession['NUMBER'], + number_format($tsmSession['EXAMINED']), + $tsmSession['AFFECTED'], + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Failed']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Failed'] : '-'), + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['ProcTime']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['ProcTime'] : '-'), + sprintf('%3.2f',$tsmSession['BYTES']/1024/1024), + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['AggRate']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['AggRate'] : '-'), + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Compress']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['Compress'] : '-')); +} +$blockBody['backup'] .= '
%s%s%s%s%s%s%s%s%s%s%s%s
%s
%s%s%s%s%s%s%s%s%s%s%s%s
'; + +$counter = 0; +$blockTitle['restore'] = sprintf(_('Client Restore Session Summary on %s'),$app['server']->getValue('server','name')); +$blockBody['restore'] = ''; +$blockBody['restore'] .= sprintf('', + 'Client','Time','Session','Type','Restored','Failed','Media W','Time','MB','Agg Rate Kb/s'); + +foreach ($summary as $tsmSession) { + if (! in_array($tsmSession['ACTIVITY'],array('RESTORE','RETRIEVE'))) continue; + + $blockBody['restore'] .= sprintf('', + ($counter++%2==0?'even':'odd'), + $tsmSession['ENTITY'], + tsmDate($tsmSession['START_TIME'],'nosec'), + $tsmSession['NUMBER'], + $tsmSession['ACTIVITY'], + $tsmSession['AFFECTED'], + $tsmSession['FAILED'], + $tsmSession['MEDIAW'], + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['ProcTime']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['ProcTime'] : '-'), + sprintf('%3.1fMB',$tsmSession['BYTES']/1024/1024), + (isset($tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['AggRate']) ? $tsmBackupSummary['detail'][$tsmSession['ENTITY']][$tsmSession['NUMBER']]['AggRate'] : '-'), + $tsmSession['SCHEDULE_NAME']); +} +$blockBody['restore'] .= '
%s%s%s%s%s%s%s%s%s%s
%s%s%s%s%s%s%s%s%s%s
'; +# End +render_page($blockTitle,$blockBody); +?> diff --git a/htdocs/node.thruput.php b/htdocs/node.thruput.php new file mode 100644 index 0000000..fef5c05 --- /dev/null +++ b/htdocs/node.thruput.php @@ -0,0 +1,21 @@ +query("SELECT substr(varchar(END_TIME),1,10) as ADATE,ENTITY,ACTIVITY,cast(float(sum(BYTES))/1024/1024/1024 as dec(8,2)) as GB from SUMMARY where ACTIVITY='BACKUP' and END_TIME>CURRENT_TIMESTAMP-(30)DAY and (ACTIVITY in ('ARCHIVE','BACKUP','RESTORE','RETRIEVE')) group by END_TIME,ENTITY,ACTIVITY order by ADATE desc,ENTITY ",null,false,false); + +echo ''; +printf('', + 'heading','Date','Node','Type','GB'); +$counter = 0; +foreach ($thruput as $details) { + printf('', + $counter++%2 ? 'even' : 'odd', + $details['ADATE'],$details['ENTITY'],$details['ACTIVITY'],$details['GB']); +} +echo '
%s%s%s%s
%s%s%s%s
'; +?> diff --git a/htdocs/purge_cache.php b/htdocs/purge_cache.php new file mode 100644 index 0000000..7746d5b --- /dev/null +++ b/htdocs/purge_cache.php @@ -0,0 +1,35 @@ +isCommandAvailable('purge')) { + error(sprintf('%s%s %s',_('This operation is not permitted by the configuration'),_(':'),_('purge')),'warn'); + return; +} + +$purge_session_keys = array('cache'); + +$size = 0; +foreach ($purge_session_keys as $key) { + if (isset($_SESSION[$key])) { + $size += strlen(serialize($_SESSION[$key])); + unset($_SESSION[$key]); + } +} + +if (! $size) + $body = _('No cache to purge.'); +else + $body = sprintf(_('Purged %s bytes of cache.'),number_format($size)); + +system_message(array( + 'title'=>_('Purge cache'), + 'body'=>$body, + 'type'=>'info'), + 'index.php'); +?> diff --git a/htdocs/schedule.gantt.php b/htdocs/schedule.gantt.php new file mode 100644 index 0000000..f242dfc --- /dev/null +++ b/htdocs/schedule.gantt.php @@ -0,0 +1,17 @@ +getValue('server','name')); +$blockBody['gantt'] = '
'; +$blockBody['gantt'] .= sprintf('','image.schedule.gantt.php',$app['server']->getIndex()) ; +$blockBody['gantt'] .= '
'; + +# End +render_page($blockTitle,$blockBody); +?> diff --git a/htdocs/server.db.php b/htdocs/server.db.php new file mode 100644 index 0000000..b99f4ae --- /dev/null +++ b/htdocs/server.db.php @@ -0,0 +1,145 @@ +GetStatusDetail('SERVER_NAME'), + $app['server']->GetStatusDetail('PLATFORM'), + $app['server']->GetStatusDetail('VERSION'), + $app['server']->GetStatusDetail('RELEASE'), + $app['server']->GetStatusDetail('LEVEL'), + $app['server']->GetStatusDetail('SUBLEVEL')); + +$blockBody['db'] = ''; + +$blockBody['db'] .= sprintf('',_('Database Summary')); +$blockBody['db'] .= ''; + +# Show our dbbackup graph +$blockBody['db'] .= sprintf('', + $app['server']->getIndex()); +$blockBody['db'] .= '
%s
'; + +$blockBody['db'] .= sprintf('', + $app['server']->GetDBDetail('PHYSICAL_VOLUMES'),$app['server']->GetDBDetail('AVAIL_SPACE_MB'), + $app['server']->GetDBDetail('PCT_UTILIZED')); + +foreach ($app['server']->GetDBDetail('dbvols') as $key) { + $blockBody['db'] .= sprintf('', + $key['COPY1_NAME'],$key['AVAIL_SPACE_MB'],$key['FREE_SPACE_MB'],$key['COPY1_STATUS']); + + if ($key['COPY2_NAME']) + $blockBody['db'] .= sprintf('', + $key['COPY2_NAME'],$key['COPY2_STATUS']); + + if ($key['COPY3_NAME']) + $blockBody['db'] .= sprintf('', + $key['COPY3_NAME'],$key['COPY3_STATUS']); +} + +$blockBody['db'] .= ''; + +$blockBody['db'] .= sprintf('', + $app['server']->GetLogDetail('PHYSICAL_VOLUMES'),$app['server']->GetLogDetail('AVAIL_SPACE_MB'), + $app['server']->GetLogDetail('PCT_UTILIZED')); + +foreach ($app['server']->GetLogDetail('logvols') as $key) { + $blockBody['db'] .= sprintf('', + $key['COPY1_NAME'],$key['AVAIL_SPACE_MB'],$key['FREE_SPACE_MB'],$key['COPY1_STATUS']); + + if ($key['COPY2_NAME']) + $blockBody['db'] .= sprintf('', + $key['COPY2_NAME'],$key['COPY2_STATUS']); + + if ($key['COPY3_NAME']) + $blockBody['db'] .= sprintf('', + $key['COPY3_NAME'],$key['COPY3_STATUS']); +} +$blockBody['db'] .= ''; + +$blockBody['db'] .= sprintf('', + $app['server']->GetStatusDetail('LOGMODE')); +$blockBody['db'] .= ''; + + +$blockBody['db'] .= sprintf('', + $app['server']->GetDBDetail('CACHE_HIT_PCT'),$app['server']->GetDBDetail('CACHE_WAIT_PCT')); + +if ($trigger = $app['server']->GetDBBackupDetail('trigger')) { + $blockBody['db'] .= ''; + + $blockBody['db'] .= sprintf('', + sprintf('%s',$app['server']->getIndex(),$trigger['INCRDEVCLASS'],$trigger['INCRDEVCLASS']), + $trigger['LOGFULLPCT']); + $blockBody['db'] .= sprintf('', + $trigger['NUMICREMENTAL'], + sprintf('%s',$app['server']->getIndex(),$trigger['DEVCLASS'],$trigger['DEVCLASS'])); +} + +$blockBody['db'] .= '
'.classValue(_('%s DB volumes totaling %sMB (%s%% utilsed).'),'value').'
 '.classValue(_('%s (%sMB) (%sMB Free) (%s).'),'value').'
 '.classValue(_('%s (%s).'),'value').'
 '.classValue(_('%s (%s).'),'value').'
 
'.classValue(_('%s LOG volumes totaling %sMB (%s%% utilsed).'),'value').'
 '.classValue(_('%s (%sMB) (%sMB Free) (%s).'),'value').'
 '.classValue(_('%s (%s).'),'value').'
 '.classValue(_('%s (%s).'),'value').'
 
'.classValue(_('Database REDO log mode %s.'),'value').'
 
'.classValue(_('%s%% database cache hit rate (%s%% cache wait).'),'value').'
 
'.classValue(_('TSM will automatically backup the database to %s when the logs reach %s%% full.'),'value').'
'.classValue(_('After %s INCREMENTAL backups, a full backup will be performed to %s.'),'value').'
'; + +# Database Backup Information +$blockTitle['backup'] = sprintf(_('Database backup information for %s'),$app['server']->getValue('server','name')); +$blockBody['backup'] = ''; + +if ($app['server']->GetDBDetail('LAST_BACKUP_DATE')) { + $blockBody['backup'] .= sprintf('', + $app['server']->GetDBDetail('BACKUP_CHG_MB'),$app['server']->GetDBDetail('BACKUP_CHG_PCT'), + tsmDate($app['server']->GetDBDetail('LAST_BACKUP_DATE'))); + + $blockBody['backup'] .= ''; + + ## Show DB Vols + $blockBody['backup'] .= sprintf('', + tsmDate($app['server']->GetDBBackupDetail('count'),'nosec'), + tsmDate($app['server']->GetDBBackupDetail('first'),'nosec'), + tsmDate($app['server']->GetDBBackupDetail('last'),'nosec')); + + $blockBody['backup'] .= sprintf('', + 'SEQ #','DATE','TYPE','DEVICE','VOLUME','LOCATION','STATUS'); + + $counter = 0; + $lastseries = 0; + foreach ($app['server']->GetDBBackupDetail('vols') as $volname => $voldetails) { + if (! is_array($voldetails)) + continue; + + if ($lastseries != $voldetails['BACKUP_SERIES']) { + $lastseries = $voldetails['BACKUP_SERIES']; + $counter ++; + } + + $blockBody['backup'] .= sprintf('', + ($voldetails['STATUS'] == 'InValid' ? 'class="shadow"' : ($counter%2==0?'class="even"':'class="odd"')), + sprintf('%s-%s-%s',$voldetails['BACKUP_SERIES'],$voldetails['BACKUP_OPERATION'],$voldetails['VOLUME_SEQ']), + tsmDate($voldetails['DATE_TIME'],'nosec'), + tsmBackupType($voldetails['TYPE']), + $voldetails['DEVCLASS'], + $volname, + $app['server']->GetVolLocation($volname), + $voldetails['STATUS']); + } + + $blockBody['backup'] .= ''; + +} else { + $blockBody['backup'] .= sprintf('',_('It looks like you have NOT yet run a TSM backup.')); +} + + +if ($app['server']->GetDBDetail('BACKUP_RUNNING') == 'YES') + $blockBody['backup'] .= sprintf('',_('Database backup IS currently running.')); +else + $blockBody['backup'] .= sprintf('',_('Database backup is NOT currently running.')); + +$blockBody['backup'] .= ''; +$blockBody['backup'] .= '
'.classValue(_('%sMB (%s%%) has changed since the last backup on %s'),'value').'
 
'.classValue(_('%s backups between %s and %s available for TSM DB restore.'),'value').'
 %s%s%s%s%s%s%s
 %s%s%s%s%s%s%s
 
%s
%s
%s
 
'; + +# End +render_page($blockTitle,$blockBody); +?> diff --git a/htdocs/server.stats.php b/htdocs/server.stats.php new file mode 100644 index 0000000..41c47a2 --- /dev/null +++ b/htdocs/server.stats.php @@ -0,0 +1,103 @@ +'; + $blockBody['form'] .= sprintf('',get_request('cmd','REQUEST')); + $blockBody['form'] .= '
'; + $blockBody['form'] .= ''; + $blockBody['form'] .= sprintf('',$app['server']->getIndex()); + $blockBody['form'] .= ''; + +} else { + $mediawait = $summaryInfo->mediaActions(); + $graph['mediawait']['total'] = 0; + $graph['mediawait']['count'] = 0; + $graph['thruput']['total'] = 0; + $graph['thruput']['count'] = 0; + $mode = array(); + + foreach ($_REQUEST['mediaactions'] as $action) { + if (isset($mediawait[$action])) + foreach ($mediawait[$action] as $index => $detail) { + $start = strtotime(tsmDate($detail['START_TIME'],'nomsec')); + $end = strtotime(tsmDate($detail['END_TIME'],'nosec')); + $graph['mediawait']['data'][$detail['ACTIVITY']][$end] = $detail['MEDIAW']; + + $graph['mediawait']['total'] += $detail['MEDIAW']; + $graph['mediawait']['count']++; + @$mode[$detail['MEDIAW']]++; + + $graph['thruput']['data'][$detail['ACTIVITY']][$end] = $detail['BYTES']/1024/($end+.1-$start); + } + + # Drop the first and last entry + ksort($mode); + array_pop($mode); + $total = 0; + $counter = 0; + foreach ($mode as $index => $value) { + if ($counter) { + $total += $index * $value; + } + $counter++; + } + if ($counter) + $graph['mediawait']['mode'] = $total/$counter; + + # Save Session + $_SESSION['graph'] = $graph; + + # Media Wait Graph + $blockTitle['mediawait'] = sprintf(_('Migration Information for %s (%s) %s.%s.%s.%s'), + $app['server']->GetStatusDetail('SERVER_NAME'), + $app['server']->GetStatusDetail('PLATFORM'), + $app['server']->GetStatusDetail('VERSION'), + $app['server']->GetStatusDetail('RELEASE'), + $app['server']->GetStatusDetail('LEVEL'), + $app['server']->GetStatusDetail('SUBLEVEL')); + + $blockBody['mediawait'] = ''; + $blockBody['mediawait'] .= sprintf('',_('Media Wait Graph')); + $blockBody['mediawait'] .= ''; + $blockBody['mediawait'] .= sprintf('', + $app['server']->getIndex()); + $blockBody['mediawait'] .= '
%s
'; + $blockBody['mediawait'] .= ''; + $blockBody['mediawait'] .= '
 
'; + + # Thruput Graph + $blockTitle['thruput'] = sprintf(_('Server Thruput for %s (%s) %s.%s.%s.%s'), + $app['server']->GetStatusDetail('SERVER_NAME'), + $app['server']->GetStatusDetail('PLATFORM'), + $app['server']->GetStatusDetail('VERSION'), + $app['server']->GetStatusDetail('RELEASE'), + $app['server']->GetStatusDetail('LEVEL'), + $app['server']->GetStatusDetail('SUBLEVEL')); + + $blockBody['thruput'] = ''; + $blockBody['thruput'] .= sprintf('',_('Server Thruput Graph')); + $blockBody['thruput'] .= ''; + $blockBody['thruput'] .= sprintf('', + $app['server']->getIndex()); + $blockBody['thruput'] .= '
%s
'; + $blockBody['thruput'] .= ''; + $blockBody['thruput'] .= '
 
'; + } +} + +# End +render_page($blockTitle,$blockBody); +?> diff --git a/htdocs/show_cache.php b/htdocs/show_cache.php new file mode 100644 index 0000000..70a0781 --- /dev/null +++ b/htdocs/show_cache.php @@ -0,0 +1,90 @@ +getValue('appearance','hide_debug_info')) { + echo '
    '; + foreach (array_keys($_SESSION) as $key) { + if (($key == 'cache') && is_array($_SESSION[$key])) + foreach (array_keys($_SESSION['cache']) as $server) { + foreach (array_keys($_SESSION['cache'][$server]) as $x) { + $index = sprintf('%s:%s',$server,$x); + + printf('
  • %s
  • ', + $key.$index,$key,$index,$key.'.'.$index,$key.$index,$key.$index); + } + } + else + printf('
  • %s
  • ', + $key,$key,$key,$key); + } + echo '
'; +} +?> + + diff --git a/htdocs/summary.gantt.php b/htdocs/summary.gantt.php new file mode 100644 index 0000000..11de7ad --- /dev/null +++ b/htdocs/summary.gantt.php @@ -0,0 +1,189 @@ +getValue('server','name')); + +$blockBody['node'] = '
'; +$blockBody['node'] .= ''; +$blockBody['node'] .= sprintf('',$app['server']->getIndex()); + +$blockBody['node'] .= 'From '; +$blockBody['node'] .= ''; +$blockBody['node'] .= ' '; +$blockBody['node'] .= 'To '; +$blockBody['node'] .= ''; +$blockBody['node'] .= ''; +$blockBody['node'] .= '
'; + +if ($form['from'] && $form['to']) { +initJPGraph(true); + +# A new graph with automatic size +$graph = new GanttGraph(0,0,'auto'); + +# A new activity on row '0' +$item = 0; +foreach ($summaryInfo->getSummary($form['from'],$form['to']) as $tsmActivity) { + $tsmCaption = ''; + $tsmCSIM = ''; + $waiting = 0; + $timestart = preg_replace('/(.*:.*):.*/','$1',$tsmActivity['START_TIME']); + $timeend = preg_replace('/(.*:.*):.*/','$1',$tsmActivity['END_TIME']); + $timeseconds = strtotime(preg_replace('/([0-9])\..*$/','$1',$tsmActivity['END_TIME'])) - + strtotime(preg_replace('/([0-9])\..*$/','$1',$tsmActivity['START_TIME'])); + if ($timeseconds) { + $waiting = $tsmActivity['MEDIAW']/$timeseconds; + } + + # It seems sometimes, the MEDIAW time can be larger than the activity time? + if ($waiting > 1) $waiting = 1; + + $priority = 10; + switch ($tsmActivity['ACTIVITY']) { + case('TAPE MOUNT'): + $summary = $tsmActivity['ACTIVITY'].' ('.$tsmActivity['DRIVE_NAME'].')'; + $tsmCSIM = sprintf('%s (%s)',$tsmActivity['VOLUME_NAME'],$tsmActivity['LAST_USE']); + $priority = 1; + break; + case('STGPOOL BACKUP'): + $summary = $tsmActivity['ACTIVITY'].' ('.$tsmActivity['ENTITY'].')'; + $tsmCSIM = sprintf('%3d MB. %ss Media wait',$tsmActivity['BYTES']/1024/1024,$tsmActivity['MEDIAW']); + $priority = 4; + break; + case('ARCHIVE'): + $summary = $tsmActivity['ACTIVITY'].' ('.$tsmActivity['ENTITY'].')'; + $tsmCSIM = sprintf('%3d MB',$tsmActivity['BYTES']/1024/1024); + $priority = 3; + break; + case('BACKUP'): + $summary = $tsmActivity['ACTIVITY'].' ('.$tsmActivity['ENTITY'].')'; + $tsmCSIM = sprintf('%3d MB',$tsmActivity['BYTES']/1024/1024); + $priority = 2; + break; + case('RESTORE'): + $summary = $tsmActivity['ACTIVITY'].' ('.$tsmActivity['ENTITY'].')'; + $tsmCSIM = sprintf('%3d MB. %ss Media wait',$tsmActivity['BYTES']/1024/1024,$tsmActivity['MEDIAW']); + $priority = 2; + break; + case('FULL_DBBACKUP'): + $summary = $tsmActivity['ACTIVITY']; + $tsmCSIM = sprintf('%3d MB. %ss Media wait',$tsmActivity['BYTES']/1024/1024,$tsmActivity['MEDIAW']); + break; + case('RECLAMATION'): + $summary = $tsmActivity['ACTIVITY'].' ('.preg_replace('/^(.*)\s+\(.*\)$/','$1',$tsmActivity['ENTITY']).')'; + $tsmCSIM = sprintf('%3d MB. %ss Media wait',$tsmActivity['BYTES']/1024/1024,$tsmActivity['MEDIAW']); + $priority = 4; + break; + case('MIGRATION'): + $summary = $tsmActivity['ACTIVITY'].' ('.$tsmActivity['ENTITY'].')'; + $tsmCSIM = sprintf('%3d MB. %ss Media wait',$tsmActivity['BYTES']/1024/1024,$tsmActivity['MEDIAW']); + $priority = 4; + break; + case('MOVE NODEDATA'): + $summary = $tsmActivity['ACTIVITY'].' ('.$tsmActivity['ENTITY'].')'; + $tsmCSIM = sprintf('%3d MB. %ss Media wait',$tsmActivity['BYTES']/1024/1024,$tsmActivity['MEDIAW']); + $priority = 4; + break; + case('EXPIRATION'): + $summary = 'EXPIRATION PROCESSING'; + $tsmCaption = sprintf('%ss',$tsmActivity['AFFECTED']); + default: $summary = $tsmActivity['ACTIVITY']; + } + + $lineitems[$summary][$item]['wait'] = $waiting; + $lineitems[$summary][$item]['summary'] = $summary; + $lineitems[$summary][$item]['start'] = $timestart; + $lineitems[$summary][$item]['end'] = $timeend; + $lineitems[$summary][$item]['csim'] = $tsmCSIM; + $lineitems[$summary][$item]['caption'] = $tsmCaption; + $lineitems[$summary]['priority'] = $priority; + $lineitems[$summary]['summary'] = $summary; + $item++; +} + +if ($lineitems) { + masort($lineitems,'priority,summary',1); + + $item=0; + foreach ($lineitems as $lineitem => $linedetails) { + + # Put a gap between our priority items. + if ($item && ($lastPriority <> $linedetails['priority'])) + $item++; + + foreach ($linedetails as $eventdetails) { + if (! is_array($eventdetails)) continue; + $activity = new GanttBar($item,$eventdetails['summary'],$eventdetails['start'],$eventdetails['end']); + $activity->progress->Set($eventdetails['wait']); + $activity->caption ->Set($eventdetails['caption']); + $activity->SetCSIMTarget('#',($eventdetails['csim'] ? $eventdetails['csim'] : 'NoCSIM')); + $activity->title->SetCSIMTarget('#',($eventdetails['csim'] ? $eventdetails['csim'] : 'NoCSIM')); + + $graph->Add($activity); + } + $item++; + $lastPriority = $linedetails['priority']; + } + + $graph->SetMarginColor('#eeeeff'); + $graph->SetFrame(true,'#eeeeff',0); + + // We want to display day, hour and minute scales + $graph->ShowHeaders(GANTT_HDAY | GANTT_HHOUR | GANTT_HMIN); + $graph->hgrid->Show(); + $graph->hgrid->SetRowFillColor('darkred@0.85'); + + // Setup hour format + $graph->scale->hour->SetIntervall(1); + $graph->scale->hour->SetStyle(HOURSTYLE_H24); + $graph->scale->minute->SetIntervall(30); + + $graph->scale->dividerh->SetWeight(3); + $graph->scale->dividerh->SetColor('navy'); + + // Display the Gantt chart + $imageFile = sprintf('%s/gantt.summary.%s.png',realpath($_SESSION[APPCONFIG]->getValue('image','path')),$app['server']->getIndex()); + $imageHTML = sprintf('%sgantt.summary.%s.png',$_SESSION[APPCONFIG]->getValue('image','pathurl'),$app['server']->getIndex()); + $graph->Stroke($imageFile); + +} else { + # @todo: Nice message about no data. +} + +$blockTitle['gantt'] = sprintf(_('Activity Summary for server %s'),$app['server']->getValue('server','name')); +$blockBody['gantt'] = '
'; +$blockBody['gantt'] .= $graph->GetHTMLImageMap('gantt.summary'); +$blockBody['gantt'] .= sprintf('',$imageHTML,$app['server']->getIndex()) ; +$blockBody['gantt'] .= '
'; +} + +# End +render_page($blockTitle,$blockBody); +?> diff --git a/htdocs/volume.info.php b/htdocs/volume.info.php new file mode 100644 index 0000000..456178c --- /dev/null +++ b/htdocs/volume.info.php @@ -0,0 +1,160 @@ +'; + +$blockBody['volinfo'] .= ''; + +# The following COPY VOLUMES in the library can be checked out. +if (count($volumes->libCopyVolumes(true))) { + $blockBody['volinfo'] .= sprintf('',_('The following COPY pool volumes can be checked out of the library(s).')); + $counter = 0; + foreach ($volumes->libCopyVolumes(true) as $volume) { + $blockBody['volinfo'] .= sprintf('', + $counter++%2 ? 'even' : 'odd', + $volume->lib['name'], + isset($volume->location) ? $volume->location : 'InLIB', + $volume->getName(), + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); + } + $blockBody['volinfo'] .= ''; +} + +# Non-Read/Write Volumes that need to be fixed. +if (count($volumes->nonReadWriteVolumes())) { + $blockBody['volinfo'] .= sprintf('',_('NON READ/WRITE volumes that need to be addressed.')); + $counter = 0; + foreach ($volumes->nonReadWriteVolumes() as $volume) { + $blockBody['volinfo'] .= sprintf('', + $counter++%2 ? 'even' : 'odd', + isset($volume->lib['name']) ? $volume->lib['name'] : ' ', + isset($volume->location) ? $volume->location : 'InLIB', + $volume->name, + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); + } + $blockBody['volinfo'] .= ''; +} + +# Non-Read/Write Volumes that need to be fixed. +if (count($volumes->reclaimVolumes())) { + $blockBody['volinfo'] .= sprintf('',_('The following volumes should be RECLAIMed (automatically).')); + $counter = 0; + foreach ($volumes->reclaimVolumes() as $volume) { + $blockBody['volinfo'] .= sprintf('', + $counter++%2 ? 'even' : 'odd', + isset($volume->lib['name']) ? $volume->lib['name'] : ' ', + isset($volume->location) ? $volume->location : 'InLIB', + $volume->getName(), + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); + } + $blockBody['volinfo'] .= ''; +} + +# Following volumes have had errors. +if (count($volumes->errorVolumes())) { + $blockBody['volinfo'] .= sprintf('',_('The following volumes have had ERRORS.')); + $counter = 0; + foreach ($volumes->errorVolumes() as $volume) { + $blockBody['volinfo'] .= sprintf('', + $counter++%2 ? 'even' : 'odd', + isset($volume->lib['name']) ? $volume->lib['name'] : ' ', + isset($volume->location) ? $volume->location : 'InLIB', + $volume->name, + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),$volume->error['read'], + tsmDate($volume->last['write'],'notime'),$volume->error['write']); + } + $blockBody['volinfo'] .= ''; +} + +# Following volumes have not been read/written to for a long time. +if (count($volumes->staleVolumes($app['server']->getValue('system','tapeage')))) { + $blockBody['volinfo'] .= sprintf('',sprintf(_('The following volumes have NOT been read/written to for %s days.'),$app['server']->getValue('system','tapeage'))); + $counter = 0; + foreach ($volumes->staleVolumes($app['server']->getValue('system','tapeage')) as $volume) { + $blockBody['volinfo'] .= sprintf('', + $counter++%2 ? 'even' : 'odd', + isset($volume->lib['name']) ? $volume->lib['name'] : ' ', + isset($volume->location) ? $volume->location : 'InLIB', + $volume->getName(), + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); + } + $blockBody['volinfo'] .= ''; +} + +# Following volumes are empty or pending. +if (count($volumes->pendingVolumes()) || count($volumes->emptyVolumes())) { + $blockBody['volinfo'] .= sprintf('',_('The following volumes are either EMPTY or PENDING.')); + $counter = 0; + foreach ($volumes->pendingVolumes() as $volume) { + $blockBody['volinfo'] .= sprintf('', + $counter++%2 ? 'even' : 'odd', + isset($volume->lib['name']) ? $volume->lib['name'] : ' ', + isset($volume->location) ? $volume->location : 'InLIB', + $volume->name, + $volume->stgpool, + tsmDate($volume->pending['start'],'notime'),tsmDate($volume->pending['end'],'notime'), + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); + } + $counter = 0; + foreach ($volumes->emptyVolumes() as $volume) { + $blockBody['volinfo'] .= sprintf('', + $counter++%2 ? 'even' : 'odd', + isset($volume->lib['name']) ? $volume->lib['name'] : ' ', + isset($volume->location) ? $volume->location : 'InLIB', + $volume->getName(), + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); + } +} +$blockBody['volinfo'] .= '
LibrarySlotBarcode/Vol NameUsageStatus/AccessLibrary AccessUtilisationReclaimLast ReadLast Write
%s
%s%s%s%s%s/%s%s%s%%%s%%%s%s
 
%s
%s%s%s%s%s/%s%s%s%%%s%%%s%s
 
%s
%s%s%s%s%s/%s%s%s%%%s%%%s%s
 
%s
%s%s%s%s%s/%s%s%s%%%s%%%s (%s)%s (%s)
 
%s
%s%s%s%s%s/%s%s%s%%%s%%%s%s
 
%s
%s%s%s%s%s/%s%s%s%%%s%%%s%s
%s%s%s%s%s/%s%s%s%%%s%%%s%s
'; + +# End +render_page($blockTitle,$blockBody); +?> diff --git a/htdocs/volume.inventory.php b/htdocs/volume.inventory.php new file mode 100644 index 0000000..c536e20 --- /dev/null +++ b/htdocs/volume.inventory.php @@ -0,0 +1,95 @@ +'; +$blockBody['innotci'] .= 'LibrarySlotBarcode/Vol NameUsageStatus/AccessLibrary AccessUtilisationReclaimLast ReadLast Write'; + +$blockBody['innotci'] .= sprintf('%s',_('The following volumes are in a library, but NOT checked in.')); +$counter = 0; +foreach ($libraries->libVolumes(false) as $library => $volumes) { + foreach ($volumes as $element => $volume) { + $blockBody['innotci'] .= sprintf('%s%s%s%s%s/%s%s%s%%%s%%%s%s', + $counter++%2 ? 'even' : 'odd', + isset($volume->lib['name']) ? $volume->lib['name'] : 'Unknown LIB', + isset($volume->location) ? $volume->location : $volume->lib['slot'], + $volume->name, + $volume->stgpool, + $volume->status['volume'], + $volume->access, + isset($volume->lib['access']) ? $volume->lib['access'] : 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); + } +} +$blockBody['innotci'] .= ' '; + +# The following PRIMARY VOLUMES not in the library. +$blockBody['innotci'] .= sprintf('%s',_('The following PRIMARY pool volumes are NOT checked in a library.')); +$counter = 0; +foreach ($libvolumes->primaryNotInLib() as $volume) { + $blockBody['innotci'] .= sprintf('%s%s%s%s%s/%s%s%s%%%s%%%s%s', + $counter++%2 ? 'even' : 'odd', + 'N/A', + isset($volume->location) ? $volume->location : 'InLIB', + $volume->getName(), + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); +} +$blockBody['innotci'] .= ' '; + +# The following COPY VOLUMES in the library can be checked out. +$blockBody['innotci'] .= sprintf('%s',_('The following COPY pool volumes can be checked out of the library(s).')); +$counter = 0; +foreach ($libvolumes->libCopyVolumes(true) as $volume) { + $blockBody['innotci'] .= sprintf('%s%s%s%s%s/%s%s%s%%%s%%%s%s', + $counter++%2 ? 'even' : 'odd', + $volume->lib['name'], + isset($volume->location) ? $volume->location : 'InLIB', + $volume->getName(), + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); +} +$blockBody['innotci'] .= ' '; + +# The following PRIMARY VOLUMES not in the library. +$blockBody['innotci'] .= sprintf('%s',_('The following COPY pool volumes are not checked in a library.')); +$counter = 0; +foreach ($libvolumes->libCopyVolumes(false) as $volume) { + $blockBody['innotci'] .= sprintf('%s%s%s%s%s/%s%s%s%%%s%%%s%s', + $counter++%2 ? 'even' : 'odd', + 'N/A', + isset($volume->location) ? $volume->location : 'InLIB', + $volume->getName(), + $volume->stgpool, + $volume->status['volume'], + $volume->access, + 'N/A', + $volume->utilisation, + $volume->reclaim, + tsmDate($volume->last['read'],'notime'),tsmDate($volume->last['write'],'notime')); +} +$blockBody['innotci'] .= ''; + +# End +render_page($blockTitle,$blockBody); +?> diff --git a/index.php b/index.php new file mode 100644 index 0000000..3d8a872 --- /dev/null +++ b/index.php @@ -0,0 +1,11 @@ + diff --git a/lib/common.php b/lib/common.php new file mode 100644 index 0000000..1072f71 --- /dev/null +++ b/lib/common.php @@ -0,0 +1,283 @@ +CheckCustom(); +} + +# Check for safe mode. +if (ini_get('safe_mode') && ! get_request('cmd','GET')) + system_message(array( + 'title'=>_('PHP Safe Mode'), + 'body'=>_('You have PHP Safe Mode enabled. This application may work unexpectedly in Safe Mode.'), + 'type'=>'info')); + +# Set our timezone, if it is specified in config.php +if ($_SESSION[APPCONFIG]->getValue('appearance','timezone')) + date_default_timezone_set($_SESSION[APPCONFIG]->getValue('appearance','timezone')); + +# If we are here, $_SESSION is set - so enabled DEBUGing if it has been configured. +if (($_SESSION[APPCONFIG]->getValue('debug','syslog') || $_SESSION[APPCONFIG]->getValue('debug','file')) + && $_SESSION[APPCONFIG]->getValue('debug','level')) + define('DEBUG_ENABLED',1); +else + define('DEBUG_ENABLED',0); + +if (DEBUG_ENABLED) + debug_log('Application (%s) initialised and starting with (%s).',1,__FILE__,__LINE__,__METHOD__, + app_version(),$_REQUEST); + +# Set our PHP timelimit. +if ($_SESSION[APPCONFIG]->getValue('session','timelimit') && ! ini_get('safe_mode')) + set_time_limit($_SESSION[APPCONFIG]->getValue('session','timelimit')); + +# If debug mode is set, increase the time_limit, since we probably need it. +if (DEBUG_ENABLED && $_SESSION[APPCONFIG]->getValue('session','timelimit') && ! ini_get('safe_mode')) + set_time_limit($_SESSION[APPCONFIG]->getValue('session','timelimit') * 5); + +/** + * Language configuration. Auto or specified? + * Shall we attempt to auto-determine the language? + */ +# If we are in safe mode, and LANG is not in the allowed vars, display an error. +if (ini_get('safe_mode') && ! in_array('LANG',explode(',',ini_get('safe_mode_allowed_env_vars')))) + error('You are running in SAFE_MODE, but LANG is not in the safe_mode_allowed_env_vars. Please add LANG to safe_mode_allowed_env_vars','error',true,false); + +$app['language'] = $_SESSION[APPCONFIG]->getValue('appearance','language'); + +if ($app['language'] == 'auto') { + + # Make sure their browser correctly reports language. If not, skip this. + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + + # Get the languages which are spetcified in the HTTP header + $app['lang_http'] = preg_split ('/[;,]+/',$_SERVER['HTTP_ACCEPT_LANGUAGE']); + foreach ($app['lang_http'] as $key => $value) { + if (substr($value,0,2) == 'q=') { + unset($app['lang_http'][$key]); + continue; + } + + $value = preg_split('/[-]+/',$value); + if (sizeof($value) == 2) + $app['lang_http'][$key] = strtolower($value[0]).'_'.strtoupper($value[1]); + else + $app['lang_http'][$key] = auto_lang(strtolower($value[0])); + } + + $app['lang_http'] = array_unique($app['lang_http']); + + foreach ($app['lang_http'] as $lang) { + $app['language_dir'] = LANGDIR.$lang; + + if ((substr($lang,0,2) == 'en') || + (file_exists($app['language_dir']) && is_readable($app['language_dir']))) { + + # Set language + putenv('LANG='.$lang); # e.g. LANG=de_DE + $lang .= '.UTF-8'; + setlocale(LC_ALL,$lang); # set LC_ALL to de_DE + bindtextdomain('messages',LANGDIR); + bind_textdomain_codeset('messages','UTF-8'); + textdomain('messages'); + header('Content-type: text/html; charset=UTF-8',true); + break; + } + } + #todo Generate an error if language doesnt exist. + } + +} else { + # Grab the language file configured in config.php + #todo Generate an error if language doesnt exist. + if ($app['language'] != null) { + if (strcmp($app['language'],'english') == 0) + $app['language'] = 'en_GB'; + + # Set language + putenv('LANG='.$app['language']); # e.g. LANG=de_DE + $app['language'] .= '.UTF-8'; + setlocale(LC_ALL,$app['language']); # set LC_ALL to de_DE + bindtextdomain('messages',LANGDIR); + bind_textdomain_codeset('messages','UTF-8'); + textdomain('messages'); + header('Content-type: text/html; charset=UTF-8',true); + } +} + +/** + * Strip slashes from GET, POST, and COOKIE variables if this + * PHP install is configured to automatically addslashes() + */ +if (get_magic_quotes_gpc() && (! isset($slashes_stripped) || ! $slashes_stripped)) { + array_stripslashes($_REQUEST); + array_stripslashes($_GET); + array_stripslashes($_POST); + array_stripslashes($_COOKIE); + $slashes_stripped = true; +} + +# Create our application repository variable. +$app['server'] = $_SESSION[APPCONFIG]->getServer(get_request('server_id','REQUEST')); + +/** + * At this point we have read all our additional function PHP files and our configuration. + * If we are using hooks, run the session_init hook. + */ +if (function_exists('run_hook')) + run_hook('post_session_init',array()); +?> diff --git a/lib/config_custom.php b/lib/config_custom.php new file mode 100644 index 0000000..253591a --- /dev/null +++ b/lib/config_custom.php @@ -0,0 +1,75 @@ +configDefinition('command','devclassinfo',array( + 'summary'=>'Device Class Info', + 'desc'=>'This will show detailed information on your devclasses.', + 'default'=>'devclass.info')); +$config->configDefinition('command','help',array( + 'summary'=>'Help', + 'desc'=>'Help Pages', + 'default'=>'help')); +$config->configDefinition('command','libraryinfo',array( + 'summary'=>'Library Info', + 'desc'=>'Information on Automated Tape Libraries.', + 'default'=>'library.info')); +$config->configDefinition('command','nodedetail',array( + 'summary'=>'Node Detail', + 'desc'=>'Detail of Node Storage Usage.', + 'default'=>'node.detail')); +$config->configDefinition('command','nodeoccupancy',array( + 'summary'=>'Node Occupancy', + 'desc'=>'Summary of Node Occupancy.', + 'default'=>'node.occupancy')); +$config->configDefinition('command','nodesummary',array( + 'summary'=>'Node Summary', + 'desc'=>'Summary of Node activities.', + 'default'=>'node.summary')); +$config->configDefinition('command','nodethruput',array( + 'summary'=>'Node Traffic', + 'desc'=>'This will show recent node traffic.', + 'default'=>'node.thruput')); +$config->configDefinition('command','schedulegantt',array( + 'summary'=>'Schedule Gantt', + 'desc'=>'Gantt view of todays schedules.', + 'default'=>'schedule.gantt')); +$config->configDefinition('command','summarygantt',array( + 'summary'=>'Activity Summary Gantt', + 'desc'=>'Gantt view of todays activities.', + 'default'=>'summary.gantt')); +$config->configDefinition('command','serverdb',array( + 'summary'=>'Server DB Information', + 'desc'=>'Database Information for TSM Server.', + 'default'=>'server.db')); +$config->configDefinition('command','serverstats',array( + 'summary'=>'Server Stats', + 'desc'=>'TSM Server Performance Stats.', + 'default'=>'server.stats')); +$config->configDefinition('command','volumeinfo',array( + 'summary'=>'Volume Status', + 'desc'=>'Status Information on Sequential Volumes.', + 'default'=>'volume.info')); +$config->configDefinition('command','volumeinventory',array( + 'summary'=>'Volume Inventory', + 'desc'=>'Information on Volumes.', + 'default'=>'volume.inventory')); + +$config->configDefinition('lib','jpgraph',array( + 'desc'=>'Path to JPGraph', + 'default'=>LIBDIR.'JpGraph')); +$config->configDefinition('image','path',array( + 'desc'=>'Path to generated images', + 'default'=>HTDOCDIR.'tmp')); +$config->configDefinition('image','pathurl',array( + 'desc'=>'URL Path to generated images', + 'default'=>'tmp/')); +?> diff --git a/lib/config_default.php b/lib/config_default.php new file mode 100644 index 0000000..7233194 --- /dev/null +++ b/lib/config_default.php @@ -0,0 +1,337 @@ +custom = new stdClass; + $this->default = new stdClass; + + $this->default->app['name'] = array( + 'desc'=>'Application Name', + 'default'=>'phpTSMadmin'); + + $this->default->appearance['compress'] = array( + 'desc'=>'Compress Output', + 'default'=>false); + + $this->default->appearance['control_icons'] = array( + 'desc'=>'Show the control as icons or text', + 'default'=>false); + + $this->default->appearance['menu'] = array( + 'desc'=>'Class name which inherits from Menu class and implements the draw() method', + 'default'=>'menu_html'); + + /** Language + * The language setting. If you set this to 'auto', This application will + * attempt to determine your language automatically. Otherwise, set + * this to your applicable language in xx_XX format. + */ + $this->default->appearance['language'] = array( + 'desc'=>'Language', + 'default'=>'auto'); + + $this->default->appearance['page_title'] = array( + 'desc'=>'Change the page title to this text', + 'default'=>''); + + $this->default->appearance['stylesheet'] = array( + 'desc'=>'Style sheet to use', + 'default'=>'style.css'); + + $this->default->appearance['timezone'] = array( + 'desc'=>'Define our timezone, if not defined in php.ini', + 'default'=>null); + + $this->default->appearance['logged_in_chars'] = array( + 'desc'=>'Number of chars of loggin name to show', + 'default'=>15); + + /** Caching + */ + $this->default->cache['menu'] = array( + 'desc'=>'Cache the Menu', + 'default'=>false); + + $this->default->cache['time'] = array( + 'desc'=>'How long to cache our TSM queries', + 'default'=>600); + + /** Commands + * Define which high level commands are available. + */ + $this->default->commands['all'] = array( + 'desc'=>'Define command availability', + 'default'=> array( + 'home' => true, + 'login' => true, + 'logout' => true, + 'purge' => true, + 'showcache' => true, + 'register' => true + )); + + /** Debug Attributes + * Attributes that control debugging + */ + $this->default->debug['level'] = array( + 'desc'=>'Debug level verbosity', + 'default'=>0); + + $this->default->debug['syslog'] = array( + 'desc'=>'Whether to send debug messages to syslog', + 'default'=>false); + + $this->default->debug['file'] = array( + 'desc'=>'Name of file to send debug output to', + 'default'=>null); + + $this->default->debug['addr'] = array( + 'desc'=>'IP address of client to provide debugging info.', + 'default'=>null); + + $this->default->debug['append'] = array( + 'desc'=>'Whether to append to the debug file, or create it fresh each time', + 'default'=>true); + + $this->default->debug['show_cache'] = array( + 'desc'=>'Show the tool that shows debugging information.', + 'default'=>false); + + /** Session Attributes + * Session related attributes. + */ + $this->default->session['blowfish'] = array( + 'desc'=>'Blowfish key to encrypt cookie details', + 'default'=>null); + + $this->default->session['memorylimit'] = array( + 'desc'=>'Set the PHP memorylimit warning threshold.', + 'default'=>24); + + $this->default->session['timelimit'] = array( + 'desc'=>'Set the PHP timelimit.', + 'default'=>30); + + /** Cookie Time + * If you used auth_type 'form' in the servers list, you can adjust how long the cookie will last + * (default is 0 seconds, which expires when you close the browser) + */ + $this->default->session['cookie_time'] = array( + 'desc'=>'Time in seconds for the life of cookies', + 'default'=>0); + + /** Session Menu + */ + $this->default->menu['session'] = array( + 'desc'=>'Menu items when logged in.', + 'default'=>array( + 'info'=>array('php'=>'info.php','icon'=>'info.png','text'=>'Server Info') + )); + + /** Available Commands + */ + } + + /** + * Access the configuration, taking into account the defaults and the customisations + */ + private function getConfigArray($usecache=true) { + static $CACHE = array(); + + if ($usecache && count($CACHE)) + return $CACHE; + + foreach ($this->default as $key => $vals) + $CACHE[$key] = $vals; + + foreach ($this->custom as $key => $vals) + foreach ($vals as $index => $val) + $CACHE[$key][$index]['value'] = $val; + + return $CACHE; + } + + /** + * Get a configuration value. + */ + public function getValue($key,$index,$fatal=true) { + $config = $this->getConfigArray(); + + if (! isset($config[$key])) + if ($fatal) + error(sprintf('A call was made in [%s] to getValue requesting [%s] that isnt predefined.', + basename($_SERVER['PHP_SELF']),$key),'error',null,true); + else + return null; + + if (! isset($config[$key][$index])) + if ($fatal) + error(sprintf('Requesting an index [%s] in key [%s] that isnt predefined.',$index,$key),'error',null,true); + else + return null; + + return isset($config[$key][$index]['value']) ? $config[$key][$index]['value'] : $config[$key][$index]['default']; + } + + /** + * Return the untested config items + */ + public function untested() { + $result = array(); + + foreach ($this->default as $option => $details) + foreach ($details as $param => $values) + if (isset($values['untested']) && $values['untested']) + array_push($result,sprintf('%s.%s',$option,$param)); + + return $result; + } + + /** + * Function to check and warn about any unusual defined variables. + */ + public function CheckCustom() { + if (isset($this->custom)) { + foreach ($this->custom as $masterkey => $masterdetails) { + + if (isset($this->default->$masterkey)) { + + if (! is_array($masterdetails)) + error(sprintf('Error in configuration file, [%s] should be an ARRAY.',$masterdetails),'error',null,true); + + foreach ($masterdetails as $key => $value) { + # Test that the key is correct. + if (! in_array($key,array_keys($this->default->$masterkey))) + error(sprintf('Error in configuration file, [%s] has not been defined as a configurable variable.',$key),'error',null,true); + + # Test if its should be an array or not. + if (is_array($this->default->{$masterkey}[$key]['default']) && ! is_array($value)) + error(sprintf('Error in configuration file, %s[\'%s\'] SHOULD be an array of values.',$masterkey,$key),'error',null,true); + + if (! is_array($this->default->{$masterkey}[$key]['default']) && is_array($value)) + error(sprintf('Error in configuration file, %s[\'%s\'] should NOT be an array of values.',$masterkey,$key),'error',null,true); + } + + } else { + error(sprintf('Error in configuration file, [%s] has not been defined as a MASTER configurable variable.',$masterkey),'error',null,true); + } + } + } + } + + /** + * Get a list of available commands. + */ + public function getCommandList() { + $config = $this->getConfigArray(false); + + masort($config['command'],'summary'); + + if (isset($config['command']) && is_array($config['command'])) + return $config['command']; + else + return array(); + } + + /** + * The parameter number is variable. + * For example : isCommandAvailable('search','simple_search') + */ + public function isCommandAvailable($index='all') { + $a = func_get_args(); + + if (! in_array($index,array('all','script'))) + $index = 'all'; + else + array_shift($a); + + if (count($a) == 1 && is_array($a[0])) + $a = $a[0]; + $i = 0; + + # Command availability list + $cmd = $this->getValue('commands',$index); + + # Search for the command + while ($i < count($a)) { + if (! is_array($cmd)) + return $cmd; + + if (! isset($cmd[$a[$i]])) + return false; + + $cmd = $cmd[$a[$i]]; + $i++; + } + + # If this is a leaf command, return its availability + if (! is_array($cmd)) + return $cmd; + + # Else the command is available, if one of its sub-command is available + $a[] = ''; + foreach ($cmd as $c => $v) { + $a[$i] = $c; + if ($this->isCommandAvailable($a)) + return true; + } + return false; + } + + public function configDefinition($key,$index,$config) { + if (! is_array($config) || ! array_key_exists('desc',$config) || ! array_key_exists('default',$config)) + return; + + if (isset($this->default->$key)) + $definition = $this->default->$key; + + $definition[$index] = $config; + $this->default->$key = $definition; + } + + public function setServers($servers) { + $this->servers = $servers; + } + + public function getServer($index=null) { + return $this->servers->Instance($index); + } + + /** + * Return a list of our servers + * @param boolean $visible - Only return visible servers + */ + public function getServerList($visible=true) { + return $this->servers->getServerList($visible); + } +} +?> diff --git a/lib/ds.php b/lib/ds.php new file mode 100644 index 0000000..076f962 --- /dev/null +++ b/lib/ds.php @@ -0,0 +1,501 @@ +', any custom extra connection to ds. + */ + abstract public function login($user=null,$pass=null,$method=null); + + /** + * Query the datasource + */ + abstract public function query($query,$method,$index=null,$debug=false); + + /** + * Return error details from previous operation + */ + abstract protected function getErrorMessage(); + abstract protected function getErrorNum(); + + /** + * Functions that set and verify object configuration details + */ + public function setDefaults($defaults) { + foreach ($defaults as $key => $details) + foreach ($details as $setting => $value) + $this->default->{$key}[$setting] = $value; + } + + public function isDefaultKey($key) { + return isset($this->default->$key); + } + + public function isDefaultSetting($key,$setting) { + return array_key_exists($setting,$this->default->{$key}); + } + + /** + * Return a configuration value + */ + public function getValue($key,$setting,$fatal=true) { + if (isset($this->custom->{$key}[$setting])) + return $this->custom->{$key}[$setting]; + + elseif (isset($this->default->{$key}[$setting]) && array_key_exists('default',$this->default->{$key}[$setting])) + return $this->default->{$key}[$setting]['default']; + + else { + if ($fatal) + debug_dump_backtrace("Error trying to get a non-existant value ($key,$setting)",1); + else + return null; + } + } + + /** + * Set a configuration value + */ + public function setValue($key,$setting,$value) { + if (isset($this->custom->{$key}[$setting])) + system_message(array( + 'title'=>_('Configuration setting already defined.'), + 'body'=>sprintf('A call has been made to reset a configuration value (%s,%s,%s)', + $key,$setting,$value), + 'type'=>'info')); + + $this->custom->{$key}[$setting] = $value; + } + + /** + * Return the untested config items + */ + public function untested() { + $result = array(); + + foreach ($this->default as $option => $details) + foreach ($details as $param => $values) + if (isset($values['untested']) && $values['untested']) + array_push($result,sprintf('%s.%s',$option,$param)); + + return $result; + } + + /** + * Get the name of this datastore + */ + public function getName() { + return $this->getValue('server','name'); + } + + /** + * Functions that enable login and logout of the application + */ + /** + * Return the authentication type for this object + */ + public function getAuthType() { + switch ($this->getValue('login','auth_type')) { + case 'config' : + case 'session' : + return $this->getValue('login','auth_type'); + + default : + die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__, + $this->getValue('login','auth_type'))); + } + } + + /** + * Get the login name of the user logged into this datastore's connection method + */ + public function getLogin($method=null) { + $method = $this->getMethod($method); + + switch ($this->getAuthType()) { + case 'config' : + return $this->getValue('login','bind_id'); + + case 'session' : + if (! isset($_SESSION['USER'][$this->index][$method]['name'])) + return null; + + return $_SESSION['USER'][$this->index][$method]['name']; + + default : + die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$this->getAuthType())); + } + } + + /** + * Set the login details of the user logged into this datastore's connection method + */ + protected function setLogin($user,$pass,$method=null) { + $method = $this->getMethod($method); + + switch ($this->getAuthType()) { + case 'config' : + case 'session' : + $_SESSION['USER'][$this->index][$method]['name'] = $user; + $_SESSION['USER'][$this->index][$method]['pass'] = $pass; + return true; + + default : + die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$this->getAuthType())); + } + } + + /** + * Get the login password of the user logged into this datastore's connection method + */ + protected function getPassword($method=null) { + $method = $this->getMethod($method); + + switch ($this->getAuthType()) { + case 'config' : + return $this->getValue('login','bind_pass'); + + case 'session' : + return (isset($_SESSION['USER'][$this->index][$method]['pass'])) ? $_SESSION['USER'][$this->index][$method]['pass'] : null; + + default : + die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$this->getAuthType())); + } + } + + /** + * Return if this datastore's connection method has been logged into + */ + public function isLoggedIn($method=null) { + $method = $this->getMethod($method); + + return is_null($this->getLogin($method)) ? false : true; + } + + /** + * Logout of this datastore's connection method + */ + public function logout($method) { + $method = $this->getMethod($method); + + switch ($this->getAuthType()) { + case 'config' : + return true; + + case 'session' : + if (isset($_SESSION['USER'][$this->index][$method])) + unset($_SESSION['USER'][$this->index][$method]); + + return true; + + default : + die(sprintf('Error: %s hasnt been configured for auth_type %s',__METHOD__,$this->getAuthType())); + } + } + + /** + * Functions that return the condition of the datasource + */ + public function isVisible() { + return $this->getValue('server','visible'); + } + + public function isReadOnly() { + return $this->getValue('server','read_only'); + } + + public function getIndex() { + return $this->index; + } + + /** + * Work out which connection method to use. + * If a method is passed, then it will be passed back. If no method is passed, then we'll + * check to see if the user is logged in. If they are, then 'user' is used, otherwise + * 'anon' is used. + * + * @param int Server ID + * @return string Connection Method + */ + protected function getMethod($method=null) { + # Immediately return if method is set. + if (! is_null($method)) + return $method; + + if ($this->isLoggedIn('user')) + return 'user'; + else + return 'anon'; + } + + /** + * Get a attribute value from a query + */ + public function getAttrValue($query,$attr,$method=null,$debug=false) { + $result = $this->query($query,$method,$attr,$debug); + + if ($debug) + debug_dump(array('query'=>$query,'result'=>$result)); + + if (count($result) > 1) + system_message(array( + 'title'=>_('Invalid call to function.'), + 'body'=>sprintf('%s should have return 0 or 1 value, but return %s (%s)
(%s)', + __METHOD__,count($result),serialize($result),serialize($query)), + 'type'=>'info')); + else + foreach ($result as $key => $value) + return $value; + } +} + +/** + * The list of database sources + * + * @package leenooksApp + * @subpackage DataStore + */ +class Datastore { + # Out DS index id + private $index; + # List of all the objects + private $objects = array(); + # Default settings + private $default; + + public function __construct() { + $this->default = new StdClass; + + $this->default->server['id'] = array( + 'desc'=>'Server ID', + 'default'=>null); + + $this->default->server['name'] = array( + 'desc'=>'Server name', + 'default'=>null); + + # Connectivity Info + $this->default->server['host'] = array( + 'desc'=>'Host Name', + 'default'=>'127.0.0.1'); + + $this->default->server['port'] = array( + 'desc'=>'Port Number', + 'default'=>null); + + # Read or write only access + $this->default->server['read_only'] = array( + 'desc'=>'Server is in READ ONLY mode', + 'default'=>false); + + $this->default->server['visible'] = array( + 'desc'=>'Whether this server is visible', + 'default'=>true); + + # Authentication Information + $this->default->login['auth_type'] = array( + 'desc'=>'Authentication Type', + 'default'=>'session'); + +/* + /* ID to login to this application, this assumes that there is + * application authentication on top of authentication required to + * access the data source ** + $this->default->login['auth_id'] = array( + 'desc'=>'User Login ID to login to this DS', + 'default'=>null); + + $this->default->login['auth_pass'] = array( + 'desc'=>'User Login Password to login to this DS', + 'default'=>null); +*/ + + $this->default->login['auth_text'] = array( + 'desc'=>'Text to show at the login prompt', + 'default'=>'User Name'); + + $this->default->login['bind_id'] = array( + 'desc'=>'User Login ID to bind to this DS', + 'default'=>null); + + $this->default->login['bind_pass'] = array( + 'desc'=>'User Login Password to bind to this DS', + 'default'=>null); + + $this->default->login['timeout'] = array( + 'desc'=>'Session timout in seconds', + 'default'=>session_cache_expire()-1); + + # Location to custom pages + $this->default->custom['pages_prefix'] = array( + 'desc'=>'Prefix name for custom pages', + 'default'=>null); + } + + /** + * Create a new database object + */ + public function newServer($type) { + if (class_exists($type)) { + $this->index = count($this->objects)+1; + $this->objects[$this->index] = new $type($this->index); + + $this->objects[$this->index]->setDefaults($this->default); + return $this->index; + + } else { + printf('ERROR: Class [%s] doesnt exist',$type); + die(); + } + } + + /** + * Set values for a database object. + */ + public function setValue($key,$setting,$value) { + if (defined('DEBUG_ENABLED') && (DEBUG_ENABLED)) + debug_log('Entered with (%s,%s,%s)',3,__FILE__,__LINE__,__METHOD__, + $key,$setting,$value); + + if (! $this->objects[$this->index]->isDefaultKey($key)) + error("ERROR: Setting a key [$key] that isnt predefined.",'error',true); + + if (! $this->objects[$this->index]->isDefaultSetting($key,$setting)) + error("ERROR: Setting a index [$key,$setting] that isnt predefined.",'error',true); + + # Test if its should be an array or not. + if (is_array($this->objects[$this->index]->getValue($key,$setting)) && ! is_array($value)) + error("Error in configuration file, {$key}['$this->index'] SHOULD be an array of values.",'error',true); + + if (! is_array($this->objects[$this->index]->getValue($key,$setting)) && is_array($value)) + error("Error in configuration file, {$key}['$this->index'] should NOT be an array of values.",'error',true); + + # Store the value in the object. + $this->objects[$this->index]->setValue($key,$setting,$value); + } + + /** + * Get a list of all the configured servers. + * + * @param boolean Only show visible servers. + * @return array list of all configured servers. + */ + public function getServerList($isVisible=true) { + static $CACHE; + + if (! isset($CACHE[$isVisible])) { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with (%s)',3,__FILE__,__LINE__,__METHOD__,$isVisible); + + $CACHE[$isVisible] = array(); + + # Debugging incase objects is not set. + if (! $this->objects) { + print "
";
+				debug_print_backtrace();
+				die();
+			}
+
+			foreach ($this->objects as $id => $server)
+				if (! $isVisible || ($isVisible && $server->getValue('server','visible')))
+					$CACHE[$isVisible][$id] = $server;
+		}
+
+		if (defined('DEBUG_ENABLED') && DEBUG_ENABLED)
+			debug_log('Entered with (%s), Returning (%s)',3,__FILE__,__LINE__,__METHOD__,
+				$isVisible,$CACHE);
+
+		return $CACHE[$isVisible];
+	}
+
+	/**
+	 * Return an object Instance of a configured database.
+	 *
+	 * @param int Index
+	 * @return object Datastore instance object.
+	 */
+	public function Instance($index=null) {
+		if (defined('DEBUG_ENABLED') && DEBUG_ENABLED)
+			debug_log('Entered with (%s)',3,__FILE__,__LINE__,__METHOD__,$index);
+
+		# If no index defined, then pick the lowest one.
+		if (is_null($index))
+			$index = min($this->GetServerList())->getIndex();
+
+		if (! isset($this->objects[$index]))
+			debug_dump_backtrace("Error: Datastore instance [$index] doesnt exist?",1);
+
+		if (defined('DEBUG_ENABLED') && DEBUG_ENABLED)
+			debug_log('Returning instance of database (%s)',3,__FILE__,__LINE__,__METHOD__,$index);
+
+		return $this->objects[$index];
+	}
+
+	/**
+	 * Return an object Instance of a configured database.
+	 *
+	 * @param string Name of the instance to retrieve
+	 * @return object Datastore instance object.
+	 */
+	public function InstanceName($name=null) {
+		if (DEBUG_ENABLED)
+			debug_log('Entered with (%s)',3,__FILE__,__LINE__,__METHOD__,$name);
+
+		foreach ($this->getServerList(false) as $index)
+			if ($this->objects[$index]->getName() == $name)
+				return $this->objects[$index];
+
+		# If we get here, then no object with the name exists.
+		return null;
+	}
+
+	/**
+	 * Return an object Instance of a configured database.
+	 *
+	 * @param string ID of the instance to retrieve
+	 * @return object Datastore instance object.
+	 */
+	public function InstanceId($id=null) {
+		if (DEBUG_ENABLED)
+			debug_log('Entered with (%s)',3,__FILE__,__LINE__,__METHOD__,$id);
+
+		foreach ($this->getServerList(false) as $index)
+			if ($this->objects[$index->getIndex()]->getValue('server','id') == $id)
+				return $this->objects[$index->getIndex()];
+
+		# If we get here, then no object with the name exists.
+		return null;
+	}
+}
+?>
diff --git a/lib/ds_tsm.php b/lib/ds_tsm.php
new file mode 100644
index 0000000..3c31bd8
--- /dev/null
+++ b/lib/ds_tsm.php
@@ -0,0 +1,712 @@
+index = $index;
+		$this->type = 'tsm';
+
+		# Additional values that can go in our config.php
+		$this->custom = new StdClass;
+		$this->default = new StdClass;
+
+		# TSM Server Stanza Name
+		$this->default->server['stanza'] = array(
+			'desc'=>'TSM Server Stanza Name',
+			'default'=>null);
+
+		$this->default->system['dsmadmc'] = array(
+			'desc'=>'Path to dsmadmc command.',
+			'default'=>'/opt/tivoli/tsm/client/ba/bin/dsmadmc');
+		$this->default->system['errorlog'] = array(
+			'desc'=>'Set errorlog filename',
+			'default'=>'/tmp/pta-tsm-errorlog.log');
+		$this->default->system['tapeage'] = array(
+			'desc'=>'Age of tapes to alert for rotation',
+			'default'=>180);
+
+		# TSM Parameters
+		$this->default->tsmparm['msgFormat'] = array(
+			'desc'=>'Format of TSM message codes.',
+			'default'=>'[A-Z]{3}[0-9]{4}[I|E|W|S]');
+
+		$this->default->tsmparm['queryStartMsg'] = array(
+			'desc'=>'TSM code depicting START of queries results.',
+			'default'=>'ANS8000I');
+
+		$this->default->tsmparm['queryEndMsg'] = array(
+			'desc'=>'TSM code depicting END of queries results.',
+			'default'=>'ANS8002I');
+
+		$this->default->tsmparm['loginFail'] = array(
+			'desc'=>'Known TSM "failed login" message codes.',
+			'default'=>array(
+				'ANS8023E',	//
+				'ANS1017E',	// TCP/IP connection rejected.
+				'ANS1217E',	// Server name not found in System Options File
+				'ANS1355E',	// Sessions disabled.
+				'ANS8034E'	// Your administrator ID is not recognized by this server.
+			));
+		$this->default->tsmparm['loginFailIgnore'] = array(
+			'desc'=>'Known TSM "ignore" message codes during login.',
+			'default'=>array(
+				'ANS0110E',	// LogMsg: Unable to open error log file '%s' for output.
+				'ANS8002I'	// Highest return code was %s.
+			));
+		$this->default->tsmparm['ignoremsg'] = array(
+			'desc'=>'Known TSM "ignore" message codes.',
+			'default'=>array(
+				'ANS8001I',	// Return code %s.
+				'ANS0102W'	// Unable to open the message repository %s. The American English repository will be used instead.
+			));
+		$this->default->tsmparm['nodatamsg'] = array(
+			'desc'=>'Known TSM "select returned no data" message codes.',
+			'default'=>array(
+				'ANR2034E'	// SELECT: No match found using this criteria.
+			));
+	}
+
+	/**
+	 * Required ABSTRACT functions
+	 */
+	/**
+	 * Connect and Bind to the Database
+	 *
+	 * @param method This enables to have multiple different types of connects, ie: read only, readwrite, etc
+	 * @return resource|null Connection resource if successful, null if not.
+	 */
+	protected function connect($method,$debug=false) {
+		$method = $this->getMethod($method);
+
+		if (! $this->isLoggedIn($method))
+			system_message(array('title'=>'Not Logged In',
+				'body'=>sprintf('You are not logged into server %s',$this->getValue('server','name')),
+				'type'=>'warn'),'index.php');
+
+		if (! file_exists($this->getValue('system','dsmadmc'))) {
+			system_message(array('title'=>'Cant find DSMADMC',
+				'body'=>sprintf('Unable to find the dsmadmc at %s',$this->getValue('system','dsmadmc')),
+				'type'=>'error'));
+			return false;
+		}
+
+		if ($this->getValue('system','errorlog'))
+			$errorlog = sprintf('-errorlogname=%s',$this->getValue('system','errorlog'));
+
+		if (is_null($this->getValue('server','stanza')) || ! trim($this->getValue('server','stanza')))
+			$TSMcmd = sprintf('%s -id=%s -password=%s -displ=list %s',
+				$this->getValue('system','dsmadmc'),$this->getLogin($method),$this->getPassword($method),
+				isset($errorlog) ? $errorlog : '');
+		else
+			$TSMcmd = sprintf('%s -se=%s -id=%s -password=%s -displ=list %s',
+				$this->getValue('system','dsmadmc'),
+				$this->getValue('server','stanza'),$this->getLogin($method),$this->getPassword($method),
+				isset($errorlog) ? $errorlog : '');
+
+		return $TSMcmd;
+	}
+
+	/**
+	 * Login to the database with the application user/password
+	 *
+	 * @param method This enables to have multiple different types of connects, ie: read only, readwrite, etc
+	 * @return boolean true|false for successful login.
+	 */
+	public function login($user=null,$pass=null,$method=null) {
+		# Set our user and password.
+		$this->setLogin($user,$pass,$method);
+
+		# Test that it works.
+		$result = $this->query('SELECT platform,version,release,level,sublevel FROM status',$method);
+
+		if (! $result)
+			$this->logout($method);
+		else
+			return true;
+
+		return false;
+	}
+
+	/**
+	 * Perform a query to the Database
+	 *
+	 * @param string SQL query to perform
+	 * @param string Index items according to this key
+	 * @return array|null Results of query.
+	 */
+	public function query($query,$method,$index=null,$debug=false) {
+		$TSMcmd = sprintf('%s "%s"',$this->connect($method,$debug),$query);
+		$TSMcmdOutput = exec($TSMcmd,$OutputLines,$return);
+
+		if ($debug)
+			debug_dump(array('cmd'=>$TSMcmd,'TSMcmdOutput'=>$TSMcmdOutput,'return'=>$return));
+
+		# Parse the ouput for key:value pairs.
+		$outindex = 0;
+		$isQueryResult = 0;
+		$isKeyValue = 0;
+		$haveHelp = false;
+		$QueryResult = false;
+		$tsmCommands = array();
+		foreach ($OutputLines as $OutputLine ) {
+			$OutputLine = preg_replace('/\s+$/','',$OutputLine);
+			if ($debug)
+				debug_dump("($isQueryResult/$isKeyValue): WORKING line [$OutputLine]");
+
+			# See if we got a message number.
+			if (preg_match("/^(".$this->getValue('tsmparm','msgFormat').")\s+/",$OutputLine,$matches)) {
+				$msgnum = $matches[1];
+
+				if ($debug)
+					debug_dump("($isQueryResult/$isKeyValue): Got SYSTEM Msg [$OutputLine]");
+
+				# Filter through the output and check if we got our start message.
+				if (preg_match("/^".$this->getValue('tsmparm','queryStartMsg')."\s+/",$OutputLine)) {
+					if ($debug)
+						debug_dump("($isQueryResult/$isKeyValue): Got START Msg [$OutputLine]");
+					$isQueryResult = 1;
+					continue;
+
+				# Break out of loop when we get our end message.
+				} elseif (preg_match("/^".$this->getValue('tsmparm','queryEndMsg')."\s+/",$OutputLine)) {
+					if ($debug)
+						debug_dump("($isQueryResult/$isKeyValue): Got END Msg [$OutputLine]");
+					break;
+				}
+
+				if (in_array($msgnum,$this->getValue('tsmparm','nodatamsg')))
+					continue;
+
+				if (! in_array($msgnum,$this->getValue('tsmparm','ignoremsg')))
+					system_message(array('title'=>'TSM Message','body'=>$OutputLine));
+			}
+
+			# If we the line is a queryResult line
+			if ($isQueryResult) {
+
+				# See if we got a key:value pair, otherwise go to next line.
+				if (preg_match('/:/',$OutputLine) && ! (preg_match('/^help/',$query))) {
+					$OutputLine = preg_replace('/\s*:\s+/',':',$OutputLine);
+					list($key,$value) = explode(':',$OutputLine,2);
+					$isKeyValue = 1;
+
+				# Not key:value, so check if we need to increase the index.
+				} else {
+
+					# Is this a show slots command, so there is more info
+					if (preg_match('/^show slots/',$query)) {
+
+						if (preg_match('/^Slot /',$OutputLine)) {
+
+							foreach ((preg_split('/,\s*/',$OutputLine,-1)) as $slotkey => $val) {
+
+								if (preg_match('/^element number\s+/',$val)) {
+									$key = preg_replace('/^element number\s+/','',$val);
+
+								} elseif (preg_match('/^Slot\s+/',$val)) {
+									$slots['slot'] = preg_replace('/^Slot\s+/','',$val);
+
+								} elseif (preg_match('/^status\s+/',$val)) {
+									$slots['status'] = preg_replace('/^status\s+/','',$val);
+
+								} elseif (preg_match('/^barcode value /',"$1",$val);
+
+								} elseif (preg_match('/^barcode\s+/',$val)) {
+									$slots['barcode'] = preg_replace('/^barcode /','',$val);
+								}
+							}
+
+							$QueryResult[$outindex]['SlotUsage'][$key] = $slots;
+
+						} elseif (preg_match('/busy.$/',$OutputLine)) {
+							system_message(array('title'=>'Library is Busy',
+								'body'=>sprintf('The library appears busy at the moment.
%s',$OutputLine), + 'type'=>'info'), + sprintf('index.php',get_request('cmd','REQUEST') ? sprintf('cmd=%s',get_request('cmd','REQUEST')) : '')); + } + + # Is this a help command, so there is more info + } elseif (preg_match('/^help$/',$query)) { + if (preg_match('/^\s*([0-9]+) - Commands /',$OutputLine)) { + $helpnum = preg_replace('/^\s*([0-9]+) - Commands .*/','$1',$OutputLine); + + $tsmCommands = array_unique(array_merge($tsmCommands,$this->query("help $helpnum",null))); + } + + } elseif (preg_match('/^help\s+[0-9]+/',$query)) { + if (! $OutputLine) + continue; + + if (! $haveHelp && preg_match('/^\s*Command Name/',$OutputLine)) { + $haveHelp = true; + $helpCommands = array(); + continue; + } + + if ($haveHelp) { + # Filter out the beginning spaces. + $OutputLine = preg_replace('/^\s*/','',$OutputLine); + + # Filter out the "(See Note)" messages. + $OutputLine = preg_replace('/\s\(.*\)/','',$OutputLine); + + # Join the two columns together + $OutputLine = preg_replace('/\ \s+/','|',$OutputLine); + + # We should now just have commands and they should be in uppercase. + if (preg_match('/[a-z0-9]/',$OutputLine)) + continue; + + #debug_dump("Im finally here with [$OutputLine]."); + + foreach (explode('|',$OutputLine) as $helpCMD) + array_push($helpCommands,$helpCMD); + } + + } elseif (preg_match('/^help\s+[A-Z]+/',$query)) { + # Sometimes QueryResult gets data because of colons - we need to capture that. + if (isset($QueryResult) && is_array($QueryResult)) { + foreach ($QueryResult as $lineDetail) + foreach ($lineDetail as $key => $ldindex) + $helpText[] = $key.$ldindex; + $QueryResult = array(); + } + + $helpText[] = $OutputLine; + //debug_dump(array("Im finally here with [$OutputLine]."=>$QueryResult)); + + # If this is a blank line + } elseif (!$OutputLine) { + + # Only increment index if the previous iteration was a key/value pair. + # Reset isKeyValue so we dont increase index for 2 non key:value lines in a row. + if ($isKeyValue) { + if (!preg_match('/^show/',$query)) + $outindex++; + $isKeyValue = 0; + } + + } // if + continue; + + } // if + + $key = preg_replace('/^\s+/','',$key); + $value = preg_replace('/^\s+/','',$value); + $QueryResult[$outindex][$key] = $value; + + } // if + } // foreach + + if (is_array($tsmCommands) && preg_match('/^help$/',$query)) { + asort($tsmCommands); + $QueryResult = $tsmCommands; + } + + if (isset($helpCommands) && preg_match('/^help\s+[0-9]+/',$query)) + $QueryResult = $helpCommands; + + if (isset($helpText) && preg_match('/^help\s+[A-Z]+/',$query)) + $QueryResult = $helpText; + + if ($debug) + debug_dump($QueryResult,0); + + if (! $isQueryResult && $debug) + Error(sprintf('The query
%s
didnt return anything [%s]',preg_replace('/-password=(.*)\ -/','-password=x -',$TSMcmd),serialize($OutputLines))); + + if ($index && is_array($QueryResult)) { + foreach ($QueryResult as $result => $val) { + if (isset($val[$index])) + $IndexQueryResult[$val[$index]] = $QueryResult[$result]; + } + + return (isset($IndexQueryResult) ? $IndexQueryResult : false); + } elseif (is_array($QueryResult)) { + return $QueryResult; + } + return array(); + } + + /** + * Get the last error string + */ + protected function getErrorMessage() { + return null; + } + + /** + * Get the last error number + */ + protected function getErrorNum() { + return null; + } + + /** + * Return if anonymous bind is allowed in the configuration + */ + public function isAnonBindAllowed() { + return false; + } + + /** + * Query the database for particular attributes values. + */ + public function queryAttr($table,$attrs,$key,$where=null,$orderby=null) { + return array(); + } + + /** + * Register user to the application + */ + public function register($user,$pass,$other=array()) { + return null; + } + + /** + * Query TSM and get Database information + */ + function QueryDBDetail() { + $query = $this->query('select * from DB',null); + $this->CACHE['db'] = $query[0]; + $this->CACHE['db']['dbvols'] = $this->query('select * from DBVOLUMES',null); + $this->CACHE['cache']['db'] = time(); + $this->CACHE['cache']['log'] = time(); + + $query = $this->query('select * from LOG',null); + $this->CACHE['log'] = $query[0]; + $this->CACHE['log']['logvols'] = $this->query('select * from LOGVOLUMES',null); + + $_SESSION['tsm'][$this->index] = $this->CACHE; + } + + /** + * Return the DB information. + * @param string attribute + * @return string value for attribute + */ + function GetDBDetail($attribute) { + if (! isset($this->CACHE['db'][$attribute])) + $this->QueryDBDetail(); + + if (! isset($this->CACHE['db'][$attribute])) + Error(sprintf(_('ERROR: A request to get DB attribute "%" didnt return a value'),$attribute)); + + return $this->CACHE['db'][$attribute]; + } + + /** + * Return the LOG information. + * @param string attribute + * @return string value for attribute + */ + function GetLogDetail($attribute) { + if (! isset($this->CACHE['log'][$attribute])) + $this->QueryDBDetail(); + + if (! isset($this->CACHE['log'][$attribute])) + Error(sprintf(_('ERROR: A request to get LOG attribute "%" didnt return a value'),$attribute)); + + return $this->CACHE['log'][$attribute]; + } + + /** + * Return the VOLUMES information + * @return array + */ + function GetVolumes() { + if (isset($this->CACHE['volumes'])) + return $this->CACHE['volumes']; + + $this->CACHE['volumes'] = $this->query('select * from VOLUMES',null,'VOLUME_NAME'); + $this->CACHE['cache']['volumes'] = time(); + $_SESSION['tsm'][$this->index] = $this->CACHE; + return $this->CACHE['volumes']; + } + + /** + * Return the LIBVOLUMES information + * @return array + */ + function GetLibVolumes() { + if (isset($this->CACHE['libvols'])) + return $this->CACHE['libvols']; + + $this->CACHE['libvols'] = $this->query('select * from LIBVOLUMES',null,'HOME_ELEMENT'); + $this->CACHE['cache']['libvols'] = time(); + $_SESSION['tsm'][$this->index] = $this->CACHE; + return $this->CACHE['libvols']; + } + + /** + * Return the DB Backup Volumes + * @return array + */ + function GetDBBackupInfo() { + if (isset($this->CACHE['dbbackup'])) + return $this->CACHE['dbbackup']; + + $this->CACHE['dbbackup']['vols'] = $this->query("select * from VOLHISTORY where TYPE in ('BACKUPFULL','BACKUPINCR','DBSNAPSHOT') order by BACKUP_SERIES,BACKUP_OPERATION,VOLUME_SEQ", + null,'VOLUME_NAME'); + + # Now check that a backup series is valid - since it should be in order. + $lastSeries = -1; + $backupfirst = ''; + $backuplast = ''; + $backupcount = 0; + if (is_array($this->CACHE['dbbackup']['vols'])) { + foreach ($this->CACHE['dbbackup']['vols'] as $volume => $voldetails) { + if ($lastSeries != $voldetails['BACKUP_SERIES']) { + $lastSeries = $voldetails['BACKUP_SERIES']; + $lastOperation = -1; + $lastSeq = 0; + + if (! $backupfirst | $voldetails['DATE_TIME'] < $backupfirst) + $backupfirst = $voldetails['DATE_TIME']; + if (! $backuplast | $voldetails['DATE_TIME'] > $backuplast) + $backuplast = $voldetails['DATE_TIME']; + $backupcount++; + } + + # No point trying this series if it is invalid. + if (isset($status[$voldetails['BACKUP_SERIES']]['status']) && + ($status[$voldetails['BACKUP_SERIES']]['status'] == 'InValid')) + + continue; + + $this->CACHE['dbbackup']['vols'][$volume]['STATUS'] = 'Valid'; + + if (($voldetails['BACKUP_OPERATION'] == $lastOperation) || ($voldetails['BACKUP_OPERATION']-1 == $lastOperation)) { + $status[$voldetails['BACKUP_SERIES']]['status'] = 'Valid'; + $lastOperation = $voldetails['BACKUP_OPERATION']; + + } else { + $status[$voldetails['BACKUP_SERIES']]['status'] = 'InValid'; + $status[$voldetails['BACKUP_SERIES']]['statusreason'] = sprintf(_('Missing SERIES volumes, expected [%s], found [%s].'), + $lastOperation+1,$voldetails['BACKUP_OPERATION']); + continue; + } + + if (($voldetails['VOLUME_SEQ'] == $lastSeq) || $voldetails['VOLUME_SEQ']-1 == $lastSeq) { + $status[$voldetails['BACKUP_SERIES']]['status'] = 'Valid'; + $lastSeq = $voldetails['VOLUME_SEQ']; + + } else { + $status[$voldetails['BACKUP_SERIES']]['status'] = 'InValid'; + $status[$voldetails['BACKUP_SERIES']]['statusreason'] = sprintf(_('Missing SERIES volumes, expected [%s], found [%s].'),$lastSeq+1,$voldetails['VOLUME_SEQ']); + } + + $lastSeq = $voldetails['VOLUME_SEQ']; + } + + # Now we know which series are invalid, mark all the volumes. + foreach ($this->CACHE['dbbackup']['vols'] as $volume => $voldetails) { + $this->CACHE['dbbackup']['vols'][$volume]['STATUS'] = $status[$voldetails['BACKUP_SERIES']]['status']; + if (isset($status[$voldetails['BACKUP_SERIES']]['statusreason'])) + $this->CACHE['dbbackup']['vols'][$volume]['STATUSREASON'] = $status[$voldetails['BACKUP_SERIES']]['statusreason']; + } + } + + # Grab backup information + $history = $this->query('select DATE_TIME,MSGNO,MESSAGE from ACTLOG where MSGNO in (4550,4551) order by DATE_TIME',null); + + if (! isset($history[0]['ANR2034E SELECT'])) + foreach ($history as $index => $historydetail) { + $this->CACHE['dbbackup']['history'][$index]['date'] = $historydetail['DATE_TIME']; + + switch ($historydetail['MSGNO']) { + case 4550: + if (preg_match('/([0-9]+) pages copied./',$historydetail['MESSAGE'],$matchall)) { + $this->CACHE['dbbackup']['history'][$index]['size'] = $matchall[1]; + $this->CACHE['dbbackup']['history'][$index]['type'] = 'FULL'; + } + break; + + case 4551: + if (preg_match('/([0-9]+) pages copied./',$historydetail['MESSAGE'],$matchall)) { + $this->CACHE['dbbackup']['history'][$index]['size'] = $matchall[1]; + $this->CACHE['dbbackup']['history'][$index]['type'] = 'INCR'; + } + break; + + default: + printf('Message %s not catered for [%s]...',$historydetail['MSGNO'],$historydetail['MESSAGE']); + die(); + } + } + + # Get DBBackup Trigger information. + $select = $this->query('select * from DBBACKUPTRIGGER',null); + $result = array_shift($select); + + if (isset($result['ANR2034E SELECT'])) + $this->CACHE['dbbackup']['trigger'] = false; + else + $this->CACHE['dbbackup']['trigger'] = $result; + + $this->CACHE['dbbackup']['first'] = $backupfirst; + $this->CACHE['dbbackup']['last'] = $backuplast; + $this->CACHE['dbbackup']['count'] = $backupcount; + + $this->CACHE['cache']['dbbackup'] = time(); + $_SESSION['tsm'][$this->index] = $this->CACHE; + return $this->CACHE['dbbackup']; + } + + function GetDBBackupDetail($detail) { + if (isset($this->CACHE['dbbackup'][$detail])) + return $this->CACHE['dbbackup'][$detail]; + + $this->GetDBBackupInfo(); + + if (isset($this->CACHE['dbbackup'][$detail])) + return $this->CACHE['dbbackup'][$detail]; + else + return null; + } + + /** + * Return the Status Information + * @return array + */ + function GetStatus() { + if (isset($this->CACHE['status'])) + return $this->CACHE['status']; + + $select = $this->query('select * from STATUS',null); + $this->CACHE['status'] = array_shift($select); + + $this->CACHE['cache']['status'] = time(); + $_SESSION['tsm'][$this->index] = $this->CACHE; + return $this->CACHE['status']; + } + + function GetStatusDetail($detail) { + if (isset($this->CACHE['status'][$detail])) + return $this->CACHE['status'][$detail]; + + $this->GetStatus(); + + if (isset($this->CACHE['status'][$detail])) + return $this->CACHE['status'][$detail]; + else + return null; + } + + /** + * Get TSM event information + * @param string Start date for report + * @param string End date for report + * @param string order by key. + * @return array Query result + */ + function GetEvents($reportStart='',$reportEnd='',$orderby='SCHEDULED_START') { + if (isset($this->CACHE['events']) && $reportStart == $this->CACHE['events']['start'] && + $reportEnd == $this->CACHE['events']['end'] && $orderby == $this->CACHE['events']['orderby']) + + return $this->CACHE['events']; + + $TSMQuery = 'select * from EVENTS where DOMAIN_NAME is not null'; + + if (($reportStart) && ($reportEnd)) + $TSMQuery .= sprintf(" and (SCHEDULED_START between '%s' and '%s')",$reportStart,$reportEnd); + else if ($reportStart) + $TSMQuery .= sprintf(" and SCHEDULED_START \> '%s'",$reportStart); + else if ($reportEnd) + $TSMQuery .= sprintf(" and SCHEDULED_START <= '%s'",$reportEnd); + + $TSMQuery .= sprintf(' order by %s',$orderby); + + $result = $this->query($TSMQuery,null); + + if (isset($result[0]['ANR2034E SELECT'])) { + $this->CACHE['events'] = false; + + } else { + $this->CACHE['events']['detail'] = $result; + $this->CACHE['events']['start'] = $reportStart; + $this->CACHE['events']['end'] = $reportEnd; + $this->CACHE['events']['orderby'] = $orderby; + } + + $this->CACHE['cache']['events'] = time(); + $_SESSION['tsm'][$this->index] = $this->CACHE; + return $this->CACHE['events']; + } + + /** + * Get TSM Activity Log Client backup information. + * @param string Start date for report + * @param string End date for report + * @return array Query result + */ + function GetActlogBackupSummary($reportStart='',$reportEnd='') { + if (isset($this->CACHE['actlogbackup']) && $reportStart == $this->CACHE['actlogbackup']['start'] && + $reportEnd == $this->CACHE['actlogbackup']['end']) + + return $this->CACHE['actlogbackup']; + + $tsmMSG['4952'] = 'Inspected'; + $tsmMSG['4954'] = 'Backup'; + $tsmMSG['4958'] = 'Update'; + $tsmMSG['4957'] = 'Delete'; + $tsmMSG['4959'] = 'Failed'; + $tsmMSG['4960'] = 'Rebound'; + $tsmMSG['4961'] = 'Transfer'; + $tsmMSG['4964'] = 'ProcTime'; + $tsmMSG['4967'] = 'AggRate'; + $tsmMSG['4968'] = 'Compress'; + $tsmMSG['4970'] = 'Expire'; + + $TSMQuery = sprintf('select MSGNO,NODENAME,SESSID,MESSAGE,SCHEDNAME,DOMAINNAME from ACTLOG where MSGNO in (%s)', + implode(',',array_keys($tsmMSG))); + + if (($reportStart) && ($reportEnd)) + $TSMQuery .= sprintf(" and (DATE_TIME between '%s' and '%s')",$reportStart,$reportEnd); + else if ($reportStart) + $TSMQuery .= sprintf(" and DATE_TIME \> '%s'",$reportStart); + else if ($reportEnd) + $TSMQuery .= sprintf(" and DATE_TIME <= '%s'",$reportEnd); + + $TSMQuery .= ' order by sessid'; + + foreach ($this->query($TSMQuery,null) as $tsmBackup => $tsmSession) { + # TSM on AIX adds a "SESSION" at the end of each line - maybe others do as well. + if (isset($tsmSession['MESSAGE'])) + $tsmSession['MESSAGE'] = preg_replace('/\(SESSION:.*\)/','',$tsmSession['MESSAGE']); + + $tsmBackupSummary['detail'][$tsmSession['NODENAME']][$tsmSession['SESSID']][$tsmMSG[$tsmSession['MSGNO']]] = preg_replace('/^.*:\s*/U','',$tsmSession['MESSAGE']); + } + + $tsmBackupSummary['start'] = $reportStart; + $tsmBackupSummary['end'] = $reportEnd; + + $this->CACHE['actlogbackup'] = $tsmBackupSummary; + $this->CACHE['cache']['actlogbackup'] = time(); + $_SESSION['tsm'][$this->index] = $this->CACHE; + return $this->CACHE['actlogbackup']; + } + + function GetVolLocation($vol) { + $volumes = $this->GetVolumes(); + + if ($this->GetLibVolumes()) + foreach ($this->GetLibVolumes() as $slot => $details) { + if ($details['VOLUME_NAME'] == $vol) + return sprintf('**%s %s**',_('LIBRARY'),$details['LIBRARY_NAME']); + } + + if (isset($volumes[$vol])) + return $volumes[$vol]['LOCATION']; + + # If we get here, we dont know where the volume is. + return null; + } +} +?> diff --git a/lib/functions.custom.php b/lib/functions.custom.php new file mode 100644 index 0000000..d6ebfeb --- /dev/null +++ b/lib/functions.custom.php @@ -0,0 +1,330 @@ +getIndex()]['stgps']->isPrimaryPool($pool); +} + +function isCopyPool($pool) { + global $app; + + objectCache('stgps'); + + return $_SESSION['cache'][$app['server']->getIndex()]['stgps']->isCopyPool($pool); +} + +function getReclaim($pool) { + global $app; + + objectCache('stgps'); + + return $_SESSION['cache'][$app['server']->getIndex()]['stgps']->getReclaim($pool); +} + +function getReUse($pool) { + global $app; + + objectCache('stgps'); + + return $_SESSION['cache'][$app['server']->getIndex()]['stgps']->getReUse($pool); +} + +function getVolume($vol) { + global $app; + + objectCache('volumes'); + + return $_SESSION['cache'][$app['server']->getIndex()]['volumes']->getVolume($vol); +} + +function isLibraryDevClass($devclass) { + global $app; + + objectCache('devclasses'); + + return $_SESSION['cache'][$app['server']->getIndex()]['devclasses']->isLibraryDevClass($devclass); +} + +function LibraryDevClass($devclass) { + global $app; + + objectCache('devclasses'); + + return $_SESSION['cache'][$app['server']->getIndex()]['devclasses']->LibraryDevClass($devclass); +} + +/** + * Return the name that a cookie will be stored by the browser. + * @param int Server ID. + * @param string Name of Cookie. + * @return string Fully qualified cookie name. + */ +function cookie_name($index,$name) { + global $app; + + return $app['server']->getValue('cookie','prefix').$index.$name; +} + +/** + * Returns the value of cookie + * @param int Server ID + * @param string Name + * @return string|false Value of cookie (null if not set/no value) + */ +function get_cookie($index,$name) { + global $_COOKIE; + + $cookie = cookie_name($index,$name); + + if (isset($_COOKIE[$cookie])) + return $_COOKIE[$cookie]; + else + return false; +} + +/** + * Store a cookie in the user browser. + * @param int Server ID + * @param string Name of Cookie + * @param string Value for Cookie + * @param int Time for cookie to live in seconds + * @param string dir Cookie Path + * @result boolean + */ +function store_cookie($index,$name,$val,$expire=null,$dir=null) { + global $app; + + $cookie_name = cookie_name($index,$name); + $cookie_time = $app['server']->getValue('cookie','time'); + + if ($expire == null) + $expire = ($cookie_time == 0 ? null : time() + $cookie_time); + + if ($dir == null) + $dir = dirname($app['server']->getValue('server','path') ? $app['server']->getValue('server','path') : '/'); + + if (setcookie($cookie_name,$val,$expire,$dir)) + return true; + else + return false; +} + +/** + * Unset all cookies on logout. + * @param int Server ID + * @result boolean True if successful, false if not + * @todo: No routine to call this. + */ +function unset_cookie($index) { + global $app; + global $_COOKIE; + + $expire = time()-3600; + + foreach ($_COOKIE as $cookie => $cookie_value) { + $cookie = preg_replace("/^".$app['server']->getValue('server','path').$index.'/','',$cookie); + if ($cookie == 'PHPSESSID') continue; + $result = store_cookie($index,$cookie,'',$expire) ; + + if (! $result) return false; + } + return true; +} + +/** + * Initialise JPgraph + * @param boolean Must initialise or fail with an error if unable to do so + */ +function initJPGraph($need=false) { + $jpgraph = $_SESSION[APPCONFIG]->getValue('lib','jpgraph'); + + if (! $jpgraph) + if ($need) + die(_('Your JPGRAPH setting in config.php is pointing to a directory that does not exist. Please fix your config.php')); + else + return false; + + if ((! file_exists($jpgraph.'/src/jpgraph.php')) || (! file_exists($jpgraph.'/src/jpgraph_gantt.php'))) + if ($need) + die(sprintf(_('PTA was not able to find the JPGRAPH utility in the "%s" directory. Either your config.php is pointing to the wrong directory, or you havent installed JPGRAPH'),$jpgraph)); + else + return false; + + if (! function_exists('imagetypes') && ! function_exists('imagecreatefromstring')) + if ($need) + die(_('It seems that GD support is not available. Please add GD support to PTA.')); + else + return false; + + if (! is_dir($_SESSION[APPCONFIG]->getValue('image','path')) || ! is_writable($_SESSION[APPCONFIG]->getValue('image','path'))) + die(sprintf(_('The temporary path for JPGRAPH either doesnt exist or is not writable - you have it configured for %s.'),$_SESSION[APPCONFIG]->getValue('image','path'))); + + # When we get here, we have all the pre-reqs. + error_reporting(0); + require_once $jpgraph.'/src/jpgraph.php'; + require_once $jpgraph.'/src/jpgraph_gantt.php'; + require_once $jpgraph.'/src/jpgraph_line.php'; + require_once $jpgraph.'/src/jpgraph_bar.php'; + require_once $jpgraph.'/src/jpgraph_pie.php'; + require_once $jpgraph.'/src/jpgraph_date.php'; + error_reporting(E_ALL); + return true; +} + +/** + * Put some HTML classes around strings containing values. + * @param string String to parse + * @param string Class to substitute + * @return string String with around %s + */ +function classValue($string,$class) { + return preg_replace('/(\%[0-9.]*[sf])/',"$1$2",$string); +} + +function objectCache($object,$force=false) { + global $app; + + $index = $app['server']->getIndex(); + + if (DEBUG_ENABLED) + debug_log('objectCache(): Entered with (%s,%s)',1,$object,isset($_SESSION['cache'][$index][$object])); + + if (isset($_SESSION['cache'][$index][$object])) { + if ($_SESSION['cache'][$index][$object]->expired() || $force) { + if (DEBUG_ENABLED) + debug_log('objectCache(): Object expired (%s,%s)',1,$_SESSION['cache'][$index][$object]->cache,time()); + + $_SESSION['cache'][$index][$object]->load(); + $_SESSION['cache'][$index][$object]->cache = time(); + } + + } else { + switch($object) { + case 'devclasses' : + $_SESSION['cache'][$index][$object] = new deviceClasses($index); + break; + + case 'drives' : + $_SESSION['cache'][$index][$object] = new drives($index); + break; + + case 'help' : + $_SESSION['cache'][$index][$object] = new help($index); + break; + + case 'libraries' : + $_SESSION['cache'][$index][$object] = new libraries($index); + break; + + case 'mgmtclasses' : + $_SESSION['cache'][$index][$object] = new mgmtClasses($index); + break; + + case 'nodes' : + $_SESSION['cache'][$index][$object] = new nodes($index); + break; + + case 'occupancy' : + $_SESSION['cache'][$index][$object] = new occupancy($index); + break; + + case 'stgps' : + $_SESSION['cache'][$index][$object] = new storagePools($index); + break; + + case 'summaryinfo' : + $_SESSION['cache'][$index][$object] = new summaryInfo($index); + break; + + case 'volumes' : + $_SESSION['cache'][$index][$object] = new volumes($index); + break; + + default: + error(sprintf('Unknown object %s',$object)); + } + + $_SESSION['cache'][$index][$object]->cache = time(); + } + + return $_SESSION['cache'][$index][$object]; +} + +function render_page($title,$body) { + $www = new page(); + + if (is_array($title)) { + foreach ($title as $key => $title) { + $block = new block(); + $block->SetTitle($title); + $block->SetBody($body[$key]); + $www->block_add('body',$block); + } + + } else { + $block = new block(); + $block->SetTitle($title); + $block->SetBody($body); + $www->block_add('body',$block); + } + $www->body(); +} +?> diff --git a/lib/functions.php b/lib/functions.php new file mode 100644 index 0000000..06c4788 --- /dev/null +++ b/lib/functions.php @@ -0,0 +1,1018 @@ +_('Generic Error'), + 'body'=>sprintf('%s: %s [%s]', + __METHOD__,_('Called to load a class that cant be found'),$className), + 'type'=>'error')); +} + +/** + * Strips all slashes from the specified array in place (pass by ref). + * @param Array The array to strip slashes from, typically one of + * $_GET, $_POST, or $_COOKIE. + */ +function array_stripslashes(&$array) { + if (DEBUG_ENABLED) + debug_log('Entered with (%s)',1,__FILE__,__LINE__,__METHOD__,$array); + + if (is_array($array)) + while (list($key) = each($array)) + if (is_array($array[$key]) && $key != $array) + array_stripslashes($array[$key]); + else + $array[$key] = stripslashes($array[$key]); +} + +/** + * Compatibility Functions + * These functions exist, so that a standard function can be used in new applications, and they + * map to already defined functions in older applications. + */ + +/** + * If gettext is not available in PHP, then this will provide compatibility for it. + */ +if (! function_exists('_')) { + function _($msg) { + return $msg; + } +} + +/** + * Generic Utility Functions + */ + +/** + * Custom error handling function. + * When a PHP error occurs, PHP will call this function rather than printing + * the typical PHP error string. This provides the application the ability to + * format an error message so that it looks better. + * Optionally, it can present a link so that a user can search/submit bugs. + * This function is not to be called directly. It is exclusively for the use of + * PHP internally. If this function is called by PHP from within a context + * where error handling has been disabled (ie, from within a function called + * with "@" prepended), then this function does nothing. + * + * @param int The PHP error number that occurred (ie, E_ERROR, E_WARNING, E_PARSE, etc). + * @param string The PHP error string provided (ie, "Warning index "foo" is undefined) + * @param string The file in which the PHP error ocurred. + * @param int The line number on which the PHP error ocurred + * @see set_error_handler + */ +function app_error_handler($errno,$errstr,$file,$lineno) { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with (%s,%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__, + $errno,$errstr,$file,$lineno); + + /** + * error_reporting will be 0 if the error context occurred + * within a function call with '@' preprended (ie, @ldap_bind() ); + * So, don't report errors if the caller has specifically + * disabled them with '@' + */ + if (ini_get('error_reporting') == 0 || error_reporting() == 0) + return; + + $file = basename($file); + $caller = basename($_SERVER['PHP_SELF']); + $errtype = ''; + + switch ($errno) { + case E_STRICT: $errtype = 'E_STRICT'; break; + case E_ERROR: $errtype = 'E_ERROR'; break; + case E_WARNING: $errtype = 'E_WARNING'; break; + case E_PARSE: $errtype = 'E_PARSE'; break; + case E_NOTICE: $errtype = 'E_NOTICE'; break; + case E_CORE_ERROR: $errtype = 'E_CORE_ERROR'; break; + case E_CORE_WARNING: $errtype = 'E_CORE_WARNING'; break; + case E_COMPILE_ERROR: $errtype = 'E_COMPILE_ERROR'; break; + case E_COMPILE_WARNING: $errtype = 'E_COMPILE_WARNING'; break; + case E_USER_ERROR: $errtype = 'E_USER_ERROR'; break; + case E_USER_WARNING: $errtype = 'E_USER_WARNING'; break; + case E_USER_NOTICE: $errtype = 'E_USER_NOTICE'; break; + case E_ALL: $errtype = 'E_ALL'; break; + + default: $errtype = sprintf('%s: %s',_('Unrecognized error number'),$errno); + } + + # Take out extra spaces in error strings. + $errstr = preg_replace('/\s+/',' ',$errstr); + + if ($errno == E_NOTICE) { + $body = ''; + $body .= sprintf('',_('Error'),$errstr,$errtype); + $body .= sprintf('', + _('File'),$file,_('line'),$lineno,_('caller'),$caller); + $body .= sprintf('', + app_version(),phpversion(),php_sapi_name()); + $body .= sprintf('',isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'SCRIPT'); + + if (function_exists('get_href')) + $body .= sprintf('', + get_href('search_bug',"&summary_keyword=".rawurlencode($errstr)), + _('Please check and see if this bug has been reported')); + $body .= '
%s:%s (%s)
%s:%s %s %s, %s %s
Versions:APP: %s, PHP: %s, SAPI: %s
Web server:%s
%s.
'; + + system_message(array( + 'title'=>_('You found a non-fatal application bug!'), + 'body'=>$body, + 'type'=>'error')); + + return; + } + + # If this is a more serious error, call the error call. + error(sprintf('%s: %s',$errtype,$errstr),'error',null,true,true); +} + +/** + * Returns the application name. + */ +function app_name() { + if (isset($_SESSION[APPCONFIG])) + return $_SESSION[APPCONFIG]->getValue('app','name',false); + else + return 'Application name NOT set'; +} + +/** + * Returns the application version currently running. The version + * is read from the file named VERSION. + * + * @return string The current version as read from the VERSION file. + */ +function app_version() { + static $CACHE = null; + + if ($CACHE) + return $CACHE; + + $version_file = realpath(LIBDIR.'../VERSION'); + if (! file_exists($version_file)) + $CACHE = 'UNKNOWN'; + + else { + $f = fopen($version_file,'r'); + $version = trim(fread($f, filesize($version_file))); + fclose($f); + + # We use cvs_prefix, because CVS will translate this on checkout otherwise. + $cvs_prefix = '\$Name:'; + + $CACHE = preg_replace('/^'.$cvs_prefix.' RELEASE-([0-9_]+)\s*\$$/','$1',$version); + $CACHE = preg_replace('/_/','.',$CACHE); + + # Check if we are a CVS copy. + if (preg_match('/^'.$cvs_prefix.'?\s*\$$/',$CACHE)) + $CACHE = 'CVS'; + + # Check if we are special CVS branch + elseif (preg_match('/^'.$cvs_prefix.'?\s*([a-zA-Z]+)?\s*\$$/',$CACHE,$match)) + $CACHE = $match[1]; + + # If return is still the same as version, then the tag is not one we expect. + elseif ($CACHE == $version) + $CACHE = 'UNKNOWN'; + } + + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with (), Returning (%s)',1,__FILE__,__LINE__,__METHOD__,$CACHE); + + return $CACHE; +} + +/** + * This function will convert the browser two character language into the + * default 5 character language, where the country portion should NOT be + * assumed to be upper case characters of the first two characters. + */ +function auto_lang($lang) { + switch ($lang) { + case 'ja': return 'ja_JP'; + case 'cs': return 'cs_CZ'; + default: return sprintf('%s_%s',$lang,strtoupper($lang)); + } +} + +/** + * Makes sure that the config file is properly setup. + */ +function check_config($config_file) { + # Read in config_default.php + require_once LIBDIR.'config_default.php'; + + # Make sure their PHP version is current enough + if (strcmp(phpversion(),REQUIRED_PHP_VERSION) < 0) + system_message(array( + 'title'=>_('Incorrect version of PHP'), + 'body'=>sprintf('This application requires PHP version %s or greater.
(You are using %s)', + REQUIRED_PHP_VERSION,phpversion()), + 'type'=>'error')); + + $config = new Config; + + if (file_exists(LIBDIR.'config_custom.php') && is_readable(LIBDIR.'config_custom.php')) + include LIBDIR.'config_custom.php'; + + ob_start(); + require $config_file; + $str = ''; + if (ob_get_level()) { + $str = ob_get_contents(); + ob_end_clean(); + } + + if ($str) { + $str = strip_tags($str); + $matches = array(); + preg_match('/(.*):\s+(.*):.*\s+on line (\d+)/',$str,$matches); + + if (isset($matches[1]) && isset($matches[2]) && isset($matches[3])) { + $error_type = $matches[1]; + $error = $matches[2]; + $line_num = $matches[3]; + + $file = file($config_file); + + $body = '

Config file ERROR

'; + $body .= sprintf('

%s (%s) on line %s

',$error_type,$error,$line_num); + + $body .= '
'; + $body .= sprintf('Looks like your config file has an ERROR on line %s.
',$line_num); + $body .= 'Here is a snippet around that line
'; + $body .= '
'."\n"; + + $body .= '
'; + + for ($i = $line_num-9; $i<$line_num+5; $i++) { + if ($i+1 == $line_num) + $body .= '
'; + + if ($i < 0) + continue; + + $body .= sprintf('%s: %s
',$i+1,$file[$i]); + + if ($i+1 == $line_num) + $body .= '
'; + } + + $body .= '
'; + $body .= '
'; + $body .= 'Hint: Sometimes these errors are caused by lines preceding the line reported.'; + $body .= '
'; + + $block = new block(); + $block->SetBody($body); + $www['page'] = new page(); + $www['page']->block_add('body',$block); + $www['page']->display(); + + die(); + } + } + + # Process our friendly attributes. + if (isset($friendly_attrs)) + $config->getFriendlyAttrs($friendly_attrs); + + # Check for server definitions. + if (! isset($servers) || count($servers->GetServerList()) == 0) + error(_('Your config.php is missing Server Definitions. Please see the sample file config/config.php.example.'),'error','index.php',true); + + $config->setServers($servers); + + # Check the memory limit parameter. + if (ini_get('memory_limit') < $config->getValue('session','memorylimit')) + system_message(array( + 'title'=>_('Memory Limit low.'), + 'body'=> sprintf('Your php memory limit is low - currently %s, you should increase it to atleast %s. This is normally controlled in /etc/php.ini.',ini_get('memory_limit'),$config->getValue('session','memorylimit')), + 'type'=>'error')); + + return $config; +} + +/** + * Commands available in the control_panel of the page + * + * @return array + */ +function cmd_control_pane($type) { + switch ($type) { + case 'main' : + return array( + 'home'=>array( + 'title'=>_('Home'), + 'link'=>sprintf('href="index.php" title="%s"',_('Home')), + 'image'=>sprintf('%s',IMGDIR,_('Home'))), + + 'purge'=>array( + 'title'=>_('Purge caches'), + 'link'=>sprintf('href="cmd.php?cmd=purge_cache" onclick="return displayAJ(\'BODY\',\'cmd=purge_cache\',\'%s\');" title="%s"', + _('Clearing cache'),_('Purge caches')), + 'image'=>sprintf('%s',IMGDIR,_('Purge caches'))), + + 'appearance:hide_debug_info'=>array( + 'title'=>_('Show Cache'), + 'link'=>sprintf('href="cmd.php?cmd=show_cache" onclick="return displayAJ(\'BODY\',\'cmd=show_cache\',\'%s\');" title="%s"', + _('Loading'),_('Show Cache'),_('Show Cache')), + 'image'=>sprintf('%s',IMGDIR,_('Show Cache'))), + ); + break; + + case 'top' : + return array(); + break; + } +} + +/** + * This function dumps the $variable for debugging purposes + * + * @param string|array Variable to dump + * @param boolean Whether to stop execution or not. + */ +function debug_dump($variable,$die=false,$onlydebugaddr=false) { + if ($onlydebugaddr && + isset($_SESSION[APPCONFIG]) && $_SESSION[APPCONFIG]->getValue('debug','addr') && + $_SERVER['HTTP_X_FORWARDED_FOR'] != $_SESSION[APPCONFIG]->getValue('debug','addr') && + $_SERVER['REMOTE_ADDR'] != $_SESSION[APPCONFIG]->getValue('debug','addr')) + return; + + $backtrace = debug_backtrace(); + $caller['class'] = isset($backtrace[0]['class']) ? $backtrace[0]['class'] : 'N/A'; + $caller['function'] = isset($backtrace[0]['function']) ? $backtrace[0]['function'] : 'N/A'; + $caller['file'] = isset($backtrace[0]['file']) ? $backtrace[0]['file'] : 'N/A'; + $caller['line'] = isset($backtrace[0]['line']) ? $backtrace[0]['line'] : 'N/A'; + $caller['debug'] = $variable; + + print '
';
+	print_r($caller);
+	print '
'; + + if ($die) + die(); +} + +/** + * This function generates a backtrace + * + * @param boolean Whether to stop execution or not. + */ +function debug_dump_backtrace($msg='Calling BackTrace',$die=false) { + error($msg,'note',null,$die,true); +} + +/** + * Send a debug as a sys message + */ +function debug_sysmsg($msg) { + system_message(array('title'=>_('Debug'),'body'=>$msg,'type'=>'debug')); +} + +/** + * Debug Logging to Syslog + * + * The global debug level is turned on in your configuration file by setting: + * + * $config->custom->debug['level'] = 255; + * + * together with atleast one output direction (currently file and syslog are supported). + * + * $config->custom->debug['file'] = '/tmp/app_debug.log'; + * $config->custom->debug['syslog'] = true; + * + * + * The debug level is turned into binary, then if the message levels bit is on + * the message will be sent to the debug log. (Thus setting your debug level to 255, + * all bits on, will results in all messages being printed.) + * + * The message level bits are defined here. + * 0( 1) = Entry/Return results from function calls. + * 1( 2) = Configuration Processing + * 2( 4) = Template Processing + * 3( 8) = Schema Processing + * 4( 16) = Server Communication + * 5( 32) = Menu Processing + * 7( 64) = Other non generic messages + * 8(128) = Page Processing + * @param string Message to send to syslog + * @param int Log bit number for this message. + * @see syslog.php + */ +function debug_log($msg,$level=0) { + global $debug_file; + + # Temporary, to catch when these are not set in the function arguments. + $file = __FILE__; + $line = __LINE__; + $method = __METHOD__; + + # In case we are called before we are fully initialised or if debugging is not set. + if (! isset($_SESSION[APPCONFIG]) || ! ($_SESSION[APPCONFIG]->getValue('debug','file') + || $_SESSION[APPCONFIG]->getValue('debug','syslog'))) + return false; + + $debug_level = $_SESSION[APPCONFIG]->getValue('debug','level'); + if (! $debug_level || (! ($level & $debug_level))) + return; + + $debugaddr = false; + if ($_SESSION[APPCONFIG]->getValue('debug','addr')) { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] == $_SESSION[APPCONFIG]->getValue('debug','addr')) + $debugaddr = true; + + elseif ($_SERVER['REMOTE_ADDR'] == $_SESSION[APPCONFIG]->getValue('debug','addr')) + $debugaddr = true; + } else $debugaddr = true; + + if (! $debugaddr) + return; + + # If we are limiting debug to a browser, then check that + $caller = basename( $_SERVER['PHP_SELF'] ); + + if (func_num_args() > 2) { + $args = func_get_args(); + array_shift($args); + array_shift($args); + + # This is temporary, until we change all the debug_log statements. + if (is_string($args[0]) && preg_match('/.php$/',$args[0])) { + $file = array_shift($args); + $line = array_shift($args); + $method = array_shift($args); + } + + $fargs = array(); + foreach ($args as $key) { + if (is_array($key) || is_object($key)) + array_push($fargs,serialize($key)); + else + array_push($fargs,$key); + } + $msg = vsprintf($msg, array_values($fargs)); + } + + if (function_exists('stopwatch')) + $timer = stopwatch(); + else + $timer = null; + + $debug_message = sprintf('[%2.3f] %3s-%s(%04s): %s: %s',$timer,$level,basename($file),$line,$method,substr($msg,0,200)); + + if ($debug_file || $_SESSION[APPCONFIG]->getValue('debug','file')) { + if (! $debug_file) + $debug_file = fopen($_SESSION[APPCONFIG]->getValue('debug','file'), + $_SESSION[APPCONFIG]->getValue('debug','append') ? 'a' : 'w'); + + fwrite($debug_file,$debug_message."\n"); + } + + if ($_SESSION[APPCONFIG]->getValue('debug','syslog')) + syslog_notice($debug_message); + + return syslog_notice( sprintf('%s(%s): %s',$caller,$level,$msg) ); +} + +/** + * Display an error message in the system message panel of the page. + */ +function error($msg,$type='note',$redirect=null,$fatal=false,$backtrace=false) { + global $www; + static $counter; + + # Just a check to see that we are called right. + if (! isset($www['page']) && ! $fatal) + die("Function error called incorrectly [$msg]"); + + # If the error is fatal, we'll need to stop here. + if (! isset($www['page'])) + $www['page'] = new page(); + + if ($fatal) + $www['page']->setsysmsg(array('title'=>_('Error'),'body'=>$msg,'type'=>$type)); + else + system_message(array('title'=>_('Error'),'body'=>$msg,'type'=>$type),$redirect); + + # Spin loop detection + if ($counter++ > 20) { + debug_dump('Spin loop detection.'); + debug_dump(array('msg'=>$msg,'session'=>$_SESSION['sysmsg'],'www'=>$www),1); + } + + # Do we have a backtrace to display? + if ($backtrace) { + $backtraceblock = new block(); + $backtraceblock->SetTitle('PHP Debug Backtrace'); + + $body = ''; + $body .= "\n"; + + foreach (debug_backtrace() as $error => $line) { + $_SESSION['backtrace'][$error]['file'] = isset($line['file']) ? $line['file'] : 'unknown'; + $_SESSION['backtrace'][$error]['line'] = isset($line['line']) ? $line['line'] : 'unknown'; + $body .= sprintf('', + _('File'),isset($line['file']) ? $line['file'] : $last['file'],isset($line['line']) ? $line['line'] : ''); + + $_SESSION['backtrace'][$error]['function'] = $line['function']; + $body .= sprintf(''; + $body .= "\n"; + + if (isset($line['file'])) + $last['file'] = $line['file']; + } + + $body .= '
%s%s (%s)
 %s%s', + _('Function'),$line['function']); + + if (isset($line['args'])) { + $display = strlen(serialize($line['args'])) < 50 ? serialize($line['args']) : substr(serialize($line['args']),0,50).'...'; + $_SESSION['backtrace'][$error]['args'] = $line['args']; + if (file_exists(LIBDIR.'../tools/unserialize.php')) + $body .= sprintf(' (%s)', + '../tools/unserialize.php',$error,$display); + else + $body .= sprintf(' (%s)',$display); + } + $body .= '
'; + $body .= "\n"; + $backtraceblock->SetBody($body); + + $www['page']->block_add('body',$backtraceblock); + } + + if ($fatal) { + $www['page']->display(array('menu'=>false)); + die(); + } +} + +/** + * Return the result of a form variable, with optional default + * + * @return The form GET/REQUEST/SESSION/POST variable value or its default + */ +function get_request($attr,$type='POST',$die=false,$default=null) { + switch($type) { + case 'GET': + $value = isset($_GET[$attr]) ? (is_array($_GET[$attr]) ? $_GET[$attr] : rawurldecode($_GET[$attr])) : $default; + break; + + case 'REQUEST': + $value = isset($_REQUEST[$attr]) ? (is_array($_REQUEST[$attr]) ? $_REQUEST[$attr] : rawurldecode($_REQUEST[$attr])) : $default; + break; + + case 'SESSION': + $value = isset($_SESSION[$attr]) ? (is_array($_SESSION[$attr]) ? $_SESSION[$attr] : rawurldecode($_SESSION[$attr])) : $default; + break; + + case 'POST': + default: + $value = isset($_POST[$attr]) ? (is_array($_POST[$attr]) ? $_POST[$attr] : rawurldecode($_POST[$attr])) : $default; + break; + } + + if ($die && is_null($value)) + system_message(array( + 'title'=>_('Generic Error'), + 'body'=>sprintf('%s: Called "%s" without "%s" using "%s"', + basename($_SERVER['PHP_SELF']),get_request('cmd','REQUEST'),$attr,$type), + 'type'=>'error'), + 'index.php'); + + return $value; +} + +/** + * Record a system message. + * This function can be used as an alternative to generate a system message, if page hasnt yet been defined. + */ +function system_message($msg,$redirect=null) { + if (! is_array($msg)) + return null; + + if (! isset($msg['title']) && ! isset($msg['body'])) + return null; + + if (! isset($msg['type'])) + $msg['type'] = 'info'; + + if (! isset($_SESSION['sysmsg']) || ! is_array($_SESSION['sysmsg'])) + $_SESSION['sysmsg'] = array(); + + # Try and detect if we are in a redirect loop + if (get_request('redirect','GET') && $msg['type'] != 'debug') { + foreach ($_SESSION['sysmsg'] as $detail) { + if ($msg == $detail && ! isset($detail['special'])) { + debug_dump($_SESSION['sysmsg']); + debug_dump_backtrace('Redirect Loop Detected',true); + } + } + } + + array_push($_SESSION['sysmsg'],$msg); + + if ($redirect) { + if (preg_match('/\?/',$redirect)) + $redirect .= '&'; + else + $redirect .= '?'; + $redirect .= 'redirect=true'; + + # Check if we were an ajax request, and only render the ajax message + if (get_request('meth','REQUEST') == 'ajax') + $redirect .= '&meth=ajax'; + + header("Location: $redirect"); + die(); + } +} + +/** + * Other Functions + */ + +/** + * Encryption using blowfish algorithm + * + * @param string Original data + * @param string The secret + * @return string The encrypted result + * @author lem9 (taken from the phpMyAdmin source) + */ +function blowfish_encrypt($data,$secret=null) { + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s)',1,__FILE__,__LINE__,__METHOD__, + $data,$secret); + + # If our secret is null or blank, get the default. + if ($secret === null || ! trim($secret)) + $secret = $_SESSION[APPCONFIG]->getValue('session','blowfish'); + + # If the secret isnt set, then just return the data. + if (! trim($secret)) + return $data; + + if (file_exists(LIBDIR.'blowfish.php')) + require_once LIBDIR.'blowfish.php'; + else + return $data; + + $pma_cipher = new Horde_Cipher_blowfish; + $encrypt = ''; + + for ($i=0; $iencryptBlock($block, $secret); + } + return base64_encode($encrypt); +} + +/** + * Decryption using blowfish algorithm + * + * @param string Encrypted data + * @param string The secret + * @return string Original data + * @author lem9 (taken from the phpMyAdmin source) + */ +function blowfish_decrypt($encdata,$secret=null) { + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s)',1,__FILE__,__LINE__,__METHOD__, + $encdata,$secret); + + # This cache gives major speed up for stupid callers :) + static $CACHE = array(); + + if (isset($CACHE[$encdata])) + return $CACHE[$encdata]; + + # If our secret is null or blank, get the default. + if ($secret === null || ! trim($secret)) + $secret = $_SESSION[APPCONFIG]->getValue('session','blowfish'); + + # If the secret isnt set, then just return the data. + if (! trim($secret)) + return $encdata; + + if (file_exists(LIBDIR.'blowfish.php')) + require_once LIBDIR.'blowfish.php'; + else + return $data; + + $pma_cipher = new Horde_Cipher_blowfish; + $decrypt = ''; + $data = base64_decode($encdata); + + for ($i=0; $idecryptBlock(substr($data, $i, 8), $secret); + + $return = trim($decrypt); + $CACHE[$encdata] = $return; + return $return; +} + +/** + * String padding + * + * @param string Input string + * @param integer Length of the result + * @param string The filling string + * @param integer Padding mode + * @return string The padded string + */ +function full_str_pad($input,$pad_length,$pad_string='',$pad_type=0) { + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__, + $input,$pad_length,$pad_string,$pad_type); + + $str = ''; + $length = $pad_length - strlen($input); + + if ($length > 0) { // str_repeat doesn't like negatives + if ($pad_type == STR_PAD_RIGHT) { // STR_PAD_RIGHT == 1 + $str = $input.str_repeat($pad_string, $length); + } elseif ($pad_type == STR_PAD_BOTH) { // STR_PAD_BOTH == 2 + $str = str_repeat($pad_string, floor($length/2)); + $str .= $input; + $str .= str_repeat($pad_string, ceil($length/2)); + } else { // defaults to STR_PAD_LEFT == 0 + $str = str_repeat($pad_string, $length).$input; + } + + } else { // if $length is negative or zero we don't need to do anything + $str = $input; + } + return $str; +} + +/** + * Returns the cached array of application server resources. + * + * Note that internally, this function utilizes a two-layer cache, + * one in memory using a static variable for multiple calls within + * the same page load, and one in a session for multiple calls within + * the same user session (spanning multiple page loads). + * + * @return Returns the cached attributed requested, + * or null if there is nothing cached.. + */ +function get_cached_item($index,$item,$subitem='null') { + # Set default return + $return = null; + + # Check config to make sure session-based caching is enabled. + if ($_SESSION[APPCONFIG]->getValue('cache',$item)) { + + global $CACHE; + if (isset($CACHE[$index][$item][$subitem])) { + if (DEBUG_ENABLED) + debug_log('Returning MEMORY cached [%s] (%s)',1,__FILE__,__LINE__,__METHOD__, + $item,$subitem); + + $return = $CACHE[$index][$item][$subitem]; + + } elseif (isset($_SESSION['cache'][$index][$item][$subitem])) { + if (DEBUG_ENABLED) + debug_log('Returning SESSION cached [%s] (%s)',1,__FILE__,__LINE__,__METHOD__, + $item,$subitem); + + $return = $_SESSION['cache'][$index][$item][$subitem]; + $CACHE[$index][$item][$subitem] = $return; + } + } + + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s,%s), Returning (%s)',1,__FILE__,__LINE__,__METHOD__, + $index,$item,$subitem,$return); + + return $return; +} + +/** + * Caches the specified $item for the specified $index. + * + * Returns true on success of false on failure. + */ +function set_cached_item($index,$item,$subitem='null',$data) { + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__, + $index,$item,$subitem,$data); + + # Check config to make sure session-based caching is enabled. + if ($_SESSION[APPCONFIG]->getValue('cache',$item)) { + global $CACHE; + + $CACHE[$index][$item][$subitem] = $data; + $_SESSION['cache'][$index][$item][$subitem] = $data; + return true; + + } else + return false; +} + +/** + * Deletes the cache for a specified $item for the specified $index + */ +function del_cached_item($index,$item,$subitem='null') { + global $CACHE; + + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__, + $index,$item,$subitem); + + # Check config to make sure session-based caching is enabled. + if (isset($_SESSION['cache'][$index][$item][$subitem])) + unset($_SESSION['cache'][$index][$item][$subitem]); + + if (isset($CACHE[$index][$item][$subitem])) + unset($CACHE[$index][$item][$subitem]); +} + +/** + * Utility wrapper for setting cookies, which takes into consideration + * application configuration values. On success, true is returned. On + * failure, false is returned. + * + * @param string The name of the cookie to set. + * @param string The value of the cookie to set. + * @param int (optional) The duration in seconds of this cookie. If unspecified, $cookie_time is used from config.php + * @param string (optional) The directory value of this cookie (see php.net/setcookie) + * @return boolean + */ +function set_cookie($name,$val,$expire=null,$dir=null) { + # Set default return + $return = false; + + if ($expire == null) { + $cookie_time = $_SESSION[APPCONFIG]->getValue('session','cookie_time'); + $expire = $cookie_time == 0 ? null : time() + $cookie_time; + } + + if ($dir == null) + $dir = dirname($_SERVER['PHP_SELF']); + + if (@setcookie($name,$val,$expire,$dir)) { + $_COOKIE[$name] = $val; + $return = true; + } + + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s,%s,%s), Returning (%s)',1,__FILE__,__LINE__,__METHOD__, + $name,$val,$expire,$dir,$return); + + return $return; +} + +/** + * Get a customized file for a server + * We don't need any caching, because it's done by PHP + * + * @param int The ID of the server + * @param string The requested filename + * + * @return string The customized filename, if exists, or the standard one + */ +function get_custom_file($index,$filename,$path) { + # Set default return + $return = $path.$filename; + $server = $_SESSION[APPCONFIG]->getServer($index); + + $custom = $server->getValue('custom','pages_prefix'); + if (! is_null($custom) && is_file(realpath($path.$custom.$filename))) + $return = $path.$custom.$filename; + + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s,%s), Returning (%s)',1,__FILE__,__LINE__,__METHOD__, + $index,$filename,$path,$return); + + return $return; +} + +/** + * Sort a multi dimensional array. + * + * @param array Multi demension array passed by reference + * @param string Comma delimited string of sort keys. + * @param boolean Whether to reverse sort. + * @return array Sorted multi demension array. + */ +function masort(&$data,$sortby,$rev=0) { + if (DEBUG_ENABLED) + debug_log('Entered with (%s,%s,%s)',1,__FILE__,__LINE__,__METHOD__, + $data,$sortby,$rev); + + # if the array to sort is null or empty + if (! $data) return; + + static $CACHE = array(); + + if (empty($CACHE[$sortby])) { + $code = "\$c=0;\n"; + foreach (split(',',$sortby) as $key) { + $code .= "if (is_object(\$a) || is_object(\$b)) {\n"; + $code .= " if (\$a->$key != \$b->$key)\n"; + + if ($rev) + $code .= " return (\$a->$key < \$b->$key ? 1 : -1);\n"; + else + $code .= " return (\$a->$key > \$b->$key ? 1 : -1);\n"; + + $code .= "} else {\n"; + + $code .= "if ((! isset(\$a['$key'])) && (! isset(\$b['$key']))) return 0;\n"; + $code .= "if ((! isset(\$a['$key'])) && isset(\$b['$key'])) return -1;\n"; + $code .= "if (isset(\$a['$key']) && (! isset(\$b['$key']))) return 1;\n"; + + + $code .= "if (is_numeric(\$a['$key']) && is_numeric(\$b['$key'])) {\n"; + + $code .= " if (\$a['$key'] != \$b['$key'])\n"; + if ($rev) + $code .= " return (\$a['$key'] < \$b['$key'] ? 1 : -1);\n"; + else + $code .= " return (\$a['$key'] > \$b['$key'] ? 1 : -1);\n"; + + $code .= "} else {\n"; + + if ($rev) + $code .= " if ( (\$c = strcasecmp(\$b['$key'],\$a['$key'])) != 0 ) return \$c;\n"; + else + $code .= " if ( (\$c = strcasecmp(\$a['$key'],\$b['$key'])) != 0 ) return \$c;\n"; + $code .= "}}\n"; + } + $code .= 'return $c;'; + $CACHE[$sortby] = create_function('$a, $b',$code); + } + + uasort($data,$CACHE[$sortby]); +} + +/** + * Is compression enabled for output + */ +function isCompress() { + return (isset($_SESSION[APPCONFIG]) && $_SESSION[APPCONFIG]->getValue('appearance','compress') + && ! ini_get('zlib.output_compression') + && eregi('gzip',$_SERVER['HTTP_ACCEPT_ENCODING'])); +} +?> diff --git a/lib/functions.tsm.php b/lib/functions.tsm.php new file mode 100644 index 0000000..0b070a3 --- /dev/null +++ b/lib/functions.tsm.php @@ -0,0 +1,330 @@ +getIndex()]['stgps']->isPrimaryPool($pool); +} + +function isCopyPool($pool) { + global $app; + + objectCache('stgps'); + + return $_SESSION['cache'][$app['server']->getIndex()]['stgps']->isCopyPool($pool); +} + +function getReclaim($pool) { + global $app; + + objectCache('stgps'); + + return $_SESSION['cache'][$app['server']->getIndex()]['stgps']->getReclaim($pool); +} + +function getReUse($pool) { + global $app; + + objectCache('stgps'); + + return $_SESSION['cache'][$app['server']->getIndex()]['stgps']->getReUse($pool); +} + +function getVolume($vol) { + global $app; + + objectCache('volumes'); + + return $_SESSION['cache'][$app['server']->getIndex()]['volumes']->getVolume($vol); +} + +function isLibraryDevClass($devclass) { + global $app; + + objectCache('devclasses'); + + return $_SESSION['cache'][$app['server']->getIndex()]['devclasses']->isLibraryDevClass($devclass); +} + +function LibraryDevClass($devclass) { + global $app; + + objectCache('devclasses'); + + return $_SESSION['cache'][$app['server']->getIndex()]['devclasses']->LibraryDevClass($devclass); +} + +/** + * Return the name that a cookie will be stored by the browser. + * @param int Server ID. + * @param string Name of Cookie. + * @return string Fully qualified cookie name. + */ +function cookie_name($index,$name) { + global $app; + + return $app['server']->getValue('cookie','prefix').$index.$name; +} + +/** + * Returns the value of cookie + * @param int Server ID + * @param string Name + * @return string|false Value of cookie (null if not set/no value) + */ +function get_cookie($index,$name) { + global $_COOKIE; + + $cookie = cookie_name($index,$name); + + if (isset($_COOKIE[$cookie])) + return $_COOKIE[$cookie]; + else + return false; +} + +/** + * Store a cookie in the user browser. + * @param int Server ID + * @param string Name of Cookie + * @param string Value for Cookie + * @param int Time for cookie to live in seconds + * @param string dir Cookie Path + * @result boolean + */ +function store_cookie($index,$name,$val,$expire=null,$dir=null) { + global $app; + + $cookie_name = cookie_name($index,$name); + $cookie_time = $app['server']->getValue('cookie','time'); + + if ($expire == null) + $expire = ($cookie_time == 0 ? null : time() + $cookie_time); + + if ($dir == null) + $dir = dirname($app['server']->getValue('server','path') ? $app['server']->getValue('server','path') : '/'); + + if (setcookie($cookie_name,$val,$expire,$dir)) + return true; + else + return false; +} + +/** + * Unset all cookies on logout. + * @param int Server ID + * @result boolean True if successful, false if not + * @todo: No routine to call this. + */ +function unset_cookie($index) { + global $app; + global $_COOKIE; + + $expire = time()-3600; + + foreach ($_COOKIE as $cookie => $cookie_value) { + $cookie = preg_replace("/^".$app['server']->getValue('server','path').$index.'/','',$cookie); + if ($cookie == 'PHPSESSID') continue; + $result = store_cookie($index,$cookie,'',$expire) ; + + if (! $result) return false; + } + return true; +} + +/** + * Initialise JPgraph + * @param boolean Must initialise or fail with an error if unable to do so + */ +function initJPGraph($need=false) { + $jpgraph = $_SESSION[APPCONFIG]->getValue('lib','jpgraph'); + + if (! $jpgraph) + if ($need) + die(_('Your JPGRAPH setting in config.php is pointing to a directory that does not exist. Please fix your config.php')); + else + return false; + + if ((! file_exists($jpgraph.'/src/jpgraph.php')) || (! file_exists($jpgraph.'/src/jpgraph_gantt.php'))) + if ($need) + die(sprintf(_('PTA was not able to find the JPGRAPH utility in the "%s" directory. Either your config.php is pointing to the wrong directory, or you havent installed JPGRAPH'),$jpgraph)); + else + return false; + + if (! function_exists('imagetypes') && ! function_exists('imagecreatefromstring')) + if ($need) + die(_('It seems that GD support is not available. Please add GD support to PTA.')); + else + return false; + + if (! is_dir($_SESSION[APPCONFIG]->getValue('image','path')) || ! is_writable($_SESSION[APPCONFIG]->getValue('image','path'))) + die(sprintf(_('The temporary path for JPGRAPH either doesnt exist or is not writable - you have it configured for %s.'),$_SESSION[APPCONFIG]->getValue('image','path'))); + + # When we get here, we have all the pre-reqs. + error_reporting(0); + require_once $jpgraph.'/src/jpgraph.php'; + require_once $jpgraph.'/src/jpgraph_gantt.php'; + require_once $jpgraph.'/src/jpgraph_line.php'; + require_once $jpgraph.'/src/jpgraph_bar.php'; + require_once $jpgraph.'/src/jpgraph_pie.php'; + require_once $jpgraph.'/src/jpgraph_date.php'; + error_reporting(E_ALL); + return true; +} + +/** + * Put some HTML classes around strings containing values. + * @param string String to parse + * @param string Class to substitute + * @return string String with around %s + */ +function classValue($string,$class) { + return preg_replace('/(\%[0-9.]*[sf])/',"$1$2",$string); +} + +function objectCache($object,$force=false) { + global $app; + + $index = $app['server']->getIndex(); + + if (DEBUG_ENABLED) + debug_log('objectCache(): Entered with (%s,%s)',1,$object,isset($_SESSION['cache'][$index][$object])); + + if (isset($_SESSION['cache'][$index][$object])) { + if ($_SESSION['cache'][$index][$object]->expired() || $force) { + if (DEBUG_ENABLED) + debug_log('objectCache(): Object expired (%s,%s)',1,$_SESSION['cache'][$index][$object]->cache,time()); + + $_SESSION['cache'][$index][$object]->load(); + $_SESSION['cache'][$index][$object]->cache = time(); + } + + } else { + switch($object) { + case 'devclasses' : + $_SESSION['cache'][$index][$object] = new deviceClasses($index); + break; + + case 'drives' : + $_SESSION['cache'][$index][$object] = new drives($index); + break; + + case 'help' : + $_SESSION['cache'][$index][$object] = new help($index); + break; + + case 'libraries' : + $_SESSION['cache'][$index][$object] = new libraries($index); + break; + + case 'mgmtclasses' : + $_SESSION['cache'][$index][$object] = new mgmtClasses($index); + break; + + case 'nodes' : + $_SESSION['cache'][$index][$object] = new nodes($index); + break; + + case 'occupancy' : + $_SESSION['cache'][$index][$object] = new occupancy($index); + break; + + case 'stgps' : + $_SESSION['cache'][$index][$object] = new storagePools($index); + break; + + case 'summaryinfo' : + $_SESSION['cache'][$index][$object] = new summaryInfo($index); + break; + + case 'volumes' : + $_SESSION['cache'][$index][$object] = new volumes($index); + break; + + default: + error(sprintf('Unknown object %s',$object)); + } + + $_SESSION['cache'][$index][$object]->cache = time(); + } + + return $_SESSION['cache'][$index][$object]; +} + +function render_page($title,$body) { + $www = new page(); + + if (is_array($title)) { + foreach ($title as $key => $title) { + $block = new block(); + $block->SetTitle($title); + $block->SetBody($body[$key]); + $www->block_add('body',$block); + } + + } else { + $block = new block(); + $block->SetTitle($title); + $block->SetBody($body); + $www->block_add('body',$block); + } + $www->body(); +} +?> diff --git a/lib/menu.php b/lib/menu.php new file mode 100644 index 0000000..7d262ed --- /dev/null +++ b/lib/menu.php @@ -0,0 +1,49 @@ +index = $index; + } + + static public function getInstance($index) { + $menu = get_cached_item($index,'menu'); + if (! $menu) { + $menu = $_SESSION[APPCONFIG]->getValue('appearance','menu'); + eval('$menu = new '.$menu.'($index);'); + set_cached_item($index,'menu','null',$menu); + } + return $menu; + } + + /** + * Get the server Object for this tree + * + * @return object Server Object for this tree + */ + protected function getServer() { + return $_SESSION[APPCONFIG]->getServer($this->index); + } + + public function getDatastore() { + return isset($_SESSION[APPCONFIG]->datastore->object[$this->index]) ? $_SESSION[APPCONFIG]->datastore->object[$this->index] : null; + } + + /** + * Displays the Menu + */ + abstract public function draw(); +} +?> diff --git a/lib/menu_html.php b/lib/menu_html.php new file mode 100644 index 0000000..7c77b0a --- /dev/null +++ b/lib/menu_html.php @@ -0,0 +1,165 @@ +index = $index; + $this->servers = $_SESSION[APPCONFIG]->servers->Instance($this->index); + } + + /** + * Displays the menu in HTML + */ + public function draw() { + if (DEBUG_ENABLED) + debug_log('Entered with ()',33,__FILE__,__LINE__,__METHOD__); + + echo ''; + $this->draw_server_name(); + + $this->javascript = ''; + $javascript_id = 0; + + # Is the user logged in, if so, show the menu. + if ($this->servers->isLoggedIn('user')) { + $this->draw_menu(); + $this->draw_logged_in_user(); + + if ($this->servers->isReadOnly()) + printf('',$this->getDepth()+3-1,_('read only')); + else + printf('',$this->getDepth()+3); + + # Draw the menu items. + foreach ($_SESSION[APPCONFIG]->getCommandList() as $command) { + printf('',$this->getDepth()+3,' '); + echo ''; + echo "\n\n"; + + $this->draw_javascript(); + } + + protected function draw_server_name() { + echo ''; + printf('%s',_('Server')); + printf('',$this->getDepth()+3-1); + printf('%s',htmlspecialchars($this->servers->getValue('server','name'))); + + if ($this->servers->isLoggedIn('user')) { + $m = sprintf(_('Inactivity will log you off at %s'), + strftime('%H:%M',time() + ($this->servers->getValue('login','timeout')*60))); + printf(' %s',$m,$m); + } + echo ''; + } + + protected function draw_menu() { + $links = ''; + if (is_array($_SESSION[APPCONFIG]->getValue('menu','session'))) + foreach ($_SESSION[APPCONFIG]->getValue('menu','session') as $link => $title) { + if ($link = $this->menu_item($link)) + $links .= sprintf('%s',$link); + } + + # Finally add our logout link. + $links .= sprintf('%s',$this->get_logout_menu_item()); + + # Draw the quick-links below the server name: + if ($links) { + printf('',$this->getDepth()+3-1); + printf('%s
',$links); + echo ''; + } + } + + protected function menu_item($item) { + $menu = $_SESSION[APPCONFIG]->getValue('menu','session'); + + if (isset($menu[$item])) + return sprintf('%s
%s
', + $menu[$item]['text'],$menu[$item]['php'],$this->servers->getIndex(),IMGDIR,$menu[$item]['icon'],$item,$item); + else + return ' '; + } + + protected function get_logout_menu_item() { + global $app; + + $href = sprintf('cmd.php?cmd=logout&index=%s',$this->servers->getIndex()); + + return sprintf('%s
%s
', + _('Logout of this server'),htmlspecialchars($href),'images/logout.png',_('logout'),_('logout')); + } + + protected function draw_logged_in_user() { + if (! is_null($_SESSION[APPCONFIG]->getValue('appearance','logged_in_chars')) + && (strlen($this->servers->getLogin('user')) > $_SESSION[APPCONFIG]->getValue('appearance','logged_in_chars'))) { + + printf('%s%s %s...', + $this->getDepth()+3-1,_('Logged in as'),_(':'), + $this->servers->getLogin('user'), + substr($this->servers->getLogin('user'),0,$_SESSION[APPCONFIG]->getValue('appearance','logged_in_chars'))); + + } else + printf('%s%s %s', + $this->getDepth()+3-1,_('Logged in as'),_(':'),$this->servers->getLogin('user')); + } + + protected function draw_login_link() { + global $recently_timed_out_servers; + + $server = $this->getServer(); + + $href = htmlspecialchars( + sprintf('cmd.php?cmd=%s&index=%s',get_custom_file($server->getIndex(),'login_form',''),$this->servers->getIndex())); + + echo ''; + printf('%s',$href,_('login')); + printf('%s',$this->getDepth()+3-2,$href,_('Login').'...'); + echo ''; + + printf(' ',$this->getDepth()+3); + printf(' ',$this->getDepth()+3); + + # If the server recently timed out display the message + if (is_array($recently_timed_out_servers) && in_array($this->servers->getIndex(),$recently_timed_out_servers)) + printf('%s', + $this->getDepth()+3-1,_('(Session timed out. Automatically logged out.)')); + } + + protected function draw_javascript() { + if ($this->javascript) { + echo "\n"; + echo $this->javascript; + echo "\n"; + } + } + + /** + * Work out how long the deepest "opened" menu item is. + * This is used to dynamically build our cells for our menu table. + */ + protected function getDepth() { + return 1; + } +} +?> diff --git a/lib/page.php b/lib/page.php new file mode 100644 index 0000000..7653c2a --- /dev/null +++ b/lib/page.php @@ -0,0 +1,504 @@ + + protected $_head; + + # Settings for this application + protected $_app; + + # Default values array. + protected $_default; + + public function __construct($index=null) { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with [%s]',129,__FILE__,__LINE__,__METHOD__,$index); + + # To be defined in a configuration file. + $this->_app['title'] = app_name(); + + # Default Values for configurable items. + $this->_default['stylecss'] = CSSDIR.'style.css'; + $this->_default['logo'] = IMGDIR.'logo-small.png'; + $this->_default['sysmsg']['error'] = IMGDIR.'error-big.png'; + $this->_default['sysmsg']['warn'] = IMGDIR.'warn-big.png'; + $this->_default['sysmsg']['info'] = IMGDIR.'info-big.png'; + + # Capture any output so far (in case we send some headers below) - there shouldnt be any output anyway. + $preOutput = ''; + + # Try and work around if php compression is on, or the user has set compression in the config. + # type = 1 for user gzip, 0 for php.ini gzip. + $obStatus = ob_get_status(); + if (isset($obStatus['type']) && $obStatus['type'] && $obStatus['status']) { + $preOutput = ob_get_contents(); + ob_end_clean(); + } + + header('Content-type: text/html; charset="UTF-8"'); + if (isCompress()) { + header('Content-Encoding: gzip'); + + if (DEBUG_ENABLED) + debug_log('Sent COMPRESSED header to browser and discarded (%s)',129,__FILE__,__LINE__,__METHOD__,$preOutput); + } + + if (isset($_SESSION[APPCONFIG]) + && $_SESSION[APPCONFIG]->getValue('appearance','compress') + && ini_get('zlib.output_compression')) + $this->setsysmsg(array('title'=>_('Warning'),'body'=>_('WARNING: You cannot have PHP compression and application compression enabled at the same time. Please unset zlib.output_compression or set $config->custom->appearance[\'compress\']=false'),'type'=>'warn')); + + # Turn back on output buffering. + ob_start(); + + # Initial Values + $this->_pageheader[] .= ''."\n"; + $this->_pageheader[] .= '_pageheader[] .= '"http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">'."\n"; + $this->_pageheader[] .= "\n"; + $this->_pageheader[] .= ''."\n"; + $this->_pageheader[] .= "\n"; + + $this->_app['logo'] = $this->_default['logo']; + + if (! is_null($index)) + $this->_app['urlcss'] = sprintf('%s%s',CSSDIR,$_SESSION[APPCONFIG]->getValue('appearance','stylesheet')); + else + $this->_app['urlcss'] = sprintf('%s%s',CSSDIR,'style.css'); + + $this->index = $index; + } + + /* Add to the HTML Header */ + public function head_add($html) { + $this->_head[] .= $html; + } + + /* Print out the HTML header */ + private function pageheader_print() { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with ()',129,__FILE__,__LINE__,__METHOD__); + + # HTML prepage requirements. + foreach ($this->_pageheader as $line) + echo $line."\n"; + + # Page Title + echo ''; + + if (isset($_SESSION[APPCONFIG])) + printf('%s (%s) - %s', + $this->_app['title'],app_version(),$_SESSION[APPCONFIG]->getValue('appearance','page_title')); + else + printf('%s - %s',$this->_app['title'],app_version()); + + # Style sheet. + printf('',$this->_app['urlcss']); + printf('',IMGDIR); + + if (defined('JSDIR')) { + echo "\n"; + printf('',JSDIR); + echo "\n"; + } + + # HTML head requirements. + if (is_array($this->_head) && count ($this->_head)) + foreach ($this->_head as $line) + echo $line."\n"; + + echo ''; + echo "\n"; + } + + private function head_print() { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with ()',129,__FILE__,__LINE__,__METHOD__); + + if (isset($_SESSION[APPCONFIG])) + $pagetitle = $_SESSION[APPCONFIG]->getValue('appearance','page_title') ? ' - '.$_SESSION[APPCONFIG]->getValue('appearance','page_title') : ''; + else + $pagetitle = ''; + + echo ''; + + echo '
'; + printf('','#',$this->_app['logo']); + + echo ''; + echo '
'; + $empty = true; + if (function_exists('cmd_control_pane')) + foreach (cmd_control_pane('top') as $cmd => $cmddetails) { + $cmds = preg_split('/:/',$cmd); + + if (defined('APPCONFIG') && isset($_SESSION[APPCONFIG]) && method_exists($_SESSION[APPCONFIG],'isCommandAvailable')) + if ($_SESSION[APPCONFIG]->isCommandAvailable('all',$cmds)) { + if ((isset($cmddetails['enable']) && trim($cmddetails['enable'])) || ! isset($cmddetails['enable'])) { + if (! $empty) + echo ' '; + + printf('%s',$cmddetails['link'],$cmddetails['image']); + + $empty = false; + } + } + } + + if ($empty) + echo ' '; + echo '
'; + echo ''; + echo "\n"; + } + + private function control_print() { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with ()',129,__FILE__,__LINE__,__METHOD__); + + echo ''; + echo ''; + if ($empty) + echo ''; + + echo ''; + echo '
'; + + $empty = true; + if (function_exists('cmd_control_pane')) + foreach (cmd_control_pane('main') as $cmd => $cmddetails) { + $cmds = preg_split('/:/',$cmd); + + if (defined('APPCONFIG') && isset($_SESSION[APPCONFIG]) && method_exists($_SESSION[APPCONFIG],'isCommandAvailable')) + if ($_SESSION[APPCONFIG]->isCommandAvailable('all',$cmds)) { + if ((isset($cmddetails['enable']) && trim($cmddetails['enable'])) || ! isset($cmddetails['enable'])) { + if (! $empty) + echo ' | '; + + printf('%s',$cmddetails['link'], + $_SESSION[APPCONFIG]->getValue('appearance','control_icons') ? $cmddetails['image'] : $cmddetails['title']); + + $empty = false; + } + } + } + + echo ' 
'; + } + + protected function menu() { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with ()',129,__FILE__,__LINE__,__METHOD__); + + if (! isset($_SESSION[APPCONFIG])) + return; + + if (is_null($this->index)) + $this->index = min(array_keys($_SESSION[APPCONFIG]->getServerList())); + + if (count($_SESSION[APPCONFIG]->getServerList()) > 1) { + echo '
'; + echo '
'; + printf('%s%s
%s',_('Server Select'),_(':'), + server_select_list($this->index,false,'index',true,sprintf("onchange=\"menu_unhide('index',%s)\"",$this->index))); + echo '
'; + echo '
'; + echo "\n\n"; + } + + foreach ($_SESSION[APPCONFIG]->getServerList() as $index => $server) { + printf('
',$server->getIndex(),($server->getIndex() == $this->index) ? 'block' : 'none'); + $menu = menu::getInstance($server->getIndex()); + $menu->draw(); + echo '
'; + echo "\n\n"; + } + } + + public function block_add($side,$object) { + if (! is_object($object)) + error(sprintf('block_add called with [%s], but it is not an object',serialize($object))); + + $this->_block[$side][] = $object; + } + + private function block_print($side) { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with ()',129,__FILE__,__LINE__,__METHOD__); + + if (! isset($this->_block[$side])) + return; + + printf('',$side); + foreach ($this->_block[$side] as $object) + echo $object->draw($side); + echo ''; + } + + private function sysmsg() { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with ()',129,__FILE__,__LINE__,__METHOD__); + + if (isset($this->sysmsg)) { + foreach ($this->sysmsg as $index => $details) { + switch ($details['type']) { + case 'error': + $icon = $this->_default['sysmsg']['error']; + break; + + case 'warn': + $icon = $this->_default['sysmsg']['warn']; + break; + + case 'info': + default: + $icon = $this->_default['sysmsg']['info']; + break; + } + + if (isset($details['title'])) + printf('%s%s', + $icon,$details['type'],$details['title']); + + if (isset($details['body'])) + if (is_array($details['body'])) { + echo ''; + foreach ($details['body'] as $line) + printf('%s
',$line); + echo ''; + + } else + printf('%s',$details['body']); + } + } + } + + public function body() { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with ()',129,__FILE__,__LINE__,__METHOD__); + + # Add the Session System Messages + if (isset($_SESSION['sysmsg']) && is_array($_SESSION['sysmsg'])) { + foreach ($_SESSION['sysmsg'] as $msg) + $this->setsysmsg($msg); + + unset($_SESSION['sysmsg']); + } + + if (isset($this->sysmsg)) { + echo ''; + $this->sysmsg(); + echo '
'; + echo "\n"; + } + + if (isset($this->_block['body'])) { + foreach ($this->_block['body'] as $object) + echo $object->draw('body'); + } + } + + private function footer_print() { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with ()',129,__FILE__,__LINE__,__METHOD__); + + printf('%s
%s
%s', + isCompress() ? '[C]' : ' ', + app_version(), + ''); + } + + /** + * Only show a particular page frame - used by an AJAX call + */ + public function show($frame,$compress=false) { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with (%s)',129,__FILE__,__LINE__,__METHOD__,$compress); + + # If the body is called via AJAX, and compression is enable, we need to compress the output + if ($compress && ob_get_level() && isCompress()) { + ob_end_clean(); + ob_start(); + } + + switch ($frame) { + case 'BODY' : + $this->body(); + break; + + case 'MENU' : + $this->menu(); + break; + + default : + error(sprintf('show called with unknown frame [%s]',$frame),'error','index.php'); + } + + if ($compress && ob_get_level() && isCompress()) { + $output = ob_get_contents(); + ob_end_clean(); + + if (DEBUG_ENABLED) + debug_log('Sending COMPRESSED output to browser[(%s),%s]',129,__FILE__,__LINE__,__METHOD__, + strlen($output),$output); + + print gzencode($output); + } + } + + public function display($filter=array()) { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with [%s]',129,__FILE__,__LINE__,__METHOD__,$filter); + + # Control what is displayed. + $display = array( + 'HEAD'=>true, + 'CONTROL'=>true, + 'MENU'=>true, + 'FOOT'=>true + ); + + $display = array_merge($display,$filter); + + # HTML Header + $this->pageheader_print(); + + # Start of body + # Page Header + echo ''; + echo "\n"; + echo ''; + + if ($display['HEAD']) + $this->head_print(); + + # Control Line + if ($display['CONTROL']) { + echo ''; + echo "\n"; + } + + # Left Block + echo ''; + + if ($display['MENU']) { + echo ''; + } + + echo ''; + echo ''; + echo "\n"; + + # Page Footer + if ($display['FOOT']) + $this->footer_print(); + + # Finish HTML + echo '
'; + echo '
'; + $this->control_print(); + echo '
'; + echo '
'; + echo "\n"; + $this->body(); + echo '
'; + echo '
'; + echo ''; + echo ''; + + # compress output + if (ob_get_level() && isCompress()) { + $output = ob_get_contents(); + ob_end_clean(); + + if (DEBUG_ENABLED) + debug_log('Sending COMPRESSED output to browser[(%s),%s]',129,__FILE__,__LINE__,__METHOD__, + strlen($output),$output); + + print gzencode($output); + } + } + + public function setsysmsg($data) { + if (defined('DEBUG_ENABLED') && DEBUG_ENABLED) + debug_log('Entered with [%s]',129,__FILE__,__LINE__,__METHOD__,$data); + + if (! is_array($data)) + return; + + if (isset($this->sysmsg)) + $msgnum = count($this->sysmsg) + 1; + else + $msgnum = 1; + + foreach (array('title','body','type') as $index) + if (isset($data[$index])) + $this->sysmsg[$msgnum][$index] = $data[$index]; + } +} + +/** + * This class draws a block. + * + * @package leenooksApp + * @subpackage Page + */ +class block { + private $title; + private $body; + private $foot; + + public function __construct() { + } + + public function setTitle($html) { + $this->title = $html; + } + + public function setBody($html) { + $this->body = $html; + } + + public function setFooter($html) { + $this->foot = $html; + } + + public function draw($side) { + $output = ''; + + $output .= sprintf('',$side); + if (isset($this->body['title'])) + $output .= sprintf('',$this->title); + + if (isset($this->body['body'])) + $output .= sprintf('',$this->body); + + if (isset($this->body['footer'])) + $output .= sprintf('',$this->foot); + $output .= '
%s
%s
%s
'; + + return $output; + } +} +?> diff --git a/lib/page_pta.php b/lib/page_pta.php new file mode 100644 index 0000000..563952d --- /dev/null +++ b/lib/page_pta.php @@ -0,0 +1,21 @@ +_app['title'] = 'phpTSMadmin'; + } +} +?> diff --git a/lib/session_functions.php b/lib/session_functions.php new file mode 100644 index 0000000..f015017 --- /dev/null +++ b/lib/session_functions.php @@ -0,0 +1,185 @@ +_('Configuration cache stale.'), + 'body'=>_('Your configuration has been automatically refreshed.'), + 'type'=>'info','special'=>true)); + + $config_file = CONFDIR.'config.php'; + $config = check_config($config_file); + if (! $config) + debug_dump_backtrace('config is empty?',1); + + } else { + # Sanity check, specially when upgrading from a previous release. + if (isset($_SESSION['cache'])) + foreach (array_keys($_SESSION['cache']) as $id) + if (isset($_SESSION['cache'][$id]['tree']['null']) && ! is_object($_SESSION['cache'][$id]['tree']['null'])) + unset($_SESSION['cache'][$id]); + } + } + + # If we came via index.php, then set our $config. + if (! isset($_SESSION[APPCONFIG]) && isset($config)) + $_SESSION[APPCONFIG] = $config; + + # Restore our sysmsg's if there were any. + if ($sysmsg) { + if (! isset($_SESSION['sysmsg']) || ! is_array($_SESSION['sysmsg'])) + $_SESSION['sysmsg'] = array(); + + array_push($_SESSION['sysmsg'],$sysmsg); + } +} + +/** + * Stops the current session. + */ +function app_session_close() { + @session_write_close(); +} +?> diff --git a/lib/tsm_classes.php b/lib/tsm_classes.php new file mode 100644 index 0000000..b4ae5f9 --- /dev/null +++ b/lib/tsm_classes.php @@ -0,0 +1,1906 @@ +name; + } +} + +abstract class copyGroup extends base { + function __construct($name) { + $this->id = $name; + } + + abstract public function setDetails($details); + + public function setDefaultMgmtClass() { + $this->default = true; + } + + public function isDefaultMgmtClass() { + return $this->default; + } + + public function getStoragePool() { + $stgpools = objectCache('stgps'); + + if ($stgpools->ifExist($this->stgpool)) + return $stgpools->getStoragePool($this->stgpool); + else + return null; + } +} + +class copyGroupArchive extends copyGroup { + public function setDetails($details) { + $this->domain = $details['DOMAIN_NAME']; + #$this->xx = $details['SET_NAME']; + $this->name = $details['CLASS_NAME']; + #$this->xx = $details['COPYGROUP_NAME']; + $this->retain['DAYS'] = $details['RETVER']; + #$this->xx = $details['SERIALIZATION']; + $this->stgpool = $details['DESTINATION']; + #$this->xx = $details['CHG_TIME']; + #$this->xx = $details['CHG_ADMIN']; + #$this->xx = $details['PROFILE']; + #$this->xx = $details['RETINIT']; + #$this->xx = $details['RETMIN']; + + $this->default = false; + $this->type = 'Arch'; + } +} + +class copyGroupBackup extends copyGroup { + public function setDetails($details) { + $this->domain = $details['DOMAIN_NAME']; + #$this->xx = $details['SET_NAME']; + $this->name = $details['CLASS_NAME']; + #$this->xx = $details['COPYGROUP_NAME']; + $this->version['EXISTS'] = $details['VEREXISTS']; + $this->version['DELETED'] = $details['VERDELETED']; + $this->retain['EXTRA'] = $details['RETEXTRA']; + $this->retain['ONLY'] = $details['RETONLY']; + #$this->xx = $details['MODE']; + #$this->xx = $details['SERIALIZATION']; + $this->frequency = $details['FREQUENCY']; + $this->stgpool = $details['DESTINATION']; + #$this->xx = $details['TOC_DESTINATION']; + #$this->xx = $details['CHG_TIME']; + #$this->xx = $details['CHG_ADMIN']; + #$this->xx = $details['PROFILE']; + + $this->default = false; + $this->type = 'Bkup'; + } +} + +class deviceClass extends base { + function deviceClass($name) { + $this->name = $name; + } + + function setDetails($details) { + $this->access = $details['ACCESS_STRATEGY']; + #$this->xx = $details['STGPOOL_COUNT']; + $this->type = $details['DEVTYPE']; + #$this->xx = $details['FORMAT']; + $this->capacity = $details['CAPACITY']; + #$this->xx = $details['MOUNTLIMIT']; + #$this->xx = $details['MOUNTWAIT']; + #$this->xx = $details['MOUNTRETENTION']; + #$this->xx = $details['PREFIX']; + $this->library = $details['LIBRARY_NAME']; + $this->dir = $details['DIRECTORY']; + #$this->xx = $details['SERVERNAME']; + #$this->xx = $details['RETRYPERIOD']; + #$this->xx = $details['RETRYINTERVAL']; + #$this->xx = $details['SHARED']; + #$this->xx = $details['HLADDRESS']; + #$this->xx = $details['MINCAPACITY']; + #$this->xx = $details['WORM']; + #$this->xx = $details['SCALECAPACITY']; + #$this->xx = $details['LAST_UPDATE_BY']; + #$this->xx = $details['LAST_UPDATE']; + + # DISK types have a blank type - we'll set it. + if ($this->access == 'Random' && ! trim($this->type) && $this->name='DISK') + $this->type = 'DISK'; + } + + function LibraryDevClass() { + if (trim($this->library)) + return $this->library; + else + return false; + } + + function isLibraryDevClass() { + if (trim($this->library)) + return true; + else + return false; + } +} + +class drive extends base { + function drive($name) { + $this->name = $name; + } + + function setDetails($details) { + $this->library = $details['LIBRARY_NAME']; + $this->type = $details['DEVICE_TYPE']; + #$this->xx = $details['ONLINE']; + #$this->xx = $details['READ_FORMATS']; + #$this->xx = $details['WRITE_FORMATS']; + #$this->xx = $details['ELEMENT']; + #$this->xx = $details['ACS_DRIVE_ID']; + $this->status = $details['DRIVE_STATE']; + #$this->xx = $details['ALLOCATED_TO']; + #$this->xx = $details['LAST_UPDATE_BY']; + #$this->xx = $details['LAST_UPDATE']; + #$this->xx = $details['CLEAN_FREQ']; + #$this->xx = $details['DRIVE_SERIAL']; + $this->volume = $details['VOLUME_NAME']; + } + + function inLibraryName($library) { + if ($this->library == $library) + return true; + else + return false; + } +} + +class filesystem extends base { + function filesystem($name) { + $this->name = $name; + } + + function setDetails($details) { + #$this->xx = $details['NODE_NAME']; + $this->id = $details['FILESPACE_ID']; + $this->type = $details['FILESPACE_TYPE']; + $this->capacity = $details['CAPACITY']; + $this->util = $details['PCT_UTIL']; + $this->backup['start'] = $details['BACKUP_START']; + $this->backup['end'] = $details['BACKUP_END']; + #$this->xx = $details['DELETE_OCCURRED']; + #$this->xx = $details['UNICODE_FILESPACE']; + #$this->xx = $details['FILESPACE_HEXNAME']; + } + + function setOccupancy($details) { + $this->occupancy[$details->type][$details->getStoragePool()] = $details->files; + } + + function setVolumeUsage($details) { + switch ($details['COPY_TYPE']) { + case 'BACKUP' : $type = 'Bkup'; + break; + case 'ARCHIVE' : $type = 'Arch'; + break; + case 'SPACE MANAGED' : $type = 'HSM'; + break; + + default: + debug_dump(sprintf('Unknown Copy Type: %s',$details['COPY_TYPE']),1); + } + #$this->xx = $details['NODE_NAME']; + #$this->xx = $details['COPY_TYPE']; + #$this->xx = $details['FILESPACE_NAME']; + #$this->xx = $details['STGPOOL_NAME']; + #$this->xx = $details['VOLUME_NAME']; + $this->volume[$type][$details['STGPOOL_NAME']][$details['VOLUME_NAME']] = getVolume($details['VOLUME_NAME']); + #$this->xx = $details['FILESPACE_ID']; + } + + function getStoragePools() { + $result = array(); + + if (isset($this->occupancy)) + foreach ($this->occupancy as $type => $stgpools) + foreach ($stgpools as $stgpool => $details) + if (! in_array($stgpool,$result)) + $result[] = $stgpool; + + return $result; + } + + /** + * Return a list of VOLUMES used by this FILE SYSTEM + * @input string (STORAGE POOL NAME|NULL [for all storage pools]) + * @return array (VOLUME NAMES) + */ + function getVolumeUsage($stgp = null) { + $result = array(); + + if (isset($this->volume)) + foreach ($this->volume as $type => $stgpools) + foreach ($stgpools as $stgpool => $volumes) { + if ($stgp && $stgp != $stgpool) + continue; + foreach ($volumes as $volume => $details) + if (! in_array($volume,$result)) + $result[] = $volume; + } + + return $result; + } + + function getOccupancy($type='Bkup') { + if (isset($this->occupancy[$type])) + return $this->occupancy[$type]; + else + return null; + } + + function getVolumes($type='Bkup') { + if (isset($this->volume[$type])) + return $this->volume[$type]; + else + return array(); + } + + function getVolumesStgp($type,$stgp) { + $return = array(); + + foreach ($this->getVolumes($type) as $stgpool => $stgpdetails) { + foreach ($stgpdetails as $volid => $volume) { + if ($volume->getStoragePool() == $stgp) + $return[] = $volume; + } + } + + return $return; + } +} + +class helpText { + function helpText($command) { + $this->command = $command; + $this->description = ''; + $this->class = ''; + $this->example = ''; + $this->parameter = ''; + $this->related = ''; + } + + function addTitle($text) { + $this->title = $text; + } + + function addDescription($text) { + if (trim($text) && (! preg_match('/^\s*\*\s+/',$text))) + $this->description .= $text.' '; + else + if (trim($text)) + $this->description .= sprintf('
%s',$text); + else + $this->description .= '

'; + } + + function addClass($text) { + if (trim($text)) + $this->class .= $text.' '; + else + $this->class .= '

'; + } + + function addParameter($text) { + static $lastcommand; + + if (preg_match('/^\s+/',$text) && trim($lastcommand)) + $this->parameter[$lastcommand]['text'] .= $text.'
'; + + else { + if (trim($text)) { + $lastcommand = preg_replace('/(.*)\s+\(Required\)/',"$1",$text); + if ($lastcommand != $text) + $this->parameter[$lastcommand]['required'] = true; + $this->parameter[$lastcommand]['text'] = ''; + + } else { + $this->parameter[$lastcommand]['text'] .= '
'; + } + } + } + + function addSyntax($text) { + $this->syntax .= $text.'
'; + } + + function addExample($text) { + $this->example[] = $text; + } + + function addRelated($text) { + static $lastrelated; + + # If there are than 2 upper case chars together, assume a command. + $text = preg_replace('/^\s*/','',$text); + if (preg_match('/^\s*([A-Z]{2,}\s?)+/',$text,$matches)) { + + # $matches[0] has the command. + $command = preg_replace('/\s*$/','',$matches[0]); + $text = preg_replace("/${command}\s*/",'',$text); + $lastrelated = $command; + } + + if (! isset($this->related[$lastrelated])) + $this->related[$lastrelated] = ''; + + $this->related[$lastrelated] .= $text; + } + + function getTitle() { + return $this->title; + } + + function getDescription() { + global $app; + $help = objectCache('help',1); + + # Find any commands and make them hotlinks + foreach ($help->getCommands() as $command) + $this->description = preg_replace("/$command/", + sprintf('%s',$app['server']->index,htmlspecialchars($command),$command),$this->description); + return $this->description; + } + + function getClass() { + return $this->class; + } + + function getSyntax() { + foreach ($this->getParameterList() as $parm) + $this->syntax = preg_replace("/-$parm-/", + sprintf('-%s-',$parm,$parm),$this->syntax); + + return "
".$this->syntax."
"; + } + + function getParameterList() { + return array_keys($this->parameter); + } + + function getParameters() { + $return = ''; + foreach ($this->parameter as $parm => $text) + $return .= sprintf('', + $parm, + isset($text['required']) ? "$parm" : $parm,$text['text']); + $return .= '
%s
%s
'; + return $return; + } + + function getRelated() { + global $app; + $help = objectCache('help',1); + $commands = $help->getCommands(); + + $return = ''."\n"; + # Find any commands and make them hotlinks + foreach ($this->related as $command => $desc) { + if (in_array($command,$commands)) + $return .= sprintf('', + $app['server']->index,htmlspecialchars($command),$command,$desc)."\n"; + else + $return .= sprintf('', + $command,$desc)."\n"; + } + $return .= '
%s
%s
%s
%s
'."\n"; + + return $return; + } +} + +class library extends base { + function library($name,$server) { + $this->name = $name; + $this->index = $server; + } + + function setLibrary($details) { + $this->type = $details['LIBRARY_TYPE']; + #$this->xx = $details['ACS_ID']; + #$this->xx = $details['PRIVATE_CATEGORY']; + #$this->xx = $details['SCRATCH_CATEGORY']; + #$this->xx = $details['EXTERNAL_MGR']; + $this->shared = $details['SHARED']; + #$this->xx = $details['LANFREE']; + #$this->xx = $details['OBEYMOUNTRETENTION']; + #$this->xx = $details['PRIMARY_LIB_MGR']; + $this->autolabel = $details['AUTOLABEL']; + #$this->xx = $details['LAST_UPDATE_BY']; + #$this->xx = $details['LAST_UPDATE']; + #$this->xx = $details['LIBRARY_SERIAL']; + #$this->xx = $details['WORMSCRATCH_CAT']; + #$this->xx = $details['RESETDRIVES']; + } + + function setAttr($attr,$value) { + $this->attr[$attr] = $value; + } + + function getAttr($attr) { + if (isset($this->attr[$attr])) + return $this->attr[$attr]; + else + return null; + } + + function addSlots($details) { + $this->slots = $details; + } + + function addVolumes($details) { + $this->volumes = $details; + } + + function addDrives($details) { + $this->drives = $details; + } + + function getDrives() { + if (isset($this->drives)) + return $this->drives; + else + return null; + } + + function slotVolumes($checkedIn=true) { + $result = array(); + + if (isset($this->slots)) { + foreach ($this->slots as $element => $details) + if ($checkedIn && $details['status'] == 'Allocated') + $result[$element] = $details; + elseif (! $checkedIn && $details['status'] != 'Allocated') + $result[$element] = $details; + + } + + return $result; + } + + function getLibraryEmpty() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->isLibraryEmpty()) + $result[$volume] = $object; + } + + return $result; + } + + function getLibraryPending() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->isLibraryPending()) + $result[$volume] = $object; + } + + return $result; + } + + function getLibraryScratch() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->isLibraryScratch()) + $result[$volume] = $object; + } + + return $result; + } +} + +class mgmtclass extends base { + function __construct($name) { + $this->name = $name; + } + + function setDetails($details) { + $this->domain = $details['DOMAIN_NAME']; + #$this->xx = $details['SET_NAME']; + $this->name = $details['CLASS_NAME']; + $this->default = $details['DEFAULTMC'] == 'Yes' ? true : false; + #$this->xx = $details['DESCRIPTION']; + #$this->xx = $details['SPACEMGTECHNIQUE']; + #$this->xx = $details['AUTOMIGNONUSE']; + #$this->xx = $details['MIGREQUIRESBKUP']; + #$this->xx = $details['MIGDESTINATION']; + #$this->xx = $details['CHG_TIME']; + #$this->xx = $details['CHG_ADMIN']; + #$this->xx = $details['PROFILE']; + } +} + +class node extends base { + function node($name) { + $this->name = $name; + } + + function setDetails($details) { + $this->os = $details['PLATFORM_NAME']; + $this->domain = $details['DOMAIN_NAME']; + $this->time['pwset'] = $details['PWSET_TIME']; + $this->passwd['invalid'] = $details['INVALID_PW_COUNT']; + $this->contact = $details['CONTACT']; + $this->compression = $details['COMPRESSION'] == 'NO' ? false : true; + $this->delete['arch'] = $details['ARCHDELETE'] == 'NO' ? false : true; + $this->delete['back'] = $details['BACKDELETE'] == 'NO' ? false : true; + $this->locked = $details['LOCKED'] == 'NO' ? false : true; + $this->time['lastacc'] = $details['LASTACC_TIME']; + $this->time['registered'] = $details['REG_TIME']; + #$this->xx = $details['REG_ADMIN']; + #$this->xx = $details['LASTSESS_COMMMETH']; + #$this->xx = $details['LASTSESS_RECVD']; + #$this->xx = $details['LASTSESS_SENT']; + #$this->xx = $details['LASTSESS_DURATION']; + #$this->xx = $details['LASTSESS_IDLEWAIT']; + #$this->xx = $details['LASTSESS_COMMWAIT']; + #$this->xx = $details['LASTSESS_MEDIAWAIT']; + $this->level['tsm_ver'] = $details['CLIENT_VERSION']; + $this->level['tsm_rel'] = $details['CLIENT_RELEASE']; + $this->level['tsm_lvl'] = $details['CLIENT_LEVEL']; + $this->level['tsm_slv'] = $details['CLIENT_SUBLEVEL']; + $this->level['os'] = $details['CLIENT_OS_LEVEL']; + $this->cloptset = $details['OPTION_SET']; + #$this->xx = $details['AGGREGATION']; + $this->url = $details['URL']; + #$this->xx = $details['NODETYPE']; + $this->passwd['expiry'] = $details['PASSEXP']; + $this->mp['keep'] = $details['KEEP_MP'] == 'NO' ? false : true; + $this->mp['max'] = $details['MAX_MP_ALLOWED']; + #$this->xx = $details['AUTO_FS_RENAME']; + #$this->xx = $details['VALIDATEPROTOCOL']; + $this->hostname = $details['TCP_NAME']; + $this->ipaddr = $details['TCP_ADDRESS']; + #$this->xx = $details['GUID']; + $this->txngroupmax = $details['TXNGROUPMAX']; + #$this->xx = $details['DATAWRITEPATH']; + #$this->xx = $details['DATAREADPATH']; + #$this->xx = $details['SESSION_INITIATION']; + #$this->xx = $details['CLIENT_HLA']; + #$this->xx = $details['CLIENT_LLA']; + $this->group = $details['COLLOCGROUP_NAME']; + #$this->xx = $details['PROXY_TARGET']; + #$this->xx = $details['PROXY_AGENT']; + } + + function addFileSpace($details) { + $this->fs[$details['FILESPACE_ID']] = new filesystem($details['FILESPACE_NAME']); + $this->fs[$details['FILESPACE_ID']]->setDetails($details); + } + + function addOccupancy($details) { + $this->fs[$details->filespace_id]->setOccupancy($details); + } + + function addVolumeUsage($details) { + $this->fs[$details['FILESPACE_ID']]->setVolumeUsage($details); + } + + function getFileSystems() { + if (isset($this->fs)) + return $this->fs; + else + return array(); + } + + /** + * Return a list of ALL STORAGE POOLS that this node has data in. + * @return: array (STORAGE POOL NAMES) + */ + function getStoragePools() { + $result = array(); + + if (isset($this->fs)) + foreach ($this->fs as $fs => $object) { + $stgpools = $object->getStoragePools(); + $result = array_unique(array_merge($result,$stgpools)); + } + + return $result; + } + + /** + * Return a list of ALL VOLUMES that this node has data on. + * @return: array (VOLUME NAMES) + */ + function getVolumeUsage() { + $result = array(); + + if (isset($this->fs)) + foreach ($this->fs as $fs => $object) { + $vols = $object->getVolumeUsage(); + $result = array_unique(array_merge($result,$vols)); + } + + return $result; + } + + /** + * Return a list of ALL PRIMARY VOLUMES that this node has data on. + * return: array (VOLUME OBJECTS) + */ + function getPrimaryVolumes($type='Bkup') { + $result = array(); + + if (isset($this->fs)) + foreach ($this->fs as $fs => $object) + foreach ($object->getVolumes($type) as $stgp => $volumes) + foreach ($volumes as $volume) + if ($volume->isPrimaryVol()) + $result[$volume->name] = $volume; + + return $result; + } + + /** + * Return a list of ALL COPYPOOL VOLUMES that this node has data on. + * return: array (VOLUME OBJECTS) + */ + function getCopyVolumes($type='Bkup') { + $result = array(); + + if (isset($this->fs)) + foreach ($this->fs as $fs => $object) + foreach ($object->getVolumes($type) as $stgp => $volumes) + foreach ($volumes as $volume) + if ($volume->isCopyVol()) + $result[$volume->name] = $volume; + + return $result; + } + + /** + * Return a list of ALL STORAGE POOLS by FILESYSTEM + * @return: array (FILE SYSTEM OBJECTS) + */ + function getFSOccupancy($type='Bkup') { + $result = array(); + + foreach ($this->fs as $fs => $object) + if (count($object->getOccupancy($type))) + $result[$fs] = $object; + + return $result; + } + + function getVolumes($type='Bkup') { + $result = array(); + + foreach ($this->fs as $fs => $object) + if (count($object->getVolumes($type))) + $result[$fs] = $object; + + return $result; + } + + function getOccupancy() { + $result = array(); + + if (isset($this->fs)) + foreach ($this->fs as $fs => $object) { + if (isset($object->occupancy)) + foreach ($object->occupancy as $type => $stgpools) { + foreach ($stgpools as $stgp => $details) { + @$result[$type][$stgp]['num'] += $details['num']; + @$result[$type][$stgp]['physical'] += $details['physical']; + @$result[$type][$stgp]['logical'] += $details['logical']; + } + } + } + + return $result; + } + + function getTapeVolumes($stgp,$type) { + $result = array(); + + if (! isset($this->fs)) + return null; + + foreach ($this->fs as $fsid => $fsobject) { + if (! isset($fsobject->volume)) + continue; + + foreach ($fsobject->getVolumesStgp($type,$stgp) as $vol => $voldetails) + $result[] = $voldetails; + } + + return $result; + } +} + +class occupant extends base { + function setDetails($details) { + $this->name = $details['NODE_NAME']; + $this->type = $details['TYPE']; + $this->filespace = $details['FILESPACE_NAME']; + $this->stgpool = $details['STGPOOL_NAME']; + $this->files['num'] = $details['NUM_FILES']; + $this->files['physical'] = $details['PHYSICAL_MB']; + $this->files['logical'] = $details['LOGICAL_MB']; + $this->filespace_id = $details['FILESPACE_ID']; + } + + function getNode() { + return ($this->name); + } + + function getStoragePool() { + return ($this->stgpool); + } + + function getType() { + return ($this->type); + } +} + +/** + * This class defines a volume. + */ +class storagePool extends base { + function storagePool($name) { + $this->name = $name; + } + + function setDetails($details) { + $this->type = $details['POOLTYPE']; + $this->devclass = $details['DEVCLASS']; + #$this->xx = $details['EST_CAPACITY_MB']; + #$this->xx = $details['TRIGGER_PCT_UTIL']; + $this->utilisation = $details['PCT_UTILIZED']; + $this->migratable = $details['PCT_MIGR']; + #$this->xx = $details['PCT_LOGICAL']; + $this->migr['hi'] = $details['HIGHMIG']; + $this->migr['low'] = $details['LOWMIG']; + #$this->xx = $details['MIGPROCESS']; + $this->nextstgp = $details['NEXTSTGPOOL']; + $this->maxsize = $details['MAXSIZE']; + $this->access = $details['ACCESS']; + #$this->xx = $details['DESCRIPTION']; + #$this->xx = $details['OVFLOCATION']; + $this->cache = $details['CACHE']; + $this->collocate = $details['COLLOCATE']; + $this->reclaim = $details['RECLAIM']; + $this->maxscratch = $details['MAXSCRATCH']; + #$this->xx = $details['NUMSCRATCHUSED']; + $this->reuse = $details['REUSEDELAY'] ? $details['REUSEDELAY'] : 0; + $this->migr['running'] = $details['MIGR_RUNNING'] == 'YES' ? true : false; + $this->migr['mb'] = $details['MIGR_MB']; + $this->migr['sec'] = $details['MIGR_SECONDS']; + $this->reclaimrunning = $details['RECL_RUNNING'] == 'YES' ? true : false; + #$this->xx = $details['CHG_TIME']; + #$this->xx = $details['CHG_ADMIN']; + $this->reclaimstgp = $details['RECLAIMSTGPOOL']; + #$this->xx = $details['MIGDELAY']; + #$this->xx = $details['MIGCONTINUE']; + #$this->xx = $details['DATAFORMAT']; + #$this->xx = $details['COPYSTGPOOLS']; + #$this->xx = $details['COPYCONTINUE']; + #$this->xx = $details['CRCDATA']; + #$this->xx = $details['RECLAIMPROCESS']; + } + + function isPrimaryPool() { + if ($this->type == 'PRIMARY') + return true; + else + return false; + } + + function isCopyPool() { + if ($this->type == 'COPY') + return true; + else + return false; + } + + function getReclaim() { + return $this->reclaim; + } + + function getReUse() { + return $this->reuse; + } +} + +class summary extends base { + function summary($name) { + $this->name = $name; + } + + function setDetails($details) { + #$this->xx = $details['LAST_UPDATE']; + } +} + +/** + * This class defines a volume. + */ +class volume extends base { + function volume($name,$server) { + $this->name = $name; + $this->index = $server; + } + + function setDetails($details) { + $this->stgpool = isset($details['STGPOOL_NAME']) ? $details['STGPOOL_NAME'] : null; + $this->devclass = isset($details['DEVCLASS_NAME']) ? $details['DEVCLASS_NAME'] : null; + $this->estcap = isset($details['EST_CAPACITY_MB']) ? $details['EST_CAPACITY_MB'] : null; + $this->utilisation = isset($details['PCT_UTILIZED']) ? $details['PCT_UTILIZED'] : null; + $this->status['volume'] = isset($details['STATUS']) ? $details['STATUS'] : null; + $this->access = isset($details['ACCESS']) ? $details['ACCESS'] : null; + $this->reclaim = isset($details['PCT_RECLAIM']) ? $details['PCT_RECLAIM'] : null; + $this->scratch = isset($details['SCRATCH']) ? ($details['SCRATCH'] == 'YES' ? true : false) : null; + $this->mounted = isset($details['TIMES_MOUNTED']) ? $details['TIMES_MOUNTED'] : null; + $this->writepass = isset($details['WRITE_PASS']) ? $details['WRITE_PASS'] : null; + $this->last['read'] = isset($details['LAST_READ_DATE']) ? $details['LAST_READ_DATE'] : null; + $this->last['write'] = isset($details['LAST_WRITE_DATE']) ? $details['LAST_WRITE_DATE'] : null; + $this->pending['start'] = isset($details['PENDING_DATE']) ? $details['PENDING_DATE'] : null; + $this->error['read'] = isset($details['READ_ERRORS']) ? $details['READ_ERRORS'] : null; + $this->error['write'] = isset($details['WRITE_ERRORS']) ? $details['WRITE_ERRORS'] : null; + $this->error['status'] = isset($details['ERROR_STATE']) ? $details['ERROR_STATE'] : null; + $this->location = isset($details['LOCATION']) ? $details['LOCATION'] : null; + #$this->xx = $details['SCALEDCAP_APPLIED']; + #$this->xx = $details['NUM_SIDES']; + #$this->xx = $details['MVSLF_CAPABLE']; + #$this->xx = $details['CHG_TIME']; + #$this->xx = $details['CHG_ADMIN']; + #$this->xx = $details['BEGIN_RCLM_DATE']; + #$this->xx = $details['END_RCLM_DATE']; + } + + function setLibrary($details) { + $this->setLibraryName($details['LIBRARY_NAME']); + $this->status['library'] = $details['STATUS']; + $this->lib['owner'] = $details['OWNER']; + $this->lib['use'] = $details['LAST_USE']; + $this->lib['element'] = $details['HOME_ELEMENT']; + #$this->xx = $details['CLEANINGS_LEFT']; + #$this->xx = $details['DEVTYPE']; + #$this->xx = $details['MEDIATYPE']; + } + + function setDBBackup($details) { + $this->dbv['date'] = $details['DATE_TIME']; + $this->dbv['type'] = $details['TYPE']; + $this->dbv['series'] = $details['BACKUP_SERIES']; + $this->dbv['operation'] = $details['BACKUP_OPERATION']; + $this->dbv['sequence'] = $details['VOLUME_SEQ']; + $this->devclass = $details['DEVCLASS']; + $this->location = $details['LOCATION']; + #$this->xx = $details['UNIQUE']; + #$this->xx = $details['COMMAND']; + } + + /** + * Is volume in library + */ + function inLibrary() { + if (isset($this->lib['name']) && ! is_null($this->lib['name'])) + return true; + else + return false; + } + + function inLibraryName($library) { + if ($this->inLibrary() && $this->lib['name'] == $library) + return true; + else + return false; + } + + function isPrimaryVol() { + if (isset($this->stgpool)) + return isPrimaryPool($this->stgpool); + else + return false; + } + + function isCopyVol() { + if (isset($this->stgpool)) + return isCopyPool($this->stgpool); + else + return false; + } + + function isReadWrite() { + if (isset($this->access)) + return $this->access == 'READWRITE' ? true : false; + else + return null; + } + + function isOutOfLibrary() { + if (! $this->inLibrary() && LibraryDevClass($this->devclass)) + return true; + else + return null; + } + + function isOffSite() { + if (isset($this->access)) + return $this->access == 'OFFSITE' ? true : false; + else + return null; + } + + function isPending() { + if (isset($this->status['volume'])) + if ($this->status['volume'] == 'PENDING') { + $start = strtotime(tsmDate($this->pending['start'],'nomsec')); + $end = $start + getReUse($this->stgpool)*86400; + $this->pending['end'] = strftime('%Y-%m-%d %H:%M:%S',$end); + return true; + } else + return false; + else + return null; + } + + function isEmpty() { + if (isset($this->status['volume'])) + return $this->status['volume'] == 'EMPTY' ? true : false; + else + return null; + } + + function tobeReclaimed() { + if (isset($this->reclaim)) + return $this->reclaim > getReclaim($this->stgpool) ? true : false; + else + return null; + } + + function hasWriteErrors() { + if (isset($this->error['write'])) + return $this->error['write'] ? true : false; + else + return null; + } + + function hasReadErrors() { + if (isset($this->error['read'])) + return $this->error['read'] ? true : false; + else + return null; + } + + function lastWrite() { + if (isset($this->last['write']) && trim($this->last['write'])) + return strtotime(tsmDate($this->last['write'],'notime')); + else + return null; + } + + function lastRead() { + if (isset($this->last['read']) && trim($this->last['read'])) + return strtotime(tsmDate($this->last['read'],'notime')); + else + return null; + } + + function setLibraryName($name) { + $this->lib['name'] = $name; + } + + function setSlot($slot) { + $this->lib['slot'] = $slot; + } + + function getSlot() { + if (isset($this->lib['slot'])) + return $this->lib['slot']; + else + return null; + } + + function getLocation() { + if (isset($this->location)) + return $this->location; + else + return null; + } + + function getStoragePool() { + if (isset($this->stgpool)) + return $this->stgpool; + else + return null; + } + + function isLibraryEmpty() { + if ($this->inLibrary() && isset($this->status['volume']) && $this->status['volume'] == 'EMPTY') + return true; + else + return false; + } + + function isLibraryPending() { + if ($this->inLibrary() && isset($this->status['volume']) && $this->status['volume'] == 'PENDING') + return true; + else + return false; + } + + function isLibraryScratch() { + if ($this->inLibrary() && $this->status['library'] == 'Scratch') + return true; + else + return false; + } + + function isScratch() { + if ($this->scratch) + return true; + else + return false; + } + + function inElement($library,$element) { + if ($this->inLibraryName($library) && $this->lib['element'] == $element) + return true; + else + return false; + } +} + +class xtsm extends tsm { + # Record when information retrieved from TSM + public $cache = null; + + # Our TSM Server + protected $server = null; + + function expired() { + if ($this->cache+$_SESSION[APPCONFIG]->getValue('cache','time')< time() || (isset($this->expired) && $this->expired)) + return true; + else + return false; + } +} + +class deviceClasses extends xtsm { + var $devclasses = array(); + + function deviceClasses($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our devclass info. + function load() { + $this->devclasses = array(); + + # Get our defined device classes. + $result = $this->server->query('select * from DEVCLASSES',null,'DEVCLASS_NAME'); + if ($result) { + foreach ($result as $devc => $details) { + $this->devclasses[$devc] = new deviceClass($devc); + $this->devclasses[$devc]->setDetails($details); + } + } + } + + function isLibraryDevClass($devclass) { + if (isset($this->devclasses[$devclass])) + return $this->devclasses[$devclass]->isLibraryDevClass(); + else + return null; + } + + function LibraryDevClass($devclass) { + if (isset($this->devclasses[$devclass])) + return $this->devclasses[$devclass]->LibraryDevClass(); + else + return null; + } + + function getDevClasses() { + return $this->devclasses; + } +} + +/** + * This class defines all drives. + */ +class drives extends xtsm { + var $drives = array(); + + function drives($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our drive info. + function load() { + $this->drives = array(); + + # Get our defined drives. + $result = $this->server->query('select * from DRIVES',null,'DRIVE_NAME'); + if ($result) { + foreach ($result as $drive => $details) { + $this->drives[$drive] = new drive($drive); + $this->drives[$drive]->setDetails($details); + } + } + } + + function getDrives($library) { + $result = array(); + + foreach ($this->drives as $drive => $object) { + if ($object->inLibraryName($library)) + $result[$drive] = $object; + } + + return $result; + } +} + +# Get TSM Help +class help extends xtsm { + function help($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our help info. + function load() { + $this->commands = $this->server->query('help',null); + } + + function getCommands() { + return $this->commands; + } + + function getCommand($command) { + $helpText = new helpText($command); + $commandHelp = $this->server->query("help $command",null); + $helpText->raw = $commandHelp; + + # Skip the two lines, its just the command again. + array_shift($commandHelp); + array_shift($commandHelp); + + # The 2nd line is the command description. + $helpText->addTitle(array_shift($commandHelp)); + array_shift($commandHelp); + $mode = 'description'; + + # Work through the output. + foreach ($commandHelp as $line) { + if (preg_match('/^Privilege Class$/',$line)) + $mode = 'class'; + elseif (preg_match('/^Syntax$/',$line)) + $mode = 'syntax'; + elseif (preg_match('/^Parameters$/',$line)) + $mode = 'parameter'; + elseif (preg_match('/^Examples$/',$line)) + $mode = 'example'; + elseif (preg_match('/^Related Commands$/',$line)) + $mode = 'related'; + + if (! isset($skip[$mode])) + $skip[$mode] = 1; + else + $skip[$mode]++; + + # Cut out the 2nd or more blank lines. + if (trim($line)) + $isBlank[$mode] = false; + + if ((! trim($line)) && isset($isBlank[$mode]) && $isBlank[$mode]) + continue; + elseif (! trim($line)) + $isBlank[$mode] = true; + + switch ($mode) { + case 'description' : $helpText->addDescription($line); + break; + case 'class' : if ($skip[$mode] > 2) + $helpText->addClass($line); + break; + case 'syntax' : if ($skip[$mode] > 2) + $helpText->addSyntax($line); + break; + case 'parameter' : if ($skip[$mode] > 2) + $helpText->addParameter($line); + break; + case 'example' : $helpText->addExample($line); + break; + case 'related' : if ($skip[$mode] > 4) + $helpText->addRelated($line); + break; + default : debug_dump("Unknown Text [$line]",1); + } + } + + return $helpText; +// debug_dump(array('command'=>$command,'commandHelp'=>$commandHelp,'helpText'=>$helpText),1); + } +} + +class libraries extends xtsm { + # Our libraries. + var $libraries = array(); + + function libraries($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our library info. + function load() { + debug_log(sprintf('%s::load(): Entered (%s).',get_class($this),$this->cache),1); + + $this->libraries = array(); + + $result = $this->server->query('select * from LIBRARIES',null,'LIBRARY_NAME'); + if ($result) { + foreach ($result as $library => $details) { + if (! isset($this->libraries[$library])) + $this->libraries[$library] = new library($library,$this->server->index); + + $this->libraries[$library]->setLibrary($details); + } + } + + # Add our volumes & drives + objectCache('volumes'); + objectCache('drives'); + foreach ($this->libraries as $library) { + # Does this library support show slots? + if (in_array($library->type,array('SCSI'))) { + $result = $this->server->query(sprintf('show slots %s',$library->name),null,'Library'); + + if (! count($result)) { + error(sprintf('querying library %s didnt return any information - is the library operational?',$library->name),'error',false); + $this->expired = true; + } + + if (isset($result[$library->name])) { + $this->libraries[$library->name]->addSlots($result[$library->name]['SlotUsage']); + $this->libraries[$library->name]->setAttr('ProductId',$result[$library->name]['Product Id']); + #$this->libraries[$library->name]->setAttr('xx',$result[$library->name]['Support module']); + #$this->libraries[$library->name]->setAttr('xx',$result[$library->name]['Mount count']); + $this->libraries[$library->name]->setAttr('Drives',$result[$library->name]['Drives']); + $this->libraries[$library->name]->setAttr('Slots',$result[$library->name]['Slots']); + $this->libraries[$library->name]->setAttr('Changers',$result[$library->name]['Changers']); + $this->libraries[$library->name]->setAttr('IO',$result[$library->name]['Import/Exports']); + #$this->libraries[$library->name]->setAttr('xx',$result[$library->name]['Device']); + } + + $this->libraries[$library->name]->addVolumes($_SESSION['cache'][$this->server->index]['volumes']->libraryVolumes($library->name)); + $this->libraries[$library->name]->addDrives($_SESSION['cache'][$this->server->index]['drives']->getDrives($library->name)); + } + } + } + + function libVolumes($checkedin=true) { + $result = array(); + + $volumes = objectCache('volumes'); + + foreach ($this->libraries as $library) { + foreach ($library->slotVolumes($checkedin) as $element => $details) { + $result[$library->name][$element] = $volumes->getVolume($details['barcodelabel']); + $result[$library->name][$element]->setSlot($details['slot']); + $result[$library->name][$element]->setDetails($details); + $result[$library->name][$element]->setLibraryName($library->name); + } + } + + return $result; + } + + function getLibraries() { + return $this->libraries; + } +} + +class mgmtClasses extends xtsm { + var $copygroups = array(); + var $mgmtclasses = array(); + + function __construct() { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our devclass info. + function load() { + $this->copygroups = array(); + $this->mgmtclasses = array(); + + # Get our defined mgmt classes + $result = $this->server->query(sprintf("select * from MGMTCLASSES where SET_NAME='%s'",'ACTIVE'),null); + if ($result) { + foreach ($result as $id => $details) { + $this->mgmtclasses[$id] = new mgmtClass($id); + $this->mgmtclasses[$id]->setDetails($details); + } + } + + # Get our defined copy groups + # Copygroup name is always STANDARD + $result = $this->server->query(sprintf("select * from BU_COPYGROUPS where SET_NAME='%s' and COPYGROUP_NAME='%s'",'ACTIVE','STANDARD'),null); + if ($result) { + foreach ($result as $id => $details) { + $this->copygroups[$id] = new copyGroupBackup($id); + $this->copygroups[$id]->setDetails($details); + + # Set the default mgmtclass + if ($this->isDefaultMgmtClass($this->copygroups[$id]->name)) + $this->copygroups[$id]->setDefaultMgmtClass(); + } + } + + $result = $this->server->query(sprintf("select * from AR_COPYGROUPS where SET_NAME='%s' and COPYGROUP_NAME='%s'",'ACTIVE','STANDARD'),null); + if ($result) { + foreach ($result as $id => $details) { + $this->copygroups[$id] = new copyGroupArchive($id); + $this->copygroups[$id]->setDetails($details); + + # Set the default mgmtclass + if ($this->isDefaultMgmtClass($this->copygroups[$id]->name)) + $this->copygroups[$id]->setDefaultMgmtClass(); + } + } + } + + private function getMgmtClass($name) { + foreach ($this->mgmtclasses as $mgmtclass) + if ($mgmtclass->name == $name) + return $mgmtclass; + } + + private function isDefaultMgmtClass($name) { + $mgmtclass = $this->getMgmtClass($name); + + if ($mgmtclass) + return $mgmtclass->default; + else + return null; + } + + private function getCopyGroup($domain,$mgmtclass,$type='Bkup') { + foreach ($this->copygroups as $copygroup) + if ($copygroup->domain == $domain && $copygroup->name == $mgmtclass && $copygroup->type == $type) + return $copygroup; + + return null; + } + + public function getMgmtClasses($domain,$type='Bkup') { + $result = array(); + + foreach ($this->mgmtclasses as $mgmtclass) { + if ($mgmtclass->domain == $domain) + $result[$mgmtclass->name] = $this->getCopyGroup($mgmtclass->domain,$mgmtclass->name,$type); + } + + return $result; + } +} + +class nodes extends xtsm { + var $nodes = array(); + + function nodes($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our node info. + function load() { + $this->nodes= array(); + $occupancy = objectcache('occupancy'); + + # Get our defined nodes. + $result = $this->server->query('select * from NODES',null,'NODE_NAME'); + if ($result) { + foreach ($result as $node => $details) { + $this->nodes[$node] = new node($node); + $this->nodes[$node]->setDetails($details); + } + } + + # Get the file systems. + $result = $this->server->query('select * from FILESPACES',null); + if ($result) + foreach ($result as $details) + $this->nodes[$details['NODE_NAME']]->addFileSpace($details); + + # Get the file systems occupancy. + foreach ($this->getNodes() as $node) { + foreach ($occupancy->getOccupancy($node->name) as $details) + $this->nodes[$node->name]->addOccupancy($details); + } + + # Get the volume usage. + $result = $this->server->query('select * from VOLUMEUSAGE',null); + if ($result) + foreach ($result as $details) + $this->nodes[$details['NODE_NAME']]->addVolumeUsage($details); + } + + function getNodes() { + return $this->nodes; + } + + function getNode($node) { + if (isset($this->nodes[$node])) + return $this->nodes[$node]; + else + return null; + } +} + +/** + * This class obtains storage pool occupancy + */ +class occupancy extends xtsm { + var $occupancy = array(); + + function occupancy($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our drive info. + function load() { + $this->occupancy = array(); + $count = 0; + + # Get our defined drives. + $result = $this->server->query('select * from OCCUPANCY',null); + if ($result) { + foreach ($result as $details) { + $this->occupancy[$count] = new occupant(); + $this->occupancy[$count]->setDetails($details); + $count++; + } + } + } + + function getOccupancy($node) { + $result = array(); + + foreach ($this->occupancy as $details) { + if ($details->getNode() == $node) + $result[] = $details; + } + + return $result; + } + + function getStoragePoolTotals($stgp) { + $result = array(); + + foreach ($this->occupancy as $details) { + if ($details->getStoragePool() == $stgp) { + @$result[$details->getNode()][$details->getType()]['files'] = $details->files['num']; + @$result[$details->getNode()][$details->getType()]['physical'] = $details->files['physical']; + @$result[$details->getNode()][$details->getType()]['logical'] = $details->files['logical']; + } + } + + return $result; + } +} + +/** + * This class defines all storage pools. + */ +class storagePools extends xtsm { + var $stgpools = array(); + + function storagePools($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our storagepool info. + function load() { + $this->stgpools = array(); + + # Get our defined storagepools. + $result = $this->server->query('select * from STGPOOLS',null,'STGPOOL_NAME'); + if ($result) { + foreach ($result as $stgp => $details) { + $this->stgpools[$stgp] = new storagePool($stgp); + $this->stgpools[$stgp]->setDetails($details); + } + } + } + + function isPrimaryPool($pool) { + if (isset($this->stgpools[$pool])) + return $this->stgpools[$pool]->isPrimaryPool(); + else + return null; + } + + function isCopyPool($pool) { + if (isset($this->stgpools[$pool])) + return $this->stgpools[$pool]->isCopyPool(); + else + return null; + } + + function getReclaim($pool) { + if (isset($this->stgpools[$pool])) + return $this->stgpools[$pool]->getReclaim(); + else + return null; + } + + function getReUse($pool) { + if (isset($this->stgpools[$pool])) + return $this->stgpools[$pool]->getReUse(); + else + return null; + } + + function getStoragePools() { + return $this->stgpools; + } + + function ifExist($pool) { + if (isset($this->stgpools[$pool])) + return true; + else + return false; + } + + function getStoragePool($pool) { + if (isset($this->stgpools[$pool])) + return $this->stgpools[$pool]; + else + return null; + } +} + +class summaryInfo extends xtsm { + # Our summary info. + var $summaryInfo = array(); + + public function __construct($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our summary info. + public function load() { + $this->summaryInfo = $this->server->query('select START_TIME,END_TIME,ACTIVITY,ENTITY,SCHEDULE_NAME,SUCCESSFUL,MEDIAW,VOLUME_NAME,DRIVE_NAME,BYTES,LAST_USE,AFFECTED,NUMBER,EXAMINED,AFFECTED,FAILED,BYTES,IDLE from SUMMARY order by START_TIME,END_TIME',null); + } + + # Show media actions in the summary table. + function mediaActions($list=false) { + $result = array(); + + foreach ($this->summaryInfo as $index => $details) { + if (trim($details['MEDIAW']) || $details['BYTES'] || $details['AFFECTED']) + $result[$details['ACTIVITY']][$index] = $details; + } + + if ($list) + return array_keys($result); + else + return $result; + } + + /** + * Get the range of dates available for the summary table + */ + public function getSummaryRange() { + $sum['start'] = null; + $sum['end'] = null; + + foreach ($this->summaryInfo as $index => $details) { + if (($details['START_TIME'] < $sum['start']) || is_null($sum['start'])) + $sum['start'] = $details['START_TIME']; + + if (($details['END_TIME'] > $sum['end']) || is_null($sum['end'])) + $sum['end'] = $details['END_TIME']; + } + + $sum['starti'] = strtotime(tsmDate($sum['start'],'notime')); + $sum['endi'] = strtotime(tsmDate($sum['end'],'notime')); + + for ($i=$sum['starti']; $i <= $sum['endi']; $i+=86400) { + $sum['date'][] = date('Y-m-d',$i); + } + + return $sum['date']; + } + + function getSummary($start,$end) { + $result = array(); + + foreach ($this->summaryInfo as $index => $details) { + $sum['start'] = tsmDate($details['START_TIME'],'notime'); + $sum['end'] = tsmDate($details['END_TIME'],'notime'); + if (($sum['start'] >= $start && ! trim($sum['end'])) || + ($sum['start'] >= $start && $sum['end'] <= $end) || + (! trim($sum['start']) && $sum['end'] <= $end)) + + $result[$index] = $details; + } + return $result; + } +} + +/** + * This class defines all volumes. + */ +class volumes extends xtsm { + # Our volumes. + var $volumes = array(); + + function volumes($server) { + global $app; + + $this->server = $app['server']; + $this->load(); + } + + # Load our media info. + function load() { + $this->volumes = array(); + + # Get our defined volumes. + $result = $this->server->query('select * from VOLUMES',null,'VOLUME_NAME'); + if ($result) { + foreach ($result as $volume => $details) { + $this->volumes[$volume] = new volume($volume,$this->server->index); + $this->volumes[$volume]->setDetails($details); + } + } + + # Get our library volumes. + $result = $this->server->query('select * from LIBVOLUMES',null,'VOLUME_NAME'); + if ($result) { + foreach ($result as $volume => $details) { + if (! isset($this->volumes[$volume])) + $this->volumes[$volume] = new volume($volume,$this->server->index); + + $this->volumes[$volume]->setLibrary($details); + } + } + + # Get our DB volumes. + $result = $this->server->query("select * from VOLHISTORY where TYPE in ('BACKUPFULL','BACKUPINCR','DBSNAPSHOT') order by BACKUP_SERIES,BACKUP_OPERATION,VOLUME_SEQ",null,'VOLUME_NAME'); + if ($result) { + foreach ($result as $volume => $details) { + $this->volumes[$volume] = new volume($volume,$this->server->index); + $this->volumes[$volume]->setDBBackup($details); + $this->volumes[$volume]->setDetails($details); + } + } + } + + /** + * Return a list of COPY POOL volumes that are in the library + */ + function libCopyVolumes($inlib) { + $result = array(); + + foreach ($this->volumes as $volume => $object) + if ($object->isCopyVol() && isLibraryDevclass($object->devclass) && ($object->inLibrary() == $inlib)) + $result[$volume] = $object; + + return $result; + } + + /** + * Return a list of NON READ/WRITE volumes that need to be fixed. + */ + function nonReadWriteVolumes() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if (! $object->isReadWrite() && ! is_null($object->isReadWrite()) && ! $object->isOffSite() && + ! $object->isOutOfLibrary()) + + $result[$volume] = $object; + } + + return $result; + } + + /** + * Return a list of volumes that should be RECLAIMed. + */ + function reclaimVolumes() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->tobeReclaimed()) + $result[$volume] = $object; + } + + return $result; + } + + /** + * Return a list of volumes that have had errors. + */ + function errorVolumes() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->hasReadErrors() || $object->hasWriteErrors()) + $result[$volume] = $object; + } + + return $result; + } + + /** + * Return a list of volumes that have not been read/written to for a long time. + */ + function staleVolumes($days=0) { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ((! is_null($object->lastRead()) && ($object->lastRead() + $days*86400) < time()) || + (! is_null($object->lastWrite()) && ($object->lastWrite() + $days*86400) < time())) { + $result[$volume] = $object; + } + } + + return $result; + } + + /** + * Return a list of volumes that are PENDING. + */ + function pendingVolumes() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->isPending()) + $result[$volume] = $object; + } + + return $result; + } + + /** + * Return a list of volumes that are EMPTY. + */ + function emptyVolumes() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->isEmpty()) + $result[$volume] = $object; + } + + return $result; + } + + function libraryVolumes($library) { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->inLibraryName($library)) + $result[$volume] = $object; + } + + return $result; + } + + function getVolume($volume) { + if (isset($this->volumes[$volume])) + return $this->volumes[$volume]; + else + return new volume('Unknown Label',$this->server->index); + } + + function primaryNotInLib() { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if ($object->isOutOfLibrary() && $object->isPrimaryVol()) + $result[$volume] = $object; + } + + return $result; + } + + function getStoragePoolVolumes($stgp) { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if (isset($object->stgpool) && $object->stgpool == $stgp) + $result[$volume] = $object; + } + + return $result; + } + + function getStoragePoolScratchVolumes($stgp) { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if (isset($object->stgpool) && $object->stgpool == $stgp && $object->isScratch()) + $result[$volume] = $object; + } + + return $result; + } + + function getStoragePoolDefinedVolumes($stgp) { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if (isset($object->stgpool) && $object->stgpool == $stgp && ! $object->isScratch()) + $result[$volume] = $object; + } + + return $result; + } + + function getStoragePoolVolumesStatus($stgp,$status) { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if (isset($object->stgpool) && $object->stgpool == $stgp && $object->status['volume'] == $status) + $result[$volume] = $object; + } + + return $result; + } + + function getStoragePoolVolumesAccess($stgp,$access) { + $result = array(); + + foreach ($this->volumes as $volume => $object) { + if (isset($object->stgpool) && $object->stgpool == $stgp && $object->access == $access) + $result[$volume] = $object; + } + + return $result; + } + + function inElement($library,$element) { + foreach ($this->volumes as $volume => $object) { + if ($object->inElement($library,$element)) + return $object->name; + } + + return null; + } +} +?> diff --git a/tools/unserialize.php b/tools/unserialize.php new file mode 100644 index 0000000..beb555c --- /dev/null +++ b/tools/unserialize.php @@ -0,0 +1,15 @@ +'; + echo ''; + echo ''; + echo ''; +} +?>