From 7c0a9b20203e13a4da06fd79081215f712cceb38 Mon Sep 17 00:00:00 2001 From: Tim Laszlo Date: Thu, 14 Oct 2010 15:28:58 -0500 Subject: web reports: new skin --- reports/site_media/AnchorPosition.js | 147 ++ reports/site_media/CalendarPopup.js | 739 +++++++- reports/site_media/PopupWindow.js | 336 ++++ reports/site_media/base.css | 5 - reports/site_media/bcfg2.js | 26 + reports/site_media/bcfg2_base.css | 244 +++ reports/site_media/boxypastel.css | 230 --- reports/site_media/date.js | 335 ++++ reports/site_media/global.css | 10 - reports/site_media/layout.css | 44 - reports/site_media/main.js | 27 - reports/site_media/sorttable.js | 203 -- reports/site_media/syntax-coloring.css | 59 - reports/site_media/yui/dom/README | 75 - reports/site_media/yui/dom/dom-debug.js | 927 --------- reports/site_media/yui/dom/dom-min.js | 59 - reports/site_media/yui/dom/dom.js | 892 --------- reports/site_media/yui/event/README | 135 -- reports/site_media/yui/event/event-debug.js | 1797 ------------------ reports/site_media/yui/event/event-min.js | 69 - reports/site_media/yui/event/event.js | 1771 ------------------ reports/site_media/yui/round_tabs.css | 76 - reports/site_media/yui/tabview/README | 16 - .../site_media/yui/tabview/assets/border_tabs.css | 48 - reports/site_media/yui/tabview/assets/tabview.css | 69 - reports/site_media/yui/tabview/tabview-debug.js | 1964 -------------------- reports/site_media/yui/tabview/tabview-min.js | 61 - reports/site_media/yui/tabview/tabview.js | 1945 ------------------- reports/site_media/yui/yahoo/README | 45 - reports/site_media/yui/yahoo/yahoo-debug.js | 144 -- reports/site_media/yui/yahoo/yahoo-min.js | 12 - reports/site_media/yui/yahoo/yahoo.js | 143 -- setup.py | 14 +- src/lib/Server/Plugin.py | 4 +- src/lib/Server/Reports/reports/models.py | 120 +- src/lib/Server/Reports/reports/models.py.orig | 330 ++++ src/lib/Server/Reports/reports/models.py.rej | 15 + src/lib/Server/Reports/reports/templates/404.html | 8 + .../Reports/reports/templates/base-timeview.html | 25 + src/lib/Server/Reports/reports/templates/base.html | 122 +- .../reports/templates/clients/client-nodebox.html | 63 - .../Reports/reports/templates/clients/detail.html | 130 +- .../reports/templates/clients/detailed-list.html | 79 +- .../Reports/reports/templates/clients/history.html | 20 + .../Reports/reports/templates/clients/index.html | 61 +- .../Reports/reports/templates/clients/manage.html | 62 +- .../reports/templates/config_items/index.html | 100 - .../reports/templates/config_items/item.html | 109 ++ .../reports/templates/config_items/listing.html | 62 +- .../Reports/reports/templates/displays/index.html | 18 - .../displays/summary-block-direct-links.html | 7 - .../reports/templates/displays/summary-block.html | 90 - .../reports/templates/displays/summary.html | 63 +- .../reports/templates/displays/sys_view.html | 20 - .../Reports/reports/templates/displays/timing.html | 72 +- .../Server/Reports/reports/templates/index.html | 15 - .../reports/templates/widgets/filter_bar.html | 13 + .../reports/templates/widgets/interaction_list.inc | 38 + .../reports/templates/widgets/page_bar.html | 23 + .../Reports/reports/templatetags/bcfg2_tags.py | 239 +++ .../reports/templatetags/django_templating_sigh.py | 41 - .../reports/templatetags/syntax_coloring.py | 26 +- src/lib/Server/Reports/reports/urls.py | 55 + src/lib/Server/Reports/reports/views.py | 647 +++---- src/lib/Server/Reports/settings.py | 25 +- src/lib/Server/Reports/urls.py | 56 +- src/lib/Server/Reports/utils.py | 94 +- 67 files changed, 3538 insertions(+), 11981 deletions(-) create mode 100644 reports/site_media/AnchorPosition.js create mode 100644 reports/site_media/PopupWindow.js delete mode 100644 reports/site_media/base.css create mode 100644 reports/site_media/bcfg2.js create mode 100644 reports/site_media/bcfg2_base.css delete mode 100644 reports/site_media/boxypastel.css create mode 100644 reports/site_media/date.js delete mode 100644 reports/site_media/global.css delete mode 100644 reports/site_media/layout.css delete mode 100644 reports/site_media/main.js delete mode 100644 reports/site_media/sorttable.js delete mode 100644 reports/site_media/syntax-coloring.css delete mode 100644 reports/site_media/yui/dom/README delete mode 100644 reports/site_media/yui/dom/dom-debug.js delete mode 100644 reports/site_media/yui/dom/dom-min.js delete mode 100644 reports/site_media/yui/dom/dom.js delete mode 100644 reports/site_media/yui/event/README delete mode 100644 reports/site_media/yui/event/event-debug.js delete mode 100644 reports/site_media/yui/event/event-min.js delete mode 100644 reports/site_media/yui/event/event.js delete mode 100644 reports/site_media/yui/round_tabs.css delete mode 100644 reports/site_media/yui/tabview/README delete mode 100644 reports/site_media/yui/tabview/assets/border_tabs.css delete mode 100644 reports/site_media/yui/tabview/assets/tabview.css delete mode 100644 reports/site_media/yui/tabview/tabview-debug.js delete mode 100644 reports/site_media/yui/tabview/tabview-min.js delete mode 100644 reports/site_media/yui/tabview/tabview.js delete mode 100644 reports/site_media/yui/yahoo/README delete mode 100644 reports/site_media/yui/yahoo/yahoo-debug.js delete mode 100644 reports/site_media/yui/yahoo/yahoo-min.js delete mode 100644 reports/site_media/yui/yahoo/yahoo.js create mode 100644 src/lib/Server/Reports/reports/models.py.orig create mode 100644 src/lib/Server/Reports/reports/models.py.rej create mode 100644 src/lib/Server/Reports/reports/templates/404.html create mode 100644 src/lib/Server/Reports/reports/templates/base-timeview.html delete mode 100644 src/lib/Server/Reports/reports/templates/clients/client-nodebox.html create mode 100644 src/lib/Server/Reports/reports/templates/clients/history.html delete mode 100644 src/lib/Server/Reports/reports/templates/config_items/index.html create mode 100644 src/lib/Server/Reports/reports/templates/config_items/item.html delete mode 100644 src/lib/Server/Reports/reports/templates/displays/index.html delete mode 100644 src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html delete mode 100644 src/lib/Server/Reports/reports/templates/displays/summary-block.html delete mode 100644 src/lib/Server/Reports/reports/templates/displays/sys_view.html delete mode 100644 src/lib/Server/Reports/reports/templates/index.html create mode 100644 src/lib/Server/Reports/reports/templates/widgets/filter_bar.html create mode 100644 src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc create mode 100644 src/lib/Server/Reports/reports/templates/widgets/page_bar.html create mode 100644 src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py delete mode 100644 src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py create mode 100644 src/lib/Server/Reports/reports/urls.py diff --git a/reports/site_media/AnchorPosition.js b/reports/site_media/AnchorPosition.js new file mode 100644 index 000000000..7db0cc89e --- /dev/null +++ b/reports/site_media/AnchorPosition.js @@ -0,0 +1,147 @@ +// =================================================================== +// Author: Matt Kruse +// WWW: http://www.mattkruse.com/ +// +// NOTICE: You may use this code for any purpose, commercial or +// private, without any further permission from the author. You may +// remove this notice from your final code if you wish, however it is +// appreciated by the author if at least my web site address is kept. +// +// You may *NOT* re-distribute this code in any way except through its +// use. That means, you can include it in your product, or your web +// site, or any other form where the code is actually being used. You +// may not put the plain javascript up on your site for download or +// include it in your javascript libraries for download. +// If you wish to share this code with others, please just point them +// to the URL instead. +// Please DO NOT link directly to my .js files from your site. Copy +// the files to your server and use them there. Thank you. +// =================================================================== + +/* +AnchorPosition.js +Author: Matt Kruse +Last modified: 10/11/02 + +DESCRIPTION: These functions find the position of an tag in a document, +so other elements can be positioned relative to it. + +COMPATABILITY: Netscape 4.x,6.x,Mozilla, IE 5.x,6.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. + +FUNCTIONS: +getAnchorPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor. Position is relative to the PAGE. + +getAnchorWindowPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor, relative to the WHOLE SCREEN. + +NOTES: + +1) For popping up separate browser windows, use getAnchorWindowPosition. + Otherwise, use getAnchorPosition + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. +*/ + +// getAnchorPosition(anchorname) +// This function returns an object having .x and .y properties which are the coordinates +// of the named anchor, relative to the page. +function getAnchorPosition(anchorname) { + // This function will return an Object with x and y properties + var useWindow=false; + var coordinates=new Object(); + var x=0,y=0; + // Browser capability sniffing + var use_gebi=false, use_css=false, use_layers=false; + if (document.getElementById) { use_gebi=true; } + else if (document.all) { use_css=true; } + else if (document.layers) { use_layers=true; } + // Logic to find position + if (use_gebi && document.all) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_gebi) { + var o=document.getElementById(anchorname); + x=AnchorPosition_getPageOffsetLeft(o); + y=AnchorPosition_getPageOffsetTop(o); + } + else if (use_css) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_layers) { + var found=0; + for (var i=0; i9?"":"0")+x} -function isDate(val,format){var date=getDateFromFormat(val,format);if(date==0){return false;}return true;} -function compareDates(date1,dateformat1,date2,dateformat2){var d1=getDateFromFormat(date1,dateformat1);var d2=getDateFromFormat(date2,dateformat2);if(d1==0 || d2==0){return -1;}else if(d1 > d2){return 1;}return 0;} -function formatDate(date,format){format=format+"";var result="";var i_format=0;var c="";var token="";var y=date.getYear()+"";var M=date.getMonth()+1;var d=date.getDate();var E=date.getDay();var H=date.getHours();var m=date.getMinutes();var s=date.getSeconds();var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;var value=new Object();if(y.length < 4){y=""+(y-0+1900);}value["y"]=""+y;value["yyyy"]=y;value["yy"]=y.substring(2,4);value["M"]=M;value["MM"]=LZ(M);value["MMM"]=MONTH_NAMES[M-1];value["NNN"]=MONTH_NAMES[M+11];value["d"]=d;value["dd"]=LZ(d);value["E"]=DAY_NAMES[E+7];value["EE"]=DAY_NAMES[E];value["H"]=H;value["HH"]=LZ(H);if(H==0){value["h"]=12;}else if(H>12){value["h"]=H-12;}else{value["h"]=H;}value["hh"]=LZ(value["h"]);if(H>11){value["K"]=H-12;}else{value["K"]=H;}value["k"]=H+1;value["KK"]=LZ(value["K"]);value["kk"]=LZ(value["k"]);if(H > 11){value["a"]="PM";}else{value["a"]="AM";}value["m"]=m;value["mm"]=LZ(m);value["s"]=s;value["ss"]=LZ(s);while(i_format < format.length){c=format.charAt(i_format);token="";while((format.charAt(i_format)==c) &&(i_format < format.length)){token += format.charAt(i_format++);}if(value[token] != null){result=result + value[token];}else{result=result + token;}}return result;} -function _isInteger(val){var digits="1234567890";for(var i=0;i < val.length;i++){if(digits.indexOf(val.charAt(i))==-1){return false;}}return true;} -function _getInt(str,i,minlength,maxlength){for(var x=maxlength;x>=minlength;x--){var token=str.substring(i,i+x);if(token.length < minlength){return null;}if(_isInteger(token)){return token;}}return null;} -function getDateFromFormat(val,format){val=val+"";format=format+"";var i_val=0;var i_format=0;var c="";var token="";var token2="";var x,y;var now=new Date();var year=now.getYear();var month=now.getMonth()+1;var date=1;var hh=now.getHours();var mm=now.getMinutes();var ss=now.getSeconds();var ampm="";while(i_format < format.length){c=format.charAt(i_format);token="";while((format.charAt(i_format)==c) &&(i_format < format.length)){token += format.charAt(i_format++);}if(token=="yyyy" || token=="yy" || token=="y"){if(token=="yyyy"){x=4;y=4;}if(token=="yy"){x=2;y=2;}if(token=="y"){x=2;y=4;}year=_getInt(val,i_val,x,y);if(year==null){return 0;}i_val += year.length;if(year.length==2){if(year > 70){year=1900+(year-0);}else{year=2000+(year-0);}}}else if(token=="MMM"||token=="NNN"){month=0;for(var i=0;i11)){month=i+1;if(month>12){month -= 12;}i_val += month_name.length;break;}}}if((month < 1)||(month>12)){return 0;}}else if(token=="EE"||token=="E"){for(var i=0;i12)){return 0;}i_val+=month.length;}else if(token=="dd"||token=="d"){date=_getInt(val,i_val,token.length,2);if(date==null||(date<1)||(date>31)){return 0;}i_val+=date.length;}else if(token=="hh"||token=="h"){hh=_getInt(val,i_val,token.length,2);if(hh==null||(hh<1)||(hh>12)){return 0;}i_val+=hh.length;}else if(token=="HH"||token=="H"){hh=_getInt(val,i_val,token.length,2);if(hh==null||(hh<0)||(hh>23)){return 0;}i_val+=hh.length;}else if(token=="KK"||token=="K"){hh=_getInt(val,i_val,token.length,2);if(hh==null||(hh<0)||(hh>11)){return 0;}i_val+=hh.length;}else if(token=="kk"||token=="k"){hh=_getInt(val,i_val,token.length,2);if(hh==null||(hh<1)||(hh>24)){return 0;}i_val+=hh.length;hh--;}else if(token=="mm"||token=="m"){mm=_getInt(val,i_val,token.length,2);if(mm==null||(mm<0)||(mm>59)){return 0;}i_val+=mm.length;}else if(token=="ss"||token=="s"){ss=_getInt(val,i_val,token.length,2);if(ss==null||(ss<0)||(ss>59)){return 0;}i_val+=ss.length;}else if(token=="a"){if(val.substring(i_val,i_val+2).toLowerCase()=="am"){ampm="AM";}else if(val.substring(i_val,i_val+2).toLowerCase()=="pm"){ampm="PM";}else{return 0;}i_val+=2;}else{if(val.substring(i_val,i_val+token.length)!=token){return 0;}else{i_val+=token.length;}}}if(i_val != val.length){return 0;}if(month==2){if( ((year%4==0)&&(year%100 != 0) ) ||(year%400==0) ){if(date > 29){return 0;}}else{if(date > 28){return 0;}}}if((month==4)||(month==6)||(month==9)||(month==11)){if(date > 30){return 0;}}if(hh<12 && ampm=="PM"){hh=hh-0+12;}else if(hh>11 && ampm=="AM"){hh-=12;}var newdate=new Date(year,month-1,date,hh,mm,ss);return newdate.getTime();} -function parseDate(val){var preferEuro=(arguments.length==2)?arguments[1]:false;generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');var d=null;for(var i=0;i screen.availHeight){this.y = screen.availHeight - this.height;}}if(screen && screen.availWidth){if((this.x + this.width) > screen.availWidth){this.x = screen.availWidth - this.width;}}var avoidAboutBlank = window.opera ||( document.layers && !navigator.mimeTypes['*']) || navigator.vendor == 'KDE' ||( document.childNodes && !document.all && !navigator.taintEnabled);this.popupWindow = window.open(avoidAboutBlank?"":"about:blank","window_"+anchorname,this.windowProperties+",width="+this.width+",height="+this.height+",screenX="+this.x+",left="+this.x+",screenY="+this.y+",top="+this.y+"");}this.refresh();}} -function PopupWindow_hidePopup(){if(this.divName != null){if(this.use_gebi){document.getElementById(this.divName).style.visibility = "hidden";}else if(this.use_css){document.all[this.divName].style.visibility = "hidden";}else if(this.use_layers){document.layers[this.divName].visibility = "hidden";}}else{if(this.popupWindow && !this.popupWindow.closed){this.popupWindow.close();this.popupWindow = null;}}} -function PopupWindow_isClicked(e){if(this.divName != null){if(this.use_layers){var clickX = e.pageX;var clickY = e.pageY;var t = document.layers[this.divName];if((clickX > t.left) &&(clickX < t.left+t.clip.width) &&(clickY > t.top) &&(clickY < t.top+t.clip.height)){return true;}else{return false;}}else if(document.all){var t = window.event.srcElement;while(t.parentElement != null){if(t.id==this.divName){return true;}t = t.parentElement;}return false;}else if(this.use_gebi && e){var t = e.originalTarget;while(t.parentNode != null){if(t.id==this.divName){return true;}t = t.parentNode;}return false;}return false;}return false;} -function PopupWindow_hideIfNotClicked(e){if(this.autoHideEnabled && !this.isClicked(e)){this.hidePopup();}} -function PopupWindow_autoHide(){this.autoHideEnabled = true;} -function PopupWindow_hidePopupWindows(e){for(var i=0;i0){this.type="DIV";this.divName = arguments[0];}else{this.type="WINDOW";}this.use_gebi = false;this.use_css = false;this.use_layers = false;if(document.getElementById){this.use_gebi = true;}else if(document.all){this.use_css = true;}else if(document.layers){this.use_layers = true;}else{this.type = "WINDOW";}this.offsetX = 0;this.offsetY = 0;this.getXYPosition = PopupWindow_getXYPosition;this.populate = PopupWindow_populate;this.setUrl = PopupWindow_setUrl;this.setWindowProperties = PopupWindow_setWindowProperties;this.refresh = PopupWindow_refresh;this.showPopup = PopupWindow_showPopup;this.hidePopup = PopupWindow_hidePopup;this.setSize = PopupWindow_setSize;this.isClicked = PopupWindow_isClicked;this.autoHide = PopupWindow_autoHide;this.hideIfNotClicked = PopupWindow_hideIfNotClicked;} - - -/* SOURCE FILE: CalendarPopup.js */ - -function CalendarPopup(){var c;if(arguments.length>0){c = new PopupWindow(arguments[0]);}else{c = new PopupWindow();c.setSize(150,175);}c.offsetX = -152;c.offsetY = 25;c.autoHide();c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December");c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");c.dayHeaders = new Array("S","M","T","W","T","F","S");c.returnFunction = "CP_tmpReturnFunction";c.returnMonthFunction = "CP_tmpReturnMonthFunction";c.returnQuarterFunction = "CP_tmpReturnQuarterFunction";c.returnYearFunction = "CP_tmpReturnYearFunction";c.weekStartDay = 0;c.isShowYearNavigation = false;c.displayType = "date";c.disabledWeekDays = new Object();c.disabledDatesExpression = "";c.yearSelectStartOffset = 2;c.currentDate = null;c.todayText="Today";c.cssPrefix="";c.isShowNavigationDropdowns=false;c.isShowYearNavigationInput=false;window.CP_calendarObject = null;window.CP_targetInput = null;window.CP_dateFormat = "MM/dd/yyyy";c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow;c.setReturnFunction = CP_setReturnFunction;c.setReturnMonthFunction = CP_setReturnMonthFunction;c.setReturnQuarterFunction = CP_setReturnQuarterFunction;c.setReturnYearFunction = CP_setReturnYearFunction;c.setMonthNames = CP_setMonthNames;c.setMonthAbbreviations = CP_setMonthAbbreviations;c.setDayHeaders = CP_setDayHeaders;c.setWeekStartDay = CP_setWeekStartDay;c.setDisplayType = CP_setDisplayType;c.setDisabledWeekDays = CP_setDisabledWeekDays;c.addDisabledDates = CP_addDisabledDates;c.setYearSelectStartOffset = CP_setYearSelectStartOffset;c.setTodayText = CP_setTodayText;c.showYearNavigation = CP_showYearNavigation;c.showCalendar = CP_showCalendar;c.hideCalendar = CP_hideCalendar;c.getStyles = getCalendarStyles;c.refreshCalendar = CP_refreshCalendar;c.getCalendar = CP_getCalendar;c.select = CP_select;c.setCssPrefix = CP_setCssPrefix;c.showNavigationDropdowns = CP_showNavigationDropdowns;c.showYearNavigationInput = CP_showYearNavigationInput;c.copyMonthNamesToWindow();return c;} -function CP_copyMonthNamesToWindow(){if(typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null){window.MONTH_NAMES = new Array();for(var i=0;i\n";result += '
\n';}else{result += '
\n';result += '
\n';result += '
\n';}if(this.displayType=="date" || this.displayType=="week-end"){if(this.currentDate==null){this.currentDate = now;}if(arguments.length > 0){var month = arguments[0];}else{var month = this.currentDate.getMonth()+1;}if(arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]){var year = arguments[1];}else{var year = this.currentDate.getFullYear();}var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31);if( ((year%4 == 0)&&(year%100 != 0) ) ||(year%400 == 0) ){daysinmonth[2] = 29;}var current_month = new Date(year,month-1,1);var display_year = year;var display_month = month;var display_date = 1;var weekday= current_month.getDay();var offset = 0;offset =(weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ;if(offset > 0){display_month--;if(display_month < 1){display_month = 12;display_year--;}display_date = daysinmonth[display_month]-offset+1;}var next_month = month+1;var next_month_year = year;if(next_month > 12){next_month=1;next_month_year++;}var last_month = month-1;var last_month_year = year;if(last_month < 1){last_month=12;last_month_year--;}var date_class;if(this.type!="WINDOW"){result += "";}result += '\n';var refresh = windowref+'CP_refreshCalendar';var refreshLink = 'javascript:' + refresh;if(this.isShowNavigationDropdowns){result += '';result += '';result += '';}else{if(this.isShowYearNavigation){result += '';result += '';result += '';result += '';result += '';if(this.isShowYearNavigationInput){result += '';}else{result += '';}result += '';}else{result += '\n';result += '\n';result += '\n';}}result += '
 <'+this.monthNames[month-1]+'> <'+year+'><<'+this.monthNames[month-1]+' '+year+'>>
\n';result += '\n';result += '\n';for(var j=0;j<7;j++){result += '\n';}result += '\n';for(var row=1;row<=6;row++){result += '\n';for(var col=1;col<=7;col++){var disabled=false;if(this.disabledDatesExpression!=""){var ds=""+display_year+LZ(display_month)+LZ(display_date);eval("disabled=("+this.disabledDatesExpression+")");}var dateClass = "";if((display_month == this.currentDate.getMonth()+1) &&(display_date==this.currentDate.getDate()) &&(display_year==this.currentDate.getFullYear())){dateClass = "cpCurrentDate";}else if(display_month == month){dateClass = "cpCurrentMonthDate";}else{dateClass = "cpOtherMonthDate";}if(disabled || this.disabledWeekDays[col-1]){result += ' \n';}else{var selected_date = display_date;var selected_month = display_month;var selected_year = display_year;if(this.displayType=="week-end"){var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0);d.setDate(d.getDate() +(7-col));selected_year = d.getYear();if(selected_year < 1000){selected_year += 1900;}selected_month = d.getMonth()+1;selected_date = d.getDate();}result += ' \n';}display_date++;if(display_date > daysinmonth[display_month]){display_date=1;display_month++;}if(display_month > 12){display_month=1;display_year++;}}result += '';}var current_weekday = now.getDay() - this.weekStartDay;if(current_weekday < 0){current_weekday += 7;}result += '\n';result += '
'+this.dayHeaders[(this.weekStartDay+j)%7]+'
'+display_date+''+display_date+'
\n';if(this.disabledDatesExpression!=""){var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate());eval("disabled=("+this.disabledDatesExpression+")");}if(disabled || this.disabledWeekDays[current_weekday+1]){result += ' '+this.todayText+'\n';}else{result += ' '+this.todayText+'\n';}result += '
\n';result += '
\n';}if(this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year"){if(arguments.length > 0){var year = arguments[0];}else{if(this.displayType=="year"){var year = now.getFullYear()-this.yearSelectStartOffset;}else{var year = now.getFullYear();}}if(this.displayType!="year" && this.isShowYearNavigation){result += "";result += '\n';result += ' \n';result += ' \n';result += ' \n';result += '
<<'+year+'>>
\n';}}if(this.displayType=="month"){result += '\n';for(var i=0;i<4;i++){result += '';for(var j=0;j<3;j++){var monthindex =((i*3)+j);result += '';}result += '';}result += '
'+this.monthAbbreviations[monthindex]+'
\n';}if(this.displayType=="quarter"){result += '
\n';for(var i=0;i<2;i++){result += '';for(var j=0;j<2;j++){var quarter =((i*2)+j+1);result += '';}result += '';}result += '

Q'+quarter+'

\n';}if(this.displayType=="year"){var yearColumnSize = 4;result += "";result += '\n';result += ' \n';result += ' \n';result += '
<<>>
\n';result += '\n';for(var i=0;i'+currentyear+'';}result += '';}result += '
\n';}if(this.type == "WINDOW"){result += "\n";}return result;} +// HISTORY +// ------------------------------------------------------------------ +// Feb 7, 2005: Fixed a CSS styles to use px unit +// March 29, 2004: Added check in select() method for the form field +// being disabled. If it is, just return and don't do anything. +// March 24, 2004: Fixed bug - when month name and abbreviations were +// changed, date format still used original values. +// January 26, 2004: Added support for drop-down month and year +// navigation (Thanks to Chris Reid for the idea) +// September 22, 2003: Fixed a minor problem in YEAR calendar with +// CSS prefix. +// August 19, 2003: Renamed the function to get styles, and made it +// work correctly without an object reference +// August 18, 2003: Changed showYearNavigation and +// showYearNavigationInput to optionally take an argument of +// true or false +// July 31, 2003: Added text input option for year navigation. +// Added a per-calendar CSS prefix option to optionally use +// different styles for different calendars. +// July 29, 2003: Fixed bug causing the Today link to be clickable +// even though today falls in a disabled date range. +// Changed formatting to use pure CSS, allowing greater control +// over look-and-feel options. +// June 11, 2003: Fixed bug causing the Today link to be unselectable +// under certain cases when some days of week are disabled +// March 14, 2003: Added ability to disable individual dates or date +// ranges, display as light gray and strike-through +// March 14, 2003: Removed dependency on graypixel.gif and instead +/// use table border coloring +// March 12, 2003: Modified showCalendar() function to allow optional +// start-date parameter +// March 11, 2003: Modified select() function to allow optional +// start-date parameter +/* +DESCRIPTION: This object implements a popup calendar to allow the user to +select a date, month, quarter, or year. +COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. +The calendar can be modified to work for any location in the world by +changing which weekday is displayed as the first column, changing the month +names, and changing the column headers for each day. + +USAGE: +// Create a new CalendarPopup object of type WINDOW +var cal = new CalendarPopup(); + +// Create a new CalendarPopup object of type DIV using the DIV named 'mydiv' +var cal = new CalendarPopup('mydiv'); + +// Easy method to link the popup calendar with an input box. +cal.select(inputObject, anchorname, dateFormat); +// Same method, but passing a default date other than the field's current value +cal.select(inputObject, anchorname, dateFormat, '01/02/2000'); +// This is an example call to the popup calendar from a link to populate an +// input box. Note that to use this, date.js must also be included!! +Select + +// Set the type of date select to be used. By default it is 'date'. +cal.setDisplayType(type); + +// When a date, month, quarter, or year is clicked, a function is called and +// passed the details. You must write this function, and tell the calendar +// popup what the function name is. +// Function to be called for 'date' select receives y, m, d +cal.setReturnFunction(functionname); +// Function to be called for 'month' select receives y, m +cal.setReturnMonthFunction(functionname); +// Function to be called for 'quarter' select receives y, q +cal.setReturnQuarterFunction(functionname); +// Function to be called for 'year' select receives y +cal.setReturnYearFunction(functionname); + +// Show the calendar relative to a given anchor +cal.showCalendar(anchorname); + +// Hide the calendar. The calendar is set to autoHide automatically +cal.hideCalendar(); + +// Set the month names to be used. Default are English month names +cal.setMonthNames("January","February","March",...); + +// Set the month abbreviations to be used. Default are English month abbreviations +cal.setMonthAbbreviations("Jan","Feb","Mar",...); + +// Show navigation for changing by the year, not just one month at a time +cal.showYearNavigation(); + +// Show month and year dropdowns, for quicker selection of month of dates +cal.showNavigationDropdowns(); + +// Set the text to be used above each day column. The days start with +// sunday regardless of the value of WeekStartDay +cal.setDayHeaders("S","M","T",...); + +// Set the day for the first column in the calendar grid. By default this +// is Sunday (0) but it may be changed to fit the conventions of other +// countries. +cal.setWeekStartDay(1); // week is Monday - Sunday + +// Set the weekdays which should be disabled in the 'date' select popup. You can +// then allow someone to only select week end dates, or Tuedays, for example +cal.setDisabledWeekDays(0,1); // To disable selecting the 1st or 2nd days of the week + +// Selectively disable individual days or date ranges. Disabled days will not +// be clickable, and show as strike-through text on current browsers. +// Date format is any format recognized by parseDate() in date.js +// Pass a single date to disable: +cal.addDisabledDates("2003-01-01"); +// Pass null as the first parameter to mean "anything up to and including" the +// passed date: +cal.addDisabledDates(null, "01/02/03"); +// Pass null as the second parameter to mean "including the passed date and +// anything after it: +cal.addDisabledDates("Jan 01, 2003", null); +// Pass two dates to disable all dates inbetween and including the two +cal.addDisabledDates("January 01, 2003", "Dec 31, 2003"); + +// When the 'year' select is displayed, set the number of years back from the +// current year to start listing years. Default is 2. +// This is also used for year drop-down, to decide how many years +/- to display +cal.setYearSelectStartOffset(2); + +// Text for the word "Today" appearing on the calendar +cal.setTodayText("Today"); + +// The calendar uses CSS classes for formatting. If you want your calendar to +// have unique styles, you can set the prefix that will be added to all the +// classes in the output. +// For example, normal output may have this: +// Today +// But if you set the prefix like this: +cal.setCssPrefix("Test"); +// The output will then look like: +// Today +// And you can define that style somewhere in your page. + +// When using Year navigation, you can make the year be an input box, so +// the user can manually change it and jump to any year +cal.showYearNavigationInput(); + +// Set the calendar offset to be different than the default. By default it +// will appear just below and to the right of the anchorname. So if you have +// a text box where the date will go and and anchor immediately after the +// text box, the calendar will display immediately under the text box. +cal.offsetX = 20; +cal.offsetY = 20; + +NOTES: +1) Requires the functions in AnchorPosition.js and PopupWindow.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a CalendarPopup object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a CalendarPopup object + or the autoHide() will not work correctly. + +5) The calendar popup display uses style sheets to make it look nice. + +*/ + +// CONSTRUCTOR for the CalendarPopup Object +function CalendarPopup() { + var c; + if (arguments.length>0) { + c = new PopupWindow(arguments[0]); + } + else { + c = new PopupWindow(); + c.setSize(150,175); + } + c.offsetX = -152; + c.offsetY = 25; + c.autoHide(); + // Calendar-specific properties + c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December"); + c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"); + c.dayHeaders = new Array("S","M","T","W","T","F","S"); + c.returnFunction = "CP_tmpReturnFunction"; + c.returnMonthFunction = "CP_tmpReturnMonthFunction"; + c.returnQuarterFunction = "CP_tmpReturnQuarterFunction"; + c.returnYearFunction = "CP_tmpReturnYearFunction"; + c.weekStartDay = 0; + c.isShowYearNavigation = false; + c.displayType = "date"; + c.disabledWeekDays = new Object(); + c.disabledDatesExpression = ""; + c.yearSelectStartOffset = 2; + c.currentDate = null; + c.todayText="Today"; + c.cssPrefix=""; + c.isShowNavigationDropdowns=false; + c.isShowYearNavigationInput=false; + window.CP_calendarObject = null; + window.CP_targetInput = null; + window.CP_dateFormat = "MM/dd/yyyy"; + // Method mappings + c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow; + c.setReturnFunction = CP_setReturnFunction; + c.setReturnMonthFunction = CP_setReturnMonthFunction; + c.setReturnQuarterFunction = CP_setReturnQuarterFunction; + c.setReturnYearFunction = CP_setReturnYearFunction; + c.setMonthNames = CP_setMonthNames; + c.setMonthAbbreviations = CP_setMonthAbbreviations; + c.setDayHeaders = CP_setDayHeaders; + c.setWeekStartDay = CP_setWeekStartDay; + c.setDisplayType = CP_setDisplayType; + c.setDisabledWeekDays = CP_setDisabledWeekDays; + c.addDisabledDates = CP_addDisabledDates; + c.setYearSelectStartOffset = CP_setYearSelectStartOffset; + c.setTodayText = CP_setTodayText; + c.showYearNavigation = CP_showYearNavigation; + c.showCalendar = CP_showCalendar; + c.hideCalendar = CP_hideCalendar; + c.getStyles = getCalendarStyles; + c.refreshCalendar = CP_refreshCalendar; + c.getCalendar = CP_getCalendar; + c.select = CP_select; + c.setCssPrefix = CP_setCssPrefix; + c.showNavigationDropdowns = CP_showNavigationDropdowns; + c.showYearNavigationInput = CP_showYearNavigationInput; + c.copyMonthNamesToWindow(); + // Return the object + return c; + } +function CP_copyMonthNamesToWindow() { + // Copy these values over to the date.js + if (typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null) { + window.MONTH_NAMES = new Array(); + for (var i=0; i\n"; + result += '
\n'; + } + else { + result += '
\n'; + result += '
\n'; + result += '
\n'; + } + // Code for DATE display (default) + // ------------------------------- + if (this.displayType=="date" || this.displayType=="week-end") { + if (this.currentDate==null) { this.currentDate = now; } + if (arguments.length > 0) { var month = arguments[0]; } + else { var month = this.currentDate.getMonth()+1; } + if (arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]) { var year = arguments[1]; } + else { var year = this.currentDate.getFullYear(); } + var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31); + if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) { + daysinmonth[2] = 29; + } + var current_month = new Date(year,month-1,1); + var display_year = year; + var display_month = month; + var display_date = 1; + var weekday= current_month.getDay(); + var offset = 0; + + offset = (weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ; + if (offset > 0) { + display_month--; + if (display_month < 1) { display_month = 12; display_year--; } + display_date = daysinmonth[display_month]-offset+1; + } + var next_month = month+1; + var next_month_year = year; + if (next_month > 12) { next_month=1; next_month_year++; } + var last_month = month-1; + var last_month_year = year; + if (last_month < 1) { last_month=12; last_month_year--; } + var date_class; + if (this.type!="WINDOW") { + result += ""; + } + result += '\n'; + var refresh = windowref+'CP_refreshCalendar'; + var refreshLink = 'javascript:' + refresh; + if (this.isShowNavigationDropdowns) { + result += ''; + result += ''; + + result += ''; + } + else { + if (this.isShowYearNavigation) { + result += ''; + result += ''; + result += ''; + result += ''; + + result += ''; + if (this.isShowYearNavigationInput) { + result += ''; + } + else { + result += ''; + } + result += ''; + } + else { + result += '\n'; + result += '\n'; + result += '\n'; + } + } + result += '
 <'+this.monthNames[month-1]+'> <'+year+'><<'+this.monthNames[month-1]+' '+year+'>>
\n'; + result += '\n'; + result += '\n'; + for (var j=0; j<7; j++) { + + result += '\n'; + } + result += '\n'; + for (var row=1; row<=6; row++) { + result += '\n'; + for (var col=1; col<=7; col++) { + var disabled=false; + if (this.disabledDatesExpression!="") { + var ds=""+display_year+LZ(display_month)+LZ(display_date); + eval("disabled=("+this.disabledDatesExpression+")"); + } + var dateClass = ""; + if ((display_month == this.currentDate.getMonth()+1) && (display_date==this.currentDate.getDate()) && (display_year==this.currentDate.getFullYear())) { + dateClass = "cpCurrentDate"; + } + else if (display_month == month) { + dateClass = "cpCurrentMonthDate"; + } + else { + dateClass = "cpOtherMonthDate"; + } + if (disabled || this.disabledWeekDays[col-1]) { + result += ' \n'; + } + else { + var selected_date = display_date; + var selected_month = display_month; + var selected_year = display_year; + if (this.displayType=="week-end") { + var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0); + d.setDate(d.getDate() + (7-col)); + selected_year = d.getYear(); + if (selected_year < 1000) { selected_year += 1900; } + selected_month = d.getMonth()+1; + selected_date = d.getDate(); + } + result += ' \n'; + } + display_date++; + if (display_date > daysinmonth[display_month]) { + display_date=1; + display_month++; + } + if (display_month > 12) { + display_month=1; + display_year++; + } + } + result += ''; + } + var current_weekday = now.getDay() - this.weekStartDay; + if (current_weekday < 0) { + current_weekday += 7; + } + result += '\n'; + result += '
'+this.dayHeaders[(this.weekStartDay+j)%7]+'
'+display_date+''+display_date+'
\n'; + if (this.disabledDatesExpression!="") { + var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate()); + eval("disabled=("+this.disabledDatesExpression+")"); + } + if (disabled || this.disabledWeekDays[current_weekday+1]) { + result += ' '+this.todayText+'\n'; + } + else { + result += ' '+this.todayText+'\n'; + } + result += '
\n'; + result += '
\n'; + } + + // Code common for MONTH, QUARTER, YEAR + // ------------------------------------ + if (this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year") { + if (arguments.length > 0) { var year = arguments[0]; } + else { + if (this.displayType=="year") { var year = now.getFullYear()-this.yearSelectStartOffset; } + else { var year = now.getFullYear(); } + } + if (this.displayType!="year" && this.isShowYearNavigation) { + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += ' \n'; + result += '
<<'+year+'>>
\n'; + } + } + + // Code for MONTH display + // ---------------------- + if (this.displayType=="month") { + // If POPUP, write entire HTML document + result += '\n'; + for (var i=0; i<4; i++) { + result += ''; + for (var j=0; j<3; j++) { + var monthindex = ((i*3)+j); + result += ''; + } + result += ''; + } + result += '
'+this.monthAbbreviations[monthindex]+'
\n'; + } + + // Code for QUARTER display + // ------------------------ + if (this.displayType=="quarter") { + result += '
\n'; + for (var i=0; i<2; i++) { + result += ''; + for (var j=0; j<2; j++) { + var quarter = ((i*2)+j+1); + result += ''; + } + result += ''; + } + result += '

Q'+quarter+'

\n'; + } + + // Code for YEAR display + // --------------------- + if (this.displayType=="year") { + var yearColumnSize = 4; + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += '
<<>>
\n'; + result += '\n'; + for (var i=0; i'+currentyear+''; + } + result += ''; + } + result += '
\n'; + } + // Common + if (this.type == "WINDOW") { + result += "\n"; + } + return result; + } diff --git a/reports/site_media/PopupWindow.js b/reports/site_media/PopupWindow.js new file mode 100644 index 000000000..5a5823590 --- /dev/null +++ b/reports/site_media/PopupWindow.js @@ -0,0 +1,336 @@ +// =================================================================== +// Author: Matt Kruse +// WWW: http://www.mattkruse.com/ +// +// NOTICE: You may use this code for any purpose, commercial or +// private, without any further permission from the author. You may +// remove this notice from your final code if you wish, however it is +// appreciated by the author if at least my web site address is kept. +// +// You may *NOT* re-distribute this code in any way except through its +// use. That means, you can include it in your product, or your web +// site, or any other form where the code is actually being used. You +// may not put the plain javascript up on your site for download or +// include it in your javascript libraries for download. +// If you wish to share this code with others, please just point them +// to the URL instead. +// Please DO NOT link directly to my .js files from your site. Copy +// the files to your server and use them there. Thank you. +// =================================================================== + +/* +PopupWindow.js +Author: Matt Kruse +Last modified: 02/16/04 + +DESCRIPTION: This object allows you to easily and quickly popup a window +in a certain place. The window can either be a DIV or a separate browser +window. + +COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. Due to bugs in Netscape 4.x, populating the popup +window with +{% endblock %} + +{% block body_onload %}javascript:clientdetailload(){% endblock %} + +{% block pagebanner %}Client Details{% endblock %} {% block content %} -

Client Status Detail page for {{client.name}}


-Select time: - + + {% for i in client.interactions.all|slice:":25" %} + + {% endfor %} +
+ + + {% if interaction.isstale %} +
+ This node did not run within the last 24 hours — it may be out of date. +
+ {% endif %} + + + {% if interaction.server %} + + {% endif %} + {% if interaction.repo_rev_code %} + + {% endif %} + + + {% if not interaction.isclean %} + + {% endif %} +
Timestamp{{interaction.timestamp}}
Served by{{interaction.server}}
Revision{{interaction.repo_rev_code}}
State{{interaction.state|capfirst}}
Managed entries{{interaction.totalcount}}
Deviation{{interaction.percentbad|floatformat:"3"}}%
+ + {% if interaction.bad_entry_count %} +
+
+
[+]
+

Bad Entries — {{ interaction.bad_entry_count }}

+
+ + {% for e in interaction.bad|sortwell %} + + + + + {% endfor %} +
{{e.entry.kind}}: + {{e.entry.name}}
+
+ {% endif %} + + {% if interaction.modified_entry_count %} +
+
+
[+]
+

Modified Entries — {{ interaction.modified_entry_count }}

+
+ + {% for e in interaction.modified|sortwell %} + + + + + {% endfor %} +
{{e.entry.kind}}: + {{e.entry.name}}
+
+ {% endif %} + + {% if interaction.extra_entry_count %} +
+
+
[+]
+

Extra Entries — {{ interaction.extra_entry_count }}

+
+ + {% for e in interaction.extra|sortwell %} + + + + {% endfor %} - -         -Manage {{client.name}} options.
+
{{e.entry.kind}}:{{e.entry.name}}
+
+ {% endif %} -{% include "clients/client-nodebox.html" %} + {% if entry_list %} +
+
+

Recent Interactions

+
+
+ {% include "widgets/interaction_list.inc" %} + +
+
+ {% endif %} {% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/clients/detailed-list.html b/src/lib/Server/Reports/reports/templates/clients/detailed-list.html index 5a1352cff..0c1fae8d5 100644 --- a/src/lib/Server/Reports/reports/templates/clients/detailed-list.html +++ b/src/lib/Server/Reports/reports/templates/clients/detailed-list.html @@ -1,57 +1,15 @@ -{% extends "base.html" %} +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} -{% block title %}Detailed Client Listing{% endblock %} - -{% block extra_header_info %} - - - -{% endblock%} - -{% block pagebanner %} -
-

Detailed Client List

-
-
-{% endblock %} +{% block title %}Bcfg2 - Detailed Client Listing{% endblock %} +{% block pagebanner %}Clients - Detailed View{% endblock %} {% block content %} -
-
- -Enter date or use calendar popup: -@ - -Calendar - - | -

-
-
- -
+
{% if entry_list %} + {% filter_navigator %} - + @@ -61,30 +19,19 @@ - {% for client,entry,stale in entry_list %} + {% for entry in entry_list %} - - + + - +
Node State GoodLast Run Server
{{ client }}{{ entry.state }}{{ entry.client.name }}{{ entry.state }} {{ entry.goodcount }} {{ entry.bad_entry_count }} {{ entry.modified_entry_count }} {{ entry.extra_entry_count }}{{ entry.timestamp|date:"Y-m-d H:i" }}{{ entry.timestamp|date:"Y-m-d\&\n\b\s\p\;H:i"|safe }} {% if entry.server %} - {{ entry.server }} + {{ entry.server }} {% else %}   {% endif %} diff --git a/src/lib/Server/Reports/reports/templates/clients/history.html b/src/lib/Server/Reports/reports/templates/clients/history.html new file mode 100644 index 000000000..01d4ec2f4 --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/clients/history.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% load bcfg2_tags %} + +{% block title %}Bcfg2 - Interaction History{% endblock %} +{% block pagebanner %}Interaction history{% if client %} for {{ client.name }}{% endif %}{% endblock %} + +{% block extra_header_info %} +{% endblock %} + +{% block content %} +
+{% if entry_list %} + {% filter_navigator %} + {% include "widgets/interaction_list.inc" %} +{% else %} +

No client records are available.

+{% endif %} +
+{% page_navigator %} +{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/clients/index.html b/src/lib/Server/Reports/reports/templates/clients/index.html index cfb8a6c83..e0c0d2d7a 100644 --- a/src/lib/Server/Reports/reports/templates/clients/index.html +++ b/src/lib/Server/Reports/reports/templates/clients/index.html @@ -1,56 +1,33 @@ -{% extends "base.html" %} +{% extends "base-timeview.html" %} {% block extra_header_info %} - - - {% endblock%} -{% block title %}Client Index Listing{% endblock %} +{% block title %}Bcfg2 - Client Grid View{% endblock %} -{% block pagebanner %} -
-

Clients List

-
-
-{% endblock %} +{% block pagebanner %}Clients - Grid View{% endblock %} {% block content %} -
- -Enter date or use calendar popup: - -
- -@ - -Calendar - - | -
-


{% if inter_list %} - + {% if forloop.last %} + {% else %} - {% url Bcfg2.Server.Reports.reports.views.client_detail client,inter.id %} - {% endifequal %} - {% endspaceless %}">{{ client }} - - {% ifequal half_list forloop.counter0 %} - -{% endif %} + {% endif %} {% endfor %} - -
-
-
    - {% endifequal %} + {% if forloop.counter|divisibleby:"4" %}
+
{% else %}

No client records are available.

{% endif %} diff --git a/src/lib/Server/Reports/reports/templates/clients/manage.html b/src/lib/Server/Reports/reports/templates/clients/manage.html index 61f0fe017..5725ae577 100644 --- a/src/lib/Server/Reports/reports/templates/clients/manage.html +++ b/src/lib/Server/Reports/reports/templates/clients/manage.html @@ -1,29 +1,45 @@ {% extends "base.html" %} + {% block extra_header_info %} - - {% endblock%} -{% block title %}{{client.name}}{% endblock %} -{% block content %} -

Client Options Management page for {{client.name}}


-

Client status detail page: {{client.name}}.

-

Hosts may be prevented from showing up in the reporting system if they have been retired, are no longer managed by bcfg2 :(, etc.

-Select deactivation date: -
- -Enter date or use calendar popup: - -
- -@ - -Calendar - -
-


-

-

{{message}}

+{% block title %}Bcfg2 - Manage Clients{% endblock %} +{% block pagebanner %}Clients - Manage{% endblock %} + +{% block content %} +
+ {% if message %} +
{{ message }}
+ {% endif %} +{% if clients %} + + + + + + + {% for client in clients %} + + + + + + {% endfor %} +
NodeExpirationManage
+ + + {{ client.name }}{% firstof client.expiration 'Active' %} +
+
{# here for no reason other then to validate #} + + + +
+
+
+
+{% else %} +

No client records are available.

+{% endif %} {% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/config_items/index.html b/src/lib/Server/Reports/reports/templates/config_items/index.html deleted file mode 100644 index 04083344c..000000000 --- a/src/lib/Server/Reports/reports/templates/config_items/index.html +++ /dev/null @@ -1,100 +0,0 @@ -{% extends "base.html" %} - -{% load syntax_coloring %} - -{% block extra_header_info %} - - - -{% endblock%} -{% block title %}Configuration Element Details{% endblock %} - -{% block pagebanner %} -
-

Configuration Element Details

-
-
-{% endblock %} - -{% block content %} - -{% ifequal mod_or_bad "bad" %} -
-

Bad {{item.entry.kind}}: {{item.entry.name}}

-
-{% else %} -
-

Modified {{item.entry.kind}}: {{item.entry.name}}

-
-{% endifequal %} -
- - -{% if item.reason.current_owner %} - -{% endif %}{% if item.reason.current_group %} - -{% endif %}{% if item.reason.current_perms %} - -{% endif %}{% if item.reason.current_status %} - -{% endif %}{% if item.reason.current_to %} - -{% endif %}{% if item.reason.current_version %} - -{% endif %}{% if not item.reason.current_exists %} - -{% endif %}{% if item.reason.current_diff %} - -{% endif %} -
ReasonCurrent StatusSpecified in bcfg2
Owner: {{item.reason.current_owner}}{{item.reason.owner}}
Group: {{item.reason.current_group}}{{item.reason.group}}
Permissions: {{item.reason.current_perms}}{{item.reason.perms}}
Status: {{item.reason.current_status}}{{item.reason.status}}
Link Destination: {{item.reason.current_to}}{{item.reason.to}}
Version: {{item.reason.current_version}}{{item.reason.version}}
Existence: This item does not currently exist on the host but is specified to exist in the configuration.
NDiff:
{{item.reason.current_diff|syntaxhilight:"diff"}}
-
-
- -Enter date or use calendar popup: - -
- -@ - -Calendar -{% ifequal mod_or_bad "modified" %} - - | -{% else %} - - | -{% endifequal %} -
-


-{% if associated_client_list %} -

The following clients had this problem as of {{timestamp_date}}@{{timestamp_time}}:

- {% for client in associated_client_list %} - {{client.name}}
- {% endfor %} -
-
-{% else %} -

No Clients had this problem at {{timestamp}}

-{% endif %} - - - - - - - -{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/config_items/item.html b/src/lib/Server/Reports/reports/templates/config_items/item.html new file mode 100644 index 000000000..41474922b --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/config_items/item.html @@ -0,0 +1,109 @@ +{% extends "base.html" %} +{% load syntax_coloring %} + + +{% block title %}Bcfg2 - Element Details{% endblock %} + + +{% block extra_header_info %} + +{% endblock%} + +{% block pagebanner %}Element Details{% endblock %} + +{% block content %} +
+

{{mod_or_bad|capfirst}} {{item.entry.kind}}: {{item.entry.name}}

+
+ +
+ + {% if isextra %} +

This item exists on the host but is not defined in the configuration.

+ {% endif %} + + {% if not item.reason.current_exists %} +
This item does not currently exist on the host but is specified to exist in the configuration.
+ {% endif %} + + {% if item.reason.current_owner or item.reason.current_group or item.reason.current_perms or item.reason.current_status or item.reason.current_status or item.reason.current_to or item.reason.current_version %} + + + + {% if item.reason.current_owner %} + + + {% endif %} + {% if item.reason.current_group %} + + + {% endif %} + {% if item.reason.current_perms %} + + + {% endif %} + {% if item.reason.current_status %} + + + {% endif %} + {% if item.reason.current_to %} + + + {% endif %} + {% if item.reason.current_version %} + + + {% endif %} +
Problem TypeExpectedFound
Owner{{item.reason.owner}}{{item.reason.current_owner}}
Group{{item.reason.group}}{{item.reason.current_group}}
Permissions{{item.reason.perms}}{{item.reason.current_perms}}
Status{{item.reason.status}}{{item.reason.current_status}}
Symlink Target{{item.reason.to}}{{item.reason.current_to}}
Package Version{{item.reason.version|cut:"("|cut:")"}}{{item.reason.current_version|cut:"("|cut:")"}}
+ {% endif %} + + {% if item.reason.current_diff %} +
+
+

Incorrect file contents

+
+
+ {{ item.reason.current_diff|syntaxhilight }} +
+
+ {% endif %} + + +
+
+

Occurances on {{ timestamp|date:"Y-m-d" }}

+
+ {% if associated_list %} + + {% for inter in associated_list %} + + + + {% endfor %} +
{{inter.client.name}}{{inter.timestamp}}
+ {% else %} +

Missing client list

+ {% endif %} +
+ +
+{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/config_items/listing.html b/src/lib/Server/Reports/reports/templates/config_items/listing.html index 64a60e506..572249470 100644 --- a/src/lib/Server/Reports/reports/templates/config_items/listing.html +++ b/src/lib/Server/Reports/reports/templates/config_items/listing.html @@ -1,50 +1,32 @@ -{% extends "base.html" %} -{% load django_templating_sigh %} +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} -{% block extra_header_info %} - - - - - - - - - -{% endblock %} +{% block pagebanner %}{{mod_or_bad|capfirst}} Element Listing{% endblock %} -{% block title %}{{mod_or_bad|capfirst}} Item Listing{% endblock %} +{% block content %} +{% if item_list_dict %} + {% for kind, entries in item_list_dict.items %} -{% block pagebanner %} -
-

{{mod_or_bad|capfirst}} Configuration Elements

+
+
+
[+]
+

{{ kind }} — {{ entries|length }}

-
-{% endblock %} -{% block content %} -{% if item_list_pseudodict %} -
+ + {% for e in entries %} + + + + {% endfor %} +
{{e.entry.name}}
+
+ {% endfor %} {% else %}

There are currently no inconsistent configuration entries.

{% endif %} diff --git a/src/lib/Server/Reports/reports/templates/displays/index.html b/src/lib/Server/Reports/reports/templates/displays/index.html deleted file mode 100644 index c078539b6..000000000 --- a/src/lib/Server/Reports/reports/templates/displays/index.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Display Index Listing{% endblock %} -{% block pagebanner %} -
-

BCFG Display Index

- {% comment %} Report Run @ {% now "F j, Y P"%}{% endcomment %} -
-
-{% endblock %} - -{% block content %} - -{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html b/src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html deleted file mode 100644 index 60f97eadc..000000000 --- a/src/lib/Server/Reports/reports/templates/displays/summary-block-direct-links.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "displays/summary-block.html" %} -{% block linkprefix1 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix2 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix3 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix4 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix5 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} -{% block linkprefix6 %}{% url Bcfg2.Server.Reports.reports.views.client_index %}{% endblock %} \ No newline at end of file diff --git a/src/lib/Server/Reports/reports/templates/displays/summary-block.html b/src/lib/Server/Reports/reports/templates/displays/summary-block.html deleted file mode 100644 index 060ff0fa1..000000000 --- a/src/lib/Server/Reports/reports/templates/displays/summary-block.html +++ /dev/null @@ -1,90 +0,0 @@ -{% load django_templating_sigh %} - -
-

Summary:

-

{{client_list|length }} Nodes were included in your report.

- {% if clean_client_list %} -
- {{clean_client_list|length}} nodes are clean.
-
    - {% for client in clean_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if bad_client_list %} -
- {{bad_client_list|length}} nodes are bad.
-
    - {% for client in bad_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if modified_client_list %} -
- {{modified_client_list|length}} nodes were modified in the previous run.
-
    - {% for client in modified_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if extra_client_list %} -
- {{extra_client_list|length}} nodes have extra configuration. (includes both good and bad nodes)
-
    - {% for client in extra_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if stale_up_client_list %} -
- {{stale_up_client_list|length}} nodes did not run within the last 24 hours but were pingable.
-
    - {% for client in stale_up_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if stale_all_client_list %} -
- {{stale_all_client_list|length}} nodes did not run within the last 24 hours. (includes nodes up and down)
-
    - {% for client in stale_all_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} - {% if down_client_list %} -
- {{down_client_list|length}} nodes were down.
-
    - {% for client in down_client_list|sortname %} - {% set_interaction "foo" %} -
  • Node: - {{client.name}}{{interaction.timestamp}}
  • - {% endfor %} -
-
- {% endif %} -
diff --git a/src/lib/Server/Reports/reports/templates/displays/summary.html b/src/lib/Server/Reports/reports/templates/displays/summary.html index 29cbb22d7..0124f635d 100644 --- a/src/lib/Server/Reports/reports/templates/displays/summary.html +++ b/src/lib/Server/Reports/reports/templates/displays/summary.html @@ -1,31 +1,42 @@ -{% extends "base.html" %} +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} + +{% block title %}Bcfg2 - Client Summary{% endblock %} +{% block pagebanner %}Clients - Summary{% endblock %} + +{% block body_onload %}javascript:hide_table_array(hide_tables){% endblock %} + {% block extra_header_info %} - - + {% endblock%} -{% block title %}Display Index Listing{% endblock %} -{% block pagebanner %} -
-

BCFG Clients Summary

- Report Run @ {% now "F j, Y P"%} -
-
-{% endblock %} {% block content %} -
- -Enter date or use calendar popup: - -
- -@ - -Calendar - - | -
-


- {% include "displays/summary-block-direct-links.html" %} +
+

{{ node_count }} nodes reporting in

+
+{% if summary_data %} + {% for summary in summary_data %} +
+
+
[+]
+

{{ summary.nodes|length }} {{ summary.label }}

+
+ + + {% for node in summary.nodes|sort_interactions_by_name %} + + + + {% endfor %} +
{{ node.client.name }}
+
+ {% endfor %} +{% else %} +

No data to report on

+{% endif %} {% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/sys_view.html b/src/lib/Server/Reports/reports/templates/displays/sys_view.html deleted file mode 100644 index 1298059bf..000000000 --- a/src/lib/Server/Reports/reports/templates/displays/sys_view.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% load django_templating_sigh %} - -{% block title %}System-View Display{% endblock %} -{% block pagebanner %} -
-

Grand System View

- Report Run @ {% now "F j, Y P"%} -
-
-{% endblock %} -{% block content %} -

This view is deprecated and will be removed soon.


Please use the "Summary" view and drill down instead.
- - {% include "displays/summary-block.html" %} - {% for client in client_list %} - {% set_interaction "foo" %} - {% include "clients/client-nodebox.html" %} - {% endfor %} -{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/displays/timing.html b/src/lib/Server/Reports/reports/templates/displays/timing.html index 32ddab464..47accb2cb 100644 --- a/src/lib/Server/Reports/reports/templates/displays/timing.html +++ b/src/lib/Server/Reports/reports/templates/displays/timing.html @@ -1,54 +1,38 @@ -{% extends "base.html" %} +{% extends "base-timeview.html" %} +{% load bcfg2_tags %} + +{% block title %}Bcfg2 - Performance Metrics{% endblock %} +{% block pagebanner %}Performance Metrics{% endblock %} + {% block extra_header_info %} - - - {% endblock%} -{% block title %}Display Index Listing{% endblock %} {% block content %} -
-

BCFG Performance Timings

- Report Run @ {% now "F j, Y P"%} -
-
-
- -Enter date or use calendar popup: - -
- -@ - -Calendar - - | -
-


-
- - - - - - - - - +
+ {% if metrics %} +
HostnameParseProbeInventoryInstallConfigTotal
+ + + + + + + + - {% for dict_unit in stats_list %} - - - - - - - - + {% for metric in metrics|dictsort:"name" %} + + + {% for mitem in metric|build_metric_list %} + + {% endfor %} {% endfor %}
NameParseProbeInventoryInstallConfigTotal
{{dict_unit.name}}{{dict_unit.parse}}{{dict_unit.probe}}{{dict_unit.inventory}}{{dict_unit.install}}{{dict_unit.config}}{{dict_unit.total}}
{{ metric.name }}{{ mitem }}
-
+ {% else %} +

No metric data available

+ {% endif %} +
{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/index.html b/src/lib/Server/Reports/reports/templates/index.html deleted file mode 100644 index 002a3f770..000000000 --- a/src/lib/Server/Reports/reports/templates/index.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base.html" %} - -{% block pagebanner %} -
-

BCFG Reports

- {% comment %} Report Run @ {% now "F j, Y P"%}{% endcomment %} -
-
-{% endblock %} -{% block content %} -

Welcome to the Bcfg2 Reporting System

-

-Please use the links at the left to navigate. -

-{% endblock %} diff --git a/src/lib/Server/Reports/reports/templates/widgets/filter_bar.html b/src/lib/Server/Reports/reports/templates/widgets/filter_bar.html new file mode 100644 index 000000000..6b57baf6a --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/widgets/filter_bar.html @@ -0,0 +1,13 @@ +{% spaceless %} +{% if filters %} +{% for filter, filter_url in filters %} + {% if forloop.first %} +
Active filters (click to remove): + {% endif %} + {{ filter|capfirst }}{% if not forloop.last %}, {% endif %} + {% if forloop.last %} +
+ {% endif %} +{% endfor %} +{% endif %} +{% endspaceless %} diff --git a/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc b/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc new file mode 100644 index 000000000..8f2dec1dc --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/widgets/interaction_list.inc @@ -0,0 +1,38 @@ +{% load bcfg2_tags %} +
+ + + + {% if not client %} + + {% endif %} + + + + + + + + {% for entry in entry_list %} + + + {% if not client %} + + {% endif %} + + + + + + + + {% endfor %} +
TimestampClientStateGoodBadModifiedExtraServer
{{ entry.timestamp|date:"Y-m-d\&\n\b\s\p\;H:i"|safe }}{{ entry.client.name }}{{ entry.state }}{{ entry.goodcount }}{{ entry.bad_entry_count }}{{ entry.modified_entry_count }}{{ entry.extra_entry_count }} + {% if entry.server %} + {{ entry.server }} + {% else %} +   + {% endif %} +
+
diff --git a/src/lib/Server/Reports/reports/templates/widgets/page_bar.html b/src/lib/Server/Reports/reports/templates/widgets/page_bar.html new file mode 100644 index 000000000..aa0def83e --- /dev/null +++ b/src/lib/Server/Reports/reports/templates/widgets/page_bar.html @@ -0,0 +1,23 @@ +{% spaceless %} +{% for page, page_url in pager %} + {% if forloop.first %} +
+ {% if prev_page %}< Prev {% endif %} + {% if first_page %}1 ... {% endif %} + {% endif %} + {% ifequal page current_page %} + {{ page }} + {% else %} + {{ page }} + {% endifequal %} + {% if forloop.last %} + {% if last_page %} ... {{ total_pages }} {% endif %} + {% if next_page %}Next > {% endif %} + |{% for limit, limit_url in page_limits %} {{ limit }}{% endfor %} +
+ {% else %} +   + {% endif %} +{% endfor %} +{% endspaceless %} + diff --git a/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py new file mode 100644 index 000000000..2c27aab04 --- /dev/null +++ b/src/lib/Server/Reports/reports/templatetags/bcfg2_tags.py @@ -0,0 +1,239 @@ +from django import template +from django.core.urlresolvers import resolve, reverse, Resolver404, NoReverseMatch +from django.utils.encoding import smart_unicode, smart_str +from datetime import datetime, timedelta +from Bcfg2.Server.Reports.utils import filter_list + +register = template.Library() + +__PAGE_NAV_LIMITS__ = (10, 25, 50, 100) + +@register.inclusion_tag('widgets/page_bar.html', takes_context=True) +def page_navigator(context): + """ + Creates paginated links. + + Expects the context to be a RequestContext and views.prepare_paginated_list() + to have populated page information. + """ + fragment = dict() + try: + path = context['request'].path + total_pages = int(context['total_pages']) + records_per_page = int(context['records_per_page']) + except KeyError, e: + return fragment + except ValueError, e: + return fragment + + if total_pages < 2: + return {} + + try: + view, args, kwargs = resolve(path) + current_page = int(kwargs.get('page_number',1)) + fragment['current_page'] = current_page + fragment['page_number'] = current_page + fragment['total_pages'] = total_pages + fragment['records_per_page'] = records_per_page + if current_page > 1: + kwargs['page_number'] = current_page - 1 + fragment['prev_page'] = reverse(view, args=args, kwargs=kwargs) + if current_page < total_pages: + kwargs['page_number'] = current_page + 1 + fragment['next_page'] = reverse(view, args=args, kwargs=kwargs) + + view_range = 5 + if total_pages > view_range: + pager_start = current_page - 2 + pager_end = current_page + 2 + if pager_start < 1: + pager_end += (1 - pager_start) + pager_start = 1 + if pager_end > total_pages: + pager_start -= (pager_end - total_pages) + pager_end = total_pages + else: + pager_start = 1 + pager_end = total_pages + + if pager_start > 1: + kwargs['page_number'] = 1 + fragment['first_page'] = reverse(view, args=args, kwargs=kwargs) + if pager_end < total_pages: + kwargs['page_number'] = total_pages + fragment['last_page'] = reverse(view, args=args, kwargs=kwargs) + + pager = [] + for page in range(pager_start, int(pager_end) + 1): + kwargs['page_number'] = page + pager.append( (page, reverse(view, args=args, kwargs=kwargs)) ) + + kwargs['page_number'] = 1 + page_limits = [] + for limit in __PAGE_NAV_LIMITS__: + kwargs['page_limit'] = limit + page_limits.append( (limit, reverse(view, args=args, kwargs=kwargs)) ) + # resolver doesn't like this + del kwargs['page_number'] + del kwargs['page_limit'] + page_limits.append( ('all', reverse(view, args=args, kwargs=kwargs) + "|all") ) + + fragment['pager'] = pager + fragment['page_limits'] = page_limits + + except Resolver404: + path = "404" + except NoReverseMatch, nr: + path = "NoReverseMatch: %s" % nr + except ValueError: + path = "ValueError" + #FIXME - Handle these + + fragment['path'] = path + return fragment + +@register.inclusion_tag('widgets/filter_bar.html', takes_context=True) +def filter_navigator(context): + try: + path = context['request'].path + view, args, kwargs = resolve(path) + + # Strip any page limits and numbers + if 'page_number' in kwargs: + del kwargs['page_number'] + if 'page_limit' in kwargs: + del kwargs['page_limit'] + + filters = [] + for filter in filter_list: + if filter in kwargs: + myargs = kwargs.copy() + del myargs[filter] + filters.append( (filter, reverse(view, args=args, kwargs=myargs) ) ) + filters.sort(lambda x,y: cmp(x[0], y[0])) + return { 'filters': filters } + except (Resolver404, NoReverseMatch, ValueError, KeyError): + pass + return dict() + +def _subtract_or_na(mdict, x, y): + """ + Shortcut for build_metric_list + """ + try: + return round(mdict[x] - mdict[y], 4) + except: + return "n/a" + +@register.filter +def build_metric_list(mdict): + """ + Create a list of metric table entries + + Moving this here it simplify the view. Should really handle the case where these + are missing... + """ + td_list = [] + # parse + td_list.append( _subtract_or_na(mdict, 'config_parse', 'config_download')) + #probe + td_list.append( _subtract_or_na(mdict, 'probe_upload', 'start')) + #inventory + td_list.append( _subtract_or_na(mdict, 'inventory', 'initialization')) + #install + td_list.append( _subtract_or_na(mdict, 'install', 'inventory')) + #cfg download & parse + td_list.append( _subtract_or_na(mdict, 'config_parse', 'probe_upload')) + #total + td_list.append( _subtract_or_na(mdict, 'finished', 'start')) + return td_list + +@register.filter +def isstale(timestamp, entry_max=None): + """ + Check for a stale timestamp + + Compares two timestamps and returns True if the + difference is greater then 24 hours. + """ + if not entry_max: + entry_max = datetime.now() + return entry_max - timestamp > timedelta(hours=24) + +@register.filter +def sort_interactions_by_name(value): + """ + Sort an interaction list by client name + """ + inters = list(value) + inters.sort(lambda a,b: cmp(a.client.name, b.client.name)) + return inters + +class AddUrlFilter(template.Node): + def __init__(self, filter_name, filter_value): + self.filter_name = filter_name + self.filter_value = filter_value + self.fallback_view = 'Bcfg2.Server.Reports.reports.views.render_history_view' + + def render(self, context): + link = '#' + try: + path = context['request'].path + view, args, kwargs = resolve(path) + filter_value = self.filter_value.resolve(context, True) + if filter_value: + filter_name = smart_str(self.filter_name) + filter_value = smart_unicode(filter_value) + kwargs[filter_name] = filter_value + # These two don't make sense + if filter_name == 'server' and 'hostname' in kwargs: + del kwargs['hostname'] + elif filter_name == 'hostname' and 'server' in kwargs: + del kwargs['server'] + try: + link = reverse(view, args=args, kwargs=kwargs) + except NoReverseMatch, rm: + link = reverse(self.fallback_view, args=None, + kwargs={ filter_name: filter_value }) + except NoReverseMatch, rm: + raise rm + except (Resolver404, ValueError), e: + pass + return link + +@register.tag +def add_url_filter(parser, token): + """ + Return a url with the filter added to the current view. + + Takes a new filter and resolves the current view with the new filter + applied. Resolves to Bcfg2.Server.Reports.reports.views.client_history + by default. + + {% add_url_filter server=interaction.server %} + """ + try: + tag_name, filter_pair = token.split_contents() + filter_name, filter_value = filter_pair.split('=', 1) + filter_name = filter_name.strip() + filter_value = parser.compile_filter(filter_value) + except ValueError: + raise template.TemplateSyntaxError, "%r tag requires exactly one argument" % token.contents.split()[0] + if not filter_name or not filter_value: + raise template.TemplateSyntaxError, "argument should be a filter=value pair" + + return AddUrlFilter(filter_name, filter_value) + +@register.filter +def sortwell(value): + """ + Sorts a list(or evaluates queryset to list) of bad, extra, or modified items in the best + way for presentation + """ + + configItems = list(value) + configItems.sort(lambda x,y: cmp(x.entry.name, y.entry.name)) + configItems.sort(lambda x,y: cmp(x.entry.kind, y.entry.kind)) + return configItems + diff --git a/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py b/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py deleted file mode 100644 index c0d05d2c1..000000000 --- a/src/lib/Server/Reports/reports/templatetags/django_templating_sigh.py +++ /dev/null @@ -1,41 +0,0 @@ -from django import template -#from Bcfg2.Server.Reports.reports.models import Client, Interaction, Bad, Modified, Extra - -register = template.Library() - -def set_interaction(parser, token): - try: - # Splitting by None == splitting by spaces. - tag_name, format_string = token.contents.split(None, 1) - except ValueError: - raise template.TemplateSyntaxError, "%r tag requires an argument" % token.contents[0] - if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): - raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name - return SetInteraction(format_string[1:-1]) - -def sortwell(value): - "sorts a list(or evaluates queryset to list) of bad, extra, or modified items in the best" - "way for presentation" - configItems = list(value) - configItems.sort(lambda x,y: cmp(x.entry.name, y.entry.name)) - configItems.sort(lambda x,y: cmp(x.entry.kind, y.entry.kind)) - return configItems -def sortname(value): - "sorts a list( or evaluates queryset) by name" - configItems = list(value) - configItems.sort(lambda x,y: cmp(x.name, y.name)) - return configItems - -class SetInteraction(template.Node): - def __init__(self, times): - self.times = times#do soemthing to select different interaction with host? - def render(self, context): - try: - context['interaction'] = context['client_interaction_dict'][context['client'].id] - except:#I don't fully know what the implications of this are. - pass - return '' - -register.tag('set_interaction', set_interaction) -register.filter('sortwell', sortwell) -register.filter('sortname', sortname) diff --git a/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py b/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py index 083b83a73..43dafb262 100644 --- a/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py +++ b/src/lib/Server/Reports/reports/templatetags/syntax_coloring.py @@ -1,4 +1,7 @@ from django import template +from django.utils.encoding import smart_unicode, smart_str +from django.utils.html import conditional_escape +from django.utils.safestring import mark_safe register = template.Library() @@ -11,15 +14,28 @@ try: except: colorize = False -def syntaxhilight(value, arg="diff"): - '''Returns a syntax-hilighted version of Code; requires code/language arguments''' +@register.filter +def syntaxhilight(value, arg="diff", autoescape=None): + """ + Returns a syntax-hilighted version of Code; requires code/language arguments + """ + + if autoescape: + value = conditional_escape(value) + arg = conditional_escape(arg) + if colorize: try: + output = u'' + lexer = get_lexer_by_name(arg) - return highlight(value, lexer, HtmlFormatter()) + output += highlight(value, lexer, HtmlFormatter()) + return mark_safe(output) except: return value else: - return value + return mark_safe(u'
Tip: Install pygments for highlighting
%s
' % value) +syntaxhilight.needs_autoescape = True -register.filter('syntaxhilight', syntaxhilight) diff --git a/src/lib/Server/Reports/reports/urls.py b/src/lib/Server/Reports/reports/urls.py new file mode 100644 index 000000000..9970d26a1 --- /dev/null +++ b/src/lib/Server/Reports/reports/urls.py @@ -0,0 +1,55 @@ +from django.conf.urls.defaults import * +from django.core.urlresolvers import reverse, NoReverseMatch +from django.http import HttpResponsePermanentRedirect +from Bcfg2.Server.Reports.utils import filteredUrls, paginatedUrls, timeviewUrls + +def newRoot(request): + try: + grid_view = reverse('reports_grid_view') + except NoReverseMatch: + grid_view = '/grid' + return HttpResponsePermanentRedirect(grid_view) + +urlpatterns = patterns('Bcfg2.Server.Reports.reports', + (r'^$', newRoot), + + url(r'^manage/?$', 'views.client_manage', name='reports_client_manage'), + url(r'^client/(?P\S+)/(?P\d+)/?$', 'views.client_detail', name='reports_client_detail_pk'), + url(r'^client/(?P\S+)/?$', 'views.client_detail', name='reports_client_detail'), + url(r'^elements/(?P\w+)/(?P\d+)/?$', 'views.config_item', name='reports_item'), +) + +urlpatterns += patterns('Bcfg2.Server.Reports.reports', + *timeviewUrls( + (r'^grid/?$', 'views.client_index', None, 'reports_grid_view'), + (r'^summary/?$', 'views.display_summary', None, 'reports_summary'), + (r'^timing/?$', 'views.display_timing', None, 'reports_timing'), + (r'^elements/(?P\w+)/?$', 'views.config_item_list', None, 'reports_item_list'), +)) + +urlpatterns += patterns('Bcfg2.Server.Reports.reports', + *filteredUrls(*timeviewUrls( + (r'^detailed/?$', + 'views.client_detailed_list', None, 'reports_detailed_list') +))) + +urlpatterns += patterns('Bcfg2.Server.Reports.reports', + *paginatedUrls( *filteredUrls( + (r'^history/?$', + 'views.render_history_view', None, 'reports_history'), + (r'^history/(?P[\w\-\.]+)/?$', + 'views.render_history_view', None, 'reports_client_history'), +))) + + # Uncomment this for admin: + #(r'^admin/', include('django.contrib.admin.urls')), + + +## Uncomment this section if using authentication +#urlpatterns += patterns('', +# (r'^login/$', 'django.contrib.auth.views.login', +# {'template_name': 'auth/login.html'}), +# (r'^logout/$', 'django.contrib.auth.views.logout', +# {'template_name': 'auth/logout.html'}) +# ) + diff --git a/src/lib/Server/Reports/reports/views.py b/src/lib/Server/Reports/reports/views.py index d159dcd43..64617ce70 100644 --- a/src/lib/Server/Reports/reports/views.py +++ b/src/lib/Server/Reports/reports/views.py @@ -1,354 +1,379 @@ -# Create your views here. -#from django.shortcuts import get_object_or_404, render_to_response -from django.template import Context, loader -from django.http import HttpResponseRedirect, HttpResponse +""" +Report views + +Functions to handle all of the reporting views. +""" +from django.template import Context, RequestContext, loader +from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError, Http404 from django.shortcuts import render_to_response, get_object_or_404 -from Bcfg2.Server.Reports.reports.models import Client, Interaction, Entries, Entries_interactions, Performance, Reason -from Bcfg2.Server.Reports.reports.models import TYPE_BAD, TYPE_MODIFIED, TYPE_EXTRA -from datetime import datetime, timedelta -from time import strptime +from django.core.urlresolvers import resolve, reverse, Resolver404, NoReverseMatch from django.db import connection from django.db.backends import util -from django.contrib.auth.decorators import login_required -def index(request): - return render_to_response('index.html') +from Bcfg2.Server.Reports.reports.models import * +from datetime import datetime, timedelta +from time import strptime +import sys -def config_item_modified(request, eyedee =None, timestamp = 'now', type=TYPE_MODIFIED): - #if eyedee = None, dump with a 404 - timestamp = timestamp.replace("@"," ") - if type == TYPE_MODIFIED: - mod_or_bad = "modified" - else: - mod_or_bad = "bad" - - item = get_object_or_404(Entries_interactions, id=eyedee) - #if everything is blank except current_exists, do something special - cursor = connection.cursor() - if timestamp == 'now': - cursor.execute("select client_id from reports_interaction, reports_entries_interactions, reports_client "+ - "WHERE reports_client.current_interaction_id = reports_entries_interactions.interaction_id "+ - "AND reports_entries_interactions.interaction_id = reports_interaction.id "+ - "AND reports_entries_interactions.entry_id = %s " + - "AND reports_entries_interactions.reason_id = %s", [item.entry.id, item.reason.id]) - associated_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in cursor.fetchall()]) - else: - interact_queryset = Interaction.objects.interaction_per_client(timestamp) - interactionlist = [] - [interactionlist.append(x.id) for x in interact_queryset] - if not interactionlist == []: - cursor.execute("select client_id from reports_interaction, reports_entries_interactions, reports_client "+ - "WHERE reports_entries_interactions.interaction_id IN %s "+ - "AND reports_entries_interactions.interaction_id = reports_interaction.id "+ - "AND reports_entries_interactions.entry_id = %s " + - "AND reports_entries_interactions.reason_id = %s ", [interactionlist, item.entry_id, item.reason.id]) - associated_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in cursor.fetchall()]) - else: - associated_client_list = [] +class PaginationError(Exception): + """This error is raised when pagination cannot be completed.""" + pass - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') +def server_error(request): + """ + 500 error handler. - return render_to_response('config_items/index.html', {'item':item, - 'mod_or_bad':mod_or_bad, - 'associated_client_list':associated_client_list, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]}) + For now always return the debug response. Mailing isn't appropriate here. + """ + from django.views import debug + return debug.technical_500_response(request, *sys.exc_info()) -def config_item_bad(request, eyedee = None, timestamp = 'now'): - return config_item_modified(request, eyedee, timestamp, TYPE_BAD) +def timeview(fn): + """ + Setup a timeview view -def bad_item_index(request, timestamp = 'now', type=TYPE_BAD): - timestamp = timestamp.replace("@"," ") - if type == TYPE_BAD: - mod_or_bad = "bad" - else: - mod_or_bad = "modified" + Handles backend posts from the calendar and converts date pieces + into a 'timestamp' parameter + + """ + def _handle_timeview(request, **kwargs): + """Send any posts back.""" + if request.method == 'POST': + cal_date = request.POST['cal_date'] + try: + fmt = "%Y/%m/%d" + if cal_date.find(' ') > -1: + fmt += " %H:%M" + timestamp = datetime(*strptime(cal_date, fmt)[0:6]) + view, args, kw = resolve(request.path) + kw['year'] = "%0.4d" % timestamp.year + kw['month'] = "%02.d" % timestamp.month + kw['day'] = "%02.d" % timestamp.day + if cal_date.find(' ') > -1: + kw['hour'] = timestamp.hour + kw['minute'] = timestamp.minute + return HttpResponseRedirect(reverse(view, args=args, kwargs=kw)) + except KeyError: + pass + except: + pass + # FIXME - Handle this + + """Extract timestamp from args.""" + timestamp = None + try: + timestamp = datetime(int(kwargs.pop('year')), int(kwargs.pop('month')), + int(kwargs.pop('day')), int(kwargs.pop('hour', 0)), + int(kwargs.pop('minute', 0)), 0) + kwargs['timestamp'] = timestamp + except KeyError: + pass + except: + raise + return fn(request, **kwargs) + + return _handle_timeview + +def config_item(request, pk, type="bad"): + """ + Display a single entry. + + Dispalys information about a single entry. + + """ + item = get_object_or_404(Entries_interactions, id=pk) + timestamp=item.interaction.timestamp + time_start=item.interaction.timestamp.replace(\ + hour=0, minute=0, second=0, microsecond=0) + time_end=time_start + timedelta(days=1) + + todays_data = Interaction.objects.filter(\ + timestamp__gte=time_start,\ + timestamp__lt=time_end) + shared_entries = Entries_interactions.objects.filter(entry=item.entry,\ + reason=item.reason, type=item.type, + interaction__in=[x['id']\ + for x in todays_data.values('id')]) + + associated_list = Interaction.objects.filter(id__in=[x['interaction']\ + for x in shared_entries.values('interaction')])\ + .order_by('client__name','timestamp').select_related().all() + + return render_to_response('config_items/item.html', + {'item':item, + 'isextra': item.type == TYPE_EXTRA, + 'mod_or_bad': type, + 'associated_list':associated_list, + 'timestamp' : timestamp}, + context_instance=RequestContext(request)) + +@timeview +def config_item_list(request, type, timestamp=None): + """Render a listing of affected elements""" + mod_or_bad = type.lower() + type = convert_entry_type_to_id(type) + if type < 0: + raise Http404 - current_clients = [c.current_interaction - for c in Client.objects.active(timestamp)] + current_clients = Interaction.objects.get_interaction_per_client_ids(timestamp) item_list_dict = {} - for x in Entries_interactions.objects.select_related().filter(interaction__in=current_clients, type=type).distinct(): + seen = dict() + for x in Entries_interactions.objects.filter(interaction__in=current_clients, type=type).select_related(): + if (x.entry, x.reason) in seen: + continue + seen[(x.entry, x.reason)] = 1 if item_list_dict.get(x.entry.kind, None): item_list_dict[x.entry.kind].append(x) else: item_list_dict[x.entry.kind] = [x] - item_list_pseudodict = item_list_dict.items() - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') + for kind in item_list_dict: + item_list_dict[kind].sort(lambda a,b: cmp(a.entry.name, b.entry.name)) - return render_to_response('config_items/listing.html', {'item_list_pseudodict':item_list_pseudodict, + return render_to_response('config_items/listing.html', {'item_list_dict':item_list_dict, 'mod_or_bad':mod_or_bad, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]}) -def modified_item_index(request, timestamp = 'now'): - return bad_item_index(request, timestamp, TYPE_MODIFIED) - -def client_index(request, timestamp = 'now'): - timestamp = timestamp.replace("@"," ") - - c_dict = dict() - [c_dict.__setitem__(cl.id,cl.name) for cl in Client.objects.active(timestamp).order_by('name')] - - list = [] - for inter in Interaction.objects.interaction_per_client(timestamp): - if inter.client_id in c_dict: - list.append([c_dict[inter.client_id], inter]) - list.sort(lambda a,b: cmp(a[0], b[0])) - half_list = len(list) / 2 - - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') + 'timestamp' : timestamp}, + context_instance=RequestContext(request)) + +@timeview +def client_index(request, timestamp=None): + """ + Render a grid view of active clients. + + Keyword parameters: + timestamp -- datetime objectto render from + + """ + list = Interaction.objects.interaction_per_client(timestamp).select_related()\ + .order_by("client__name").all() + return render_to_response('clients/index.html', - {'inter_list': list, - 'half_list': half_list, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]}) + { 'inter_list': list, 'timestamp' : timestamp}, + context_instance=RequestContext(request)) -def client_detailed_list(request, **kwargs): +@timeview +def client_detailed_list(request, timestamp=None, **kwargs): """ Provides a more detailed list view of the clients. Allows for extra - filters to be passed in. Somewhat clunky now that dates are allowed. + filters to be passed in. """ - context = dict(path=request.path) - timestamp = 'now' - entry_max = datetime.now() - if request.GET: - context['qsa']='?%s' % request.GET.urlencode() - if request.GET.has_key('date1') and request.GET.has_key('time'): - timestamp = "%s %s" % (request.GET['date1'],request.GET['time']) - entry_max = datetime(*strptime(timestamp, "%Y-%m-%d %H:%M:%S")[0:6]) - client_list = Client.objects.active(timestamp).order_by('name') - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') - context['timestamp_date'] = timestamp[:10] - context['timestamp_time'] = timestamp[11:19] - - interactions = Interaction.objects.interaction_per_client(timestamp) - if 'state' in kwargs and kwargs['state']: - context['state'] = kwargs['state'] - interactions=interactions.filter(state__exact=kwargs['state']) - if 'server' in kwargs and kwargs['server']: - interactions=interactions.filter(server__exact=kwargs['server']) - context['server'] = kwargs['server'] - # build the entry list from available clients - c_dict = dict() - [c_dict.__setitem__(cl.id,cl.name) for cl in client_list] - - entry_list = [] - for inter in interactions: - if inter.client_id in c_dict: - entry_list.append([c_dict[inter.client_id], inter, \ - entry_max - inter.timestamp > timedelta(hours=24)]) - entry_list.sort(lambda a,b: cmp(a[0], b[0])) - ''' - if(datetime.now()-self.timestamp > timedelta(hours=25) ): - return True - else: - return False - ''' - - context['entry_list'] = entry_list - return render_to_response('clients/detailed-list.html', context) + kwargs['interaction_base'] = Interaction.objects.interaction_per_client(timestamp).select_related() + kwargs['orderby'] = "client__name" + kwargs['page_limit'] = 0 + return render_history_view(request, 'clients/detailed-list.html', **kwargs) def client_detail(request, hostname = None, pk = None): - #SETUP error pages for when you specify a client or interaction that doesn't exist + context = dict() client = get_object_or_404(Client, name=hostname) if(pk == None): - interaction = client.current_interaction + context['interaction'] = client.current_interaction + return render_history_view(request, 'clients/detail.html', page_limit=5, + client=client, context=context) else: - interaction = client.interactions.get(pk=pk)#can this be a get object or 404? - return render_to_response('clients/detail.html', {'client': client, 'interaction': interaction}) + context['interaction'] = client.interactions.get(pk=pk) + return render_history_view(request, 'clients/detail.html', page_limit=5, + client=client, maxdate=context['interaction'].timestamp, context=context) -def client_manage(request, hostname = None): - #SETUP error pages for when you specify a client or interaction that doesn't exist - client = get_object_or_404(Client, name=hostname) - currenttime = datetime.now().isoformat('@') - if client.expiration != None: - message = ("This client currently has an expiration date of %s. " - "Reports after %s will not include data for this host " - "You may change this if you wish by selecting a new " - "time, earlier or later." - % (client.expiration, client.expiration)) - else: - message = ("This client is currently active and displayed. You " - "may choose a date after which this client will no " - "longer appear in reports.") +def client_manage(request): + """Manage client expiration""" + message = '' if request.method == 'POST': - date = request.POST['date1'] - time = request.POST['time'] try: - timestamp = datetime(*(strptime(date+"@"+time, "%Y-%m-%d@%H:%M:%S")[0:6])) - except ValueError: - timestamp = None - if timestamp == None: - message = "Invalid removal date, please try again using the format: yyyy-mm-dd hh:mm:ss." - else: - client.expiration = timestamp + client_name = request.POST.get('client_name', None) + client_action = request.POST.get('client_action', None) + client = Client.objects.get(name=client_name) + if client_action == 'expire': + client.expiration = datetime.now(); + client.save() + message = "Expiration for %s set to %s." % \ + (client_name, client.expiration.strftime("%Y-%m-%d %H:%M:%S")) + elif client_action == 'unexpire': + client.expiration = None; client.save() - message = "Expiration for client set to %s." % client.expiration - return render_to_response('clients/manage.html', {'client': client, 'message': message, - 'timestamp_date' : currenttime[:10], - 'timestamp_time' : currenttime[11:19]}) - -def display_sys_view(request, timestamp = 'now'): - client_lists = prepare_client_lists(request, timestamp) - return render_to_response('displays/sys_view.html', client_lists) - -def display_summary(request, timestamp = 'now'): - - client_lists = prepare_client_lists(request, timestamp) - #this returns timestamp and the timestamp parts too - return render_to_response('displays/summary.html', client_lists) - -def display_timing(request, timestamp = 'now'): - #We're going to send a list of dictionaries. Each dictionary will be a row in the table - #+------+-------+----------------+-----------+---------+----------------+-------+ - #| name | parse | probe download | inventory | install | cfg dl & parse | total | - #+------+-------+----------------+-----------+---------+----------------+-------+ - client_list = Client.objects.active(timestamp.replace("@"," ")).order_by('name') - stats_list = [] - - if not timestamp == 'now': - results = Performance.objects.performance_per_client(timestamp.replace("@"," ")) + message = "%s is now active." % client_name else: - results = Performance.objects.performance_per_client() - timestamp = datetime.now().isoformat('@') - - for client in client_list:#Go explicitly to an interaction ID! (new item in dictionary) + message = "Missing action" + except Client.DoesNotExist: + if not client_name: + client_name = "" + message = "Couldn't find client \"%s\"" % client_name + + return render_to_response('clients/manage.html', + {'clients': Client.objects.order_by('name').all(), 'message': message}, + context_instance=RequestContext(request)) + +@timeview +def display_summary(request, timestamp=None): + """ + Display a summary of the bcfg2 world + """ + query = Interaction.objects.interaction_per_client(timestamp).select_related() + node_count = query.count() + recent_data = query.all() + if not timestamp: + timestamp = datetime.now() + + collected_data = dict(clean=[],bad=[],modified=[],extra=[],stale=[],pings=[]) + for node in recent_data: + if timestamp - node.timestamp > timedelta(hours=24): + collected_data['stale'].append(node) + # If stale check for uptime try: - d = results[client.name] - except KeyError: - d = {} + if node.client.pings.latest().status == 'N': + collected_data['pings'].append(node) + except Ping.DoesNotExist: + collected_data['pings'].append(node) + continue + if node.bad_entry_count() > 0: + collected_data['bad'].append(node) + else: + collected_data['clean'].append(node) + if node.modified_entry_count() > 0: + collected_data['modified'].append(node) + if node.extra_entry_count() > 0: + collected_data['extra'].append(node) + + # label, header_text, node_list + summary_data = [] + get_dict = lambda name, label: { 'name': name, + 'nodes': collected_data[name], + 'label': label } + if len(collected_data['clean']) > 0: + summary_data.append( get_dict('clean', 'nodes are clean.') ) + if len(collected_data['bad']) > 0: + summary_data.append( get_dict('bad', 'nodes are bad.') ) + if len(collected_data['modified']) > 0: + summary_data.append( get_dict('modified', 'nodes were modified.') ) + if len(collected_data['extra']) > 0: + summary_data.append( get_dict('extra', + 'nodes have extra configurations.') ) + if len(collected_data['stale']) > 0: + summary_data.append( get_dict('stale', + 'nodes did not run within the last 24 hours.') ) + if len(collected_data['pings']) > 0: + summary_data.append( get_dict('pings', + 'are down.') ) + + return render_to_response('displays/summary.html', + {'summary_data': summary_data, 'node_count': node_count, + 'timestamp': timestamp}, + context_instance=RequestContext(request)) + +@timeview +def display_timing(request, timestamp=None): + mdict = dict() + inters = Interaction.objects.interaction_per_client(timestamp).select_related().all() + [mdict.__setitem__(inter, {'name': inter.client.name}) \ + for inter in inters] + for metric in Performance.objects.filter(interaction__in=mdict.keys()).all(): + for i in metric.interaction.all(): + mdict[i][metric.metric] = metric.value + return render_to_response('displays/timing.html', + {'metrics': mdict.values(), 'timestamp': timestamp}, + context_instance=RequestContext(request)) + + +def render_history_view(request, template='clients/history.html', **kwargs): + """ + Provides a detailed history of a clients interactions. + + Renders a detailed history of a clients interactions. Allows for various + filters and settings. Automatically sets pagination data into the context. + + Keyword arguments: + interaction_base -- Interaction QuerySet to build on + (default Interaction.objects) + context -- Additional context data to render with + page_number -- Page to display (default 1) + page_limit -- Number of results per page, if 0 show all (default 25) + client -- Client object to render + hostname -- Client hostname to lookup and render. Returns a 404 if + not found + server -- Filter interactions by server + state -- Filter interactions by state + entry_max -- Most recent interaction to display + orderby -- Sort results using this field - dict_unit = {} - try: - dict_unit["name"] = client.name #node name - except: - dict_unit["name"] = "n/a" - try: - dict_unit["parse"] = round(d["config_parse"] - d["config_download"], 4) #parse - except: - dict_unit["parse"] = "n/a" - try: - dict_unit["probe"] = round(d["probe_upload"] - d["start"], 4) #probe - except: - dict_unit["probe"] = "n/a" - try: - dict_unit["inventory"] = round(d["inventory"] - d["initialization"], 4) #inventory - except: - dict_unit["inventory"] = "n/a" - try: - dict_unit["install"] = round(d["install"] - d["inventory"], 4) #install - except: - dict_unit["install"] = "n/a" - try: - dict_unit["config"] = round(d["config_parse"] - d["probe_upload"], 4)#config download & parse - except: - dict_unit["config"] = "n/a" + """ + + context = kwargs.get('context', dict()) + max_results = int(kwargs.get('page_limit', 25)) + page = int(kwargs.get('page_number', 1)) + + client=kwargs.get('client', None) + if not client and 'hostname' in kwargs: + client = get_object_or_404(Client, name=kwargs['hostname']) + if client: + context['client'] = client + + entry_max = kwargs.get('maxdate', None) + context['entry_max'] = entry_max + + # Either filter by client or limit by clients + iquery = kwargs.get('interaction_base', Interaction.objects) + if client: + iquery = iquery.filter(client__exact=client).select_related() + + if 'orderby' in kwargs and kwargs['orderby']: + iquery = iquery.order_by(kwargs['orderby']) + + if 'state' in kwargs and kwargs['state']: + iquery = iquery.filter(state__exact=kwargs['state']) + if 'server' in kwargs and kwargs['server']: + iquery = iquery.filter(server__exact=kwargs['server']) + + if entry_max: + iquery = iquery.filter(timestamp__lte=entry_max) + + if max_results < 0: + max_results = 1 + entry_list = [] + if max_results > 0: try: - dict_unit["total"] = round(d["finished"] - d["start"], 4) #total - except: - dict_unit["total"] = "n/a" - - stats_list.append(dict_unit) - - return render_to_response('displays/timing.html', {'client_list': client_list, - 'stats_list': stats_list, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]}) - -def display_index(request): - return render_to_response('displays/index.html') - -def prepare_client_lists(request, timestamp = 'now'): - #I suggest we implement "expiration" here. - - timestamp = timestamp.replace("@"," ") - #client_list = Client.objects.all().order_by('name')#change this to order by interaction's state - client_interaction_dict = {} - clean_client_list = [] - bad_client_list = [] - extra_client_list = [] - modified_client_list = [] - stale_up_client_list = [] - #stale_all_client_list = [] - down_client_list = [] - - cursor = connection.cursor() - - interact_queryset = Interaction.objects.interaction_per_client(timestamp) - # or you can specify a time like this: '2007-01-01 00:00:00' - [client_interaction_dict.__setitem__(x.client_id, x) for x in interact_queryset] - client_list = Client.objects.active(timestamp).filter(id__in=client_interaction_dict.keys()).order_by('name') - - [clean_client_list.append(x) for x in Client.objects.active(timestamp).filter(id__in=[y.client_id for y in interact_queryset.filter(state='clean')])] - [bad_client_list.append(x) for x in Client.objects.active(timestamp).filter(id__in=[y.client_id for y in interact_queryset.filter(state='dirty')])] - - client_ping_dict = {} - [client_ping_dict.__setitem__(x,'Y') for x in client_interaction_dict.keys()]#unless we know otherwise... + rec_start, rec_end = prepare_paginated_list(request, context, iquery, page, max_results) + except PaginationError, page_error: + if isinstance(page_error[0], HttpResponse): + return page_error[0] + return HttpResponseServerError(page_error) + context['entry_list'] = iquery.all()[rec_start:rec_end] + else: + context['entry_list'] = iquery.all() + + return render_to_response(template, context, + context_instance=RequestContext(request)) + +def prepare_paginated_list(request, context, paged_list, page=1, max_results=25): + """ + Prepare context and slice an object for pagination. + """ + if max_results < 1: + raise PaginationError, "Max results less then 1" + if paged_list == None: + raise PaginationError, "Invalid object" + + try: + nitems = paged_list.count() + except TypeError: + nitems = len(paged_list) + rec_start = (page - 1) * int(max_results) try: - cursor.execute("select reports_ping.status, x.client_id from (select client_id, MAX(endtime) "+ - "as timer from reports_ping GROUP BY client_id) x, reports_ping where "+ - "reports_ping.client_id = x.client_id AND reports_ping.endtime = x.timer") - [client_ping_dict.__setitem__(x[1], x[0]) for x in cursor.fetchall()] + total_pages = (nitems / int(max_results)) + 1 except: - pass #This is to fix problems when you have only zero records returned - - client_down_ids = [y for y in client_ping_dict.keys() if client_ping_dict[y]=='N'] - if not client_down_ids == []: - [down_client_list.append(x) for x in Client.objects.active(timestamp).filter(id__in=client_down_ids)] - - if (timestamp == 'now' or timestamp == None): - cursor.execute("select client_id, MAX(timestamp) as timestamp from reports_interaction GROUP BY client_id") - results = cursor.fetchall() - for x in results: - if type(x[1]) == type("") or type(x[1]) == type(u""): - ts = util.typecast_timestamp(x[1]) - else: - ts = x[1] - stale_all_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in results if datetime.now() - ts > timedelta(days=1)]) - else: - cursor.execute("select client_id, timestamp, MAX(timestamp) as timestamp from reports_interaction "+ - "WHERE timestamp < %s GROUP BY client_id", [timestamp]) - t = strptime(timestamp,"%Y-%m-%d %H:%M:%S") - datetimestamp = datetime(t[0], t[1], t[2], t[3], t[4], t[5]) - results = cursor.fetchall() - for x in results: - if type(x[1]) == type(""): - x[1] = util.typecast_timestamp(x[1]) - stale_all_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in results if datetimestamp - x[1] > timedelta(days=1)]) - - [stale_up_client_list.append(x) for x in stale_all_client_list if not client_ping_dict[x.id]=='N'] + total_pages = 1 + if page > total_pages: + # If we passed beyond the end send back + try: + view, args, kwargs = resolve(request.path) + kwargs['page_number'] = total_pages + raise PaginationError, HttpResponseRedirect( reverse(view, kwargs=kwargs) ) + except (Resolver404, NoReverseMatch, ValueError): + raise "Accessing beyond last page. Unable to resolve redirect." + + context['total_pages'] = total_pages + context['records_per_page'] = max_results + return (rec_start, rec_start + int(max_results)) - - cursor.execute("SELECT reports_client.id FROM reports_client, reports_interaction, reports_entries_interactions WHERE reports_client.id = reports_interaction.client_id AND reports_client.current_interaction_id = reports_entries_interactions.interaction_id and reports_entries_interactions.type=%s GROUP BY reports_client.id", [TYPE_MODIFIED]) - modified_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in cursor.fetchall()]) - - cursor.execute("SELECT reports_client.id FROM reports_client, reports_interaction, reports_entries_interactions WHERE reports_client.id = reports_interaction.client_id AND reports_client.current_interaction_id = reports_entries_interactions.interaction_id and reports_entries_interactions.type=%s GROUP BY reports_client.id", [TYPE_EXTRA]) - extra_client_list = Client.objects.active(timestamp).filter(id__in=[x[0] for x in cursor.fetchall()]) - - if timestamp == 'now': - timestamp = datetime.now().isoformat('@') - - return {'client_list': client_list, - 'client_interaction_dict':client_interaction_dict, - 'clean_client_list': clean_client_list, - 'bad_client_list': bad_client_list, - 'extra_client_list': extra_client_list, - 'modified_client_list': modified_client_list, - 'stale_up_client_list': stale_up_client_list, - 'stale_all_client_list': stale_all_client_list, - 'down_client_list': down_client_list, - 'timestamp' : timestamp, - 'timestamp_date' : timestamp[:10], - 'timestamp_time' : timestamp[11:19]} diff --git a/src/lib/Server/Reports/settings.py b/src/lib/Server/Reports/settings.py index 59d29114d..81220c0e3 100644 --- a/src/lib/Server/Reports/settings.py +++ b/src/lib/Server/Reports/settings.py @@ -1,3 +1,5 @@ +import django + # Django settings for bcfg2 reports project. from ConfigParser import ConfigParser, NoSectionError, NoOptionError c = ConfigParser() @@ -62,7 +64,9 @@ MEDIA_ROOT = '' # URL that handles the media served from MEDIA_ROOT. # Example: "http://media.lawrence.com" -MEDIA_URL = '' +MEDIA_URL = '/site_media' +if c.has_option('statistics', 'web_prefix'): + MEDIA_URL = c.get('statistics', 'web_prefix').rstrip('/') + MEDIA_URL # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. @@ -109,9 +113,26 @@ TEMPLATE_DIRS = ( # Put strings here, like "/home/html/django_templates". # Always use forward slashes, even on Windows. '/usr/share/python-support/python-django/django/contrib/admin/templates/', - '/usr/share/bcfg2/Reports/templates' + 'Bcfg2.Server.Reports.reports' ) +if django.VERSION[0] == 1 and django.VERSION[1] < 2: + TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.core.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.core.context_processors.media', + 'django.core.context_processors.request' + ) +else: + TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.contrib.auth.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.core.context_processors.media', + 'django.core.context_processors.request' + ) + INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', diff --git a/src/lib/Server/Reports/urls.py b/src/lib/Server/Reports/urls.py index e1326b5ea..5d298c974 100644 --- a/src/lib/Server/Reports/urls.py +++ b/src/lib/Server/Reports/urls.py @@ -1,4 +1,7 @@ from django.conf.urls.defaults import * +from django.http import HttpResponsePermanentRedirect + +handler500 = 'Bcfg2.Server.Reports.reports.views.server_error' from ConfigParser import ConfigParser, NoSectionError, NoOptionError c = ConfigParser() @@ -9,56 +12,15 @@ c.read(['/etc/bcfg2.conf', '/etc/bcfg2-web.conf']) # web_prefix_root is a workaround for the index if c.has_option('statistics', 'web_prefix'): web_prefix = c.get('statistics', 'web_prefix').lstrip('/') - web_prefix_root = web_prefix else: web_prefix = '' - web_prefix_root = '/' urlpatterns = patterns('', - # Example: - # (r'^%sBcfg2.Server.Reports/' % web_prefix, include('Bcfg2.Server.Reports.apps.foo.urls.foo')), - (r'^%s*$' % web_prefix_root,'Bcfg2.Server.Reports.reports.views.index'), - - (r'^%sclients-detailed/state/(?P\w+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_detailed_list'), - (r'^%sclients-detailed/server/(?P[\w\-\.]+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_detailed_list'), - (r'^%sclients-detailed/server/(?P[\w\-\.]+)/(?P[A-Za-z]+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_detailed_list'), - (r'^%sclients-detailed/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_detailed_list'), - (r'^%sclients/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_index'), - (r'^%sclients/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_index'), - (r'^%sclients/(?P\S+)/(?P\d+)/$' % web_prefix, 'Bcfg2.Server.Reports.reports.views.client_detail'), - (r'^%sclients/(?P\S+)/manage/$' % web_prefix, 'Bcfg2.Server.Reports.reports.views.client_manage'), - (r'^%sclients/(?P\S+)/$' % web_prefix, 'Bcfg2.Server.Reports.reports.views.client_detail'), - (r'^%sclients/(?P\S+)$' % web_prefix, 'Bcfg2.Server.Reports.reports.views.client_detail'), - #hack because hostnames have periods and we still want to append slash - (r'^%sclients/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.client_index'), - (r'^%sdisplays/sys-view/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_sys_view'), - (r'^%sdisplays/sys-view/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_sys_view'), - (r'^%sdisplays/summary/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_summary'), - (r'^%sdisplays/summary/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_summary'), - (r'^%sdisplays/timing/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_timing'), - (r'^%sdisplays/timing/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_timing'), - (r'^%sdisplays/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.display_index'), - - (r'^%selements/modified/(?P\d+)/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.config_item_modified'), - (r'^%selements/modified/(?P\d+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.config_item_modified'), - (r'^%selements/modified/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01]\ - [0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.modified_item_index'), - (r'^%selements/modified/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.modified_item_index'), - (r'^%selements/bad/(?P\d+)/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01][0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.config_item_bad'), - (r'^%selements/bad/(?P\d+)/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.config_item_bad'), - (r'^%selements/bad/(?P(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])@([01]\ - [0-9]|2[0-3]):([0-5][0-9]|60):([0-5][0-9]|60))/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.bad_item_index'), - (r'^%selements/bad/$' % web_prefix,'Bcfg2.Server.Reports.reports.views.bad_item_index'), + (r'^%s' % web_prefix, include('Bcfg2.Server.Reports.reports.urls')) ) - # Uncomment this for admin: - #(r'^%sadmin/' % web_prefix, include('django.contrib.admin.urls')), - - -## Uncomment this section if using authentication -#urlpatterns += patterns('', -# (r'^%slogin/$' % web_prefix, 'django.contrib.auth.views.login', -# {'template_name': 'auth/login.html'}), -# (r'^%slogout/$' % web_prefix, 'django.contrib.auth.views.logout', -# {'template_name': 'auth/logout.html'}) -# ) +urlpatterns += patterns("django.views", + url(r"media/(?P.*)$", "static.serve", { + "document_root": '/Users/tlaszlo/svn/bcfg2/reports/site_media/', + }) +) diff --git a/src/lib/Server/Reports/utils.py b/src/lib/Server/Reports/utils.py index 2ef21e446..b74f09e74 100755 --- a/src/lib/Server/Reports/utils.py +++ b/src/lib/Server/Reports/utils.py @@ -1,7 +1,13 @@ -'''Helper functions for reports''' +"""Helper functions for reports""" +from Bcfg2.Server.Reports.reports.models import TYPE_CHOICES +from django.conf.urls.defaults import * +import re + +"""List of filters provided by filteredUrls""" +filter_list = ('server', 'state') class BatchFetch(object): - '''Fetch Django objects in smaller batches to save memory''' + """Fetch Django objects in smaller batches to save memory""" def __init__(self, obj, step=10000): self.count = 0 @@ -15,8 +21,8 @@ class BatchFetch(object): return self def next(self): - '''Return the next object from our array and fetch from the - database when needed''' + """Return the next object from our array and fetch from the + database when needed""" if self.block_count + self.count - self.step == self.max: raise StopIteration if self.block_count == 0 or self.count == self.step: @@ -28,3 +34,83 @@ class BatchFetch(object): self.count += 1 return self.data[self.count - 1] +def generateUrls(fn): + """ + Parse url tuples and send to functions. + + Decorator for url generators. Handles url tuple parsing + before the actual function is called. + """ + def url_gen(*urls): + results = [] + for url_tuple in urls: + if isinstance(url_tuple, (list, tuple)): + results += fn(*url_tuple) + else: + raise ValueError("Unable to handle compiled urls") + return results + return url_gen + +@generateUrls +def paginatedUrls(pattern, view, kwargs=None, name=None): + """ + Takes a group of url tuples and adds paginated urls. + + Extends a url tuple to include paginated urls. Currently doesn't handle url() compiled + patterns. + + """ + results = [(pattern, view, kwargs, name)] + tail = '' + mtail = re.search('(/+\+?\\*?\??\$?)$', pattern) + if mtail: + tail = mtail.group(1) + pattern = pattern[:len(pattern) - len(tail)] + results += [(pattern + "/(?P\d+)" + tail, view, kwargs)] + results += [(pattern + "/(?P\d+)\|(?P\d+)" + tail, view, kwargs)] + if not kwargs: + kwargs = dict() + kwargs['page_limit'] = 0 + results += [(pattern + "/?\|(?Pall)" + tail, view, kwargs)] + return results + +@generateUrls +def filteredUrls(pattern, view, kwargs=None, name=None): + """ + Takes a url and adds filtered urls. + + Extends a url tuple to include filtered view urls. Currently doesn't + handle url() compiled patterns. + """ + results = [(pattern, view, kwargs, name)] + tail = '' + mtail = re.search('(/+\+?\\*?\??\$?)$', pattern) + if mtail: + tail = mtail.group(1) + pattern = pattern[:len(pattern) - len(tail)] + for filter in ('/state/(?P\w+)', + '/server/(?P[\w\-\.]+)', + '/server/(?P[\w\-\.]+)/(?P[A-Za-z]+)'): + results += [(pattern + filter + tail, view, kwargs)] + return results + +@generateUrls +def timeviewUrls(pattern, view, kwargs=None, name=None): + """ + Takes a url and adds timeview urls + + Extends a url tuple to include filtered view urls. Currently doesn't + handle url() compiled patterns. + """ + results = [(pattern, view, kwargs, name)] + tail = '' + mtail = re.search('(/+\+?\\*?\??\$?)$', pattern) + if mtail: + tail = mtail.group(1) + pattern = pattern[:len(pattern) - len(tail)] + for filter in ('/(?P\d{4})-(?P\d{2})-(?P\d{2})/' + \ + '(?P\d\d)-(?P\d\d)', + '/(?P\d{4})-(?P\d{2})-(?P\d{2})'): + results += [(pattern + filter + tail, view, kwargs)] + return results + -- cgit v1.2.3-1-g7c22