import uuid import random import os import json from utils.Config import Config from utils.Tools import _warn import constants as _C class PageBuilder: serviceIcon = { 'ec2': 'server', 'rds': 'database', 's3': 'hdd', 'iam': 'users', 'guardduty': 'shield-alt', 'opensearch': 'warehouse', 'efs': 'network-wired', 'eks': 'box', 'cloudfront': 'wifi', 'elasticache': 'store', 'lambda': 'calculator', 'cloudtrail': 'user-secret' } frameworkIcon = 'tasks' pageTemplate = { 'header.precss': 'header.precss.template.html', 'header.postcss': 'header.postcss.template.html', 'sidebar.precustom': 'sidebar.precustom.template.html', 'sidebar.postcustom': 'sidebar.postcustom.template.html', 'breadcrumb': 'breadcrumb.template.html', 'footer.prejs': 'footer.prejs.template.html', 'footer.postjs': 'footer.postjs.template.html', } isHome = False def __init__(self, service, reporter): self.service = service self.services = Config.get('cli_services', []) self.frameworks = Config.get('cli_frameworks', []) self.regions = Config.get('cli_regions', []) self.reporter = reporter self.idPrefix = self.service + '-' self.js = [] self.jsLib = [] self.cssLib = [] def getHtmlId(self, el=''): o = uuid.uuid4().hex el = el or o[0:11] return self.idPrefix + el def buildPage(self): self.init() output = [] output.append(self.buildHeader()) output.append(self.buildNav()) output.append(self.buildBreadcrumb()) output.append(self.buildContentSummary()) output.append(self.buildContentDetail()) output.append(self.buildFooter()) finalHTML = "" for arrayOfText in output: if arrayOfText: finalHTML += "\n".join(arrayOfText) with open(_C.HTML_DIR + '/' + self.service + '.html', 'w') as f: f.write(finalHTML) def init(self): self.template = 'default' def buildContentSummary(self): method = 'buildContentSummary_' + self.template if hasattr(self, method): return getattr(self, method)() else: cls = self.__class__.__name__ print("[{}] Template for ContentSummary not found: {}".format(cls, method)) def buildContentDetail(self): method = 'buildContentDetail_' + self.template if hasattr(self, method): return getattr(self, method)() else: cls = self.__class__.__name__ print("[{}] Template for ContentDetail not found: {}".format(cls, method)) def generateRowWithCol(self, size=12, items=[], rowHtmlAttr=''): output = [] output.append("
".format(rowHtmlAttr)) _size = size for ind, item in enumerate(items): if isinstance(size, list): i = ind % len(size) _size = size[i] output.append(self.generateCol(_size, item)) output.append("
") return "\n".join(output) def generateCol(self, size=12, item=[]): output = [] if not item: output.append("
") else: html, divAttr = item output.append("
".format(size, divAttr)) output.append(html) output.append("
") return "\n".join(output) def generateCard(self, pid, html, cardClass='warning', title='', titleBadge='', collapse=False, noPadding=False): output = [] lteCardClass = '' if not cardClass else "card-{}".format(cardClass) defaultCollapseClass = "collapsed-card" if collapse == 9 else "" defaultCollapseIcon = "plus" if collapse == 9 else "minus" output.append("
".format(pid, lteCardClass, defaultCollapseClass)) if title: output.append("

{}

".format(title)) if collapse: output.append("
".format(defaultCollapseIcon)) if titleBadge: output.append(titleBadge) output.append("
") noPadClass = 'p-0' if noPadding else '' output.append("
".format(noPadClass)) output.append(html) output.append("
") output.append("
") return "\n".join(output) def generateCategoryBadge(self, category, addtionalHtmlAttr): validCategory = ['R', 'S', 'O', 'P', 'C', 'T'] colorByCategory = ['info', 'danger', 'primary', 'success', 'warning', 'info'] nameByCategory = ['Reliability', 'Security', 'Operation Excellence', 'Performance Efficiency', 'Cost Optimization', 'Text'] if category not in validCategory: category = 'X' color = 'info' name = 'Suggestion' else: indexOf = validCategory.index(category) color = colorByCategory[indexOf] name = nameByCategory[indexOf] return "{}".format(color, addtionalHtmlAttr, name) def generatePriorityPrefix(self, criticality, addtionalHtmlAttr): validCategory = ['I', 'L', 'M', 'H'] colorByCategory = ['info', 'primary', 'warning', 'danger'] iconByCategory = ['info-circle', 'eye', 'exclamation-triangle', 'ban'] criticality = criticality if criticality in validCategory else validCategory[0] indexOf = validCategory.index(criticality) color = colorByCategory[indexOf] icon = iconByCategory[indexOf] return "".format(color, addtionalHtmlAttr, icon) def generateSummaryCardContent(self, summary): output = [] resources = summary['__affectedResources'] resHtml = [] for region, resource in resources.items(): items = [] resHtml.append(f"
{region}: ") for identifier in resource: items.append(f"{identifier}") resHtml.append(" | ".join(items)) resHtml.append("
") output.append("
Description
" + summary['^description'] + "
Resources
" + "".join(resHtml)) hasTags = self.generateSummaryCardTag(summary) if len(hasTags.strip()) > 0: output.append(f"
Label
{hasTags}
") if summary['__links']: output.append("
Recommendation
" + "
".join(summary['__links']) + "
") output.append("
") return "\n".join(output) def generateDonutPieChart(self, datasets, idPrefix='', typ='doughnut'): htmlId = idPrefix + typ + str(uuid.uuid1()) output = [] output.append("
".format(htmlId)) labels, enriched = self._enrichDonutPieData(datasets) self.addJS("var donutPieChartCanvas = $('#{}').get(0).getContext('2d'); var donutPieData = {{labels: {},datasets: [{}]}}".format(htmlId, json.dumps(labels), json.dumps(enriched))) self.addJS("var donutPieOptions= {{maintainAspectRatio : false,responsive : true}}; new Chart(donutPieChartCanvas, {{type: '{}', data: donutPieData, options: donutPieOptions}})".format(typ)) return '\n'.join(output) def generateBarChart(self, labels, datasets, idPrefix = ''): id = idPrefix + 'bar' + str(uuid.uuid1()) output = [] output.append("
") enriched = self._enrichChartData(datasets) self.addJS("var areaChartData = {labels: " + json.dumps(labels) + ", datasets: " + json.dumps(enriched) + "}") self.addJS("var barChartData = $.extend(true, {}, areaChartData); var stackedBarChartCanvas = $('#" + id + "').get(0).getContext('2d'); var stackedBarChartData = $.extend(true, {}, barChartData)") self.addJS(""" var stackedBarChartOptions = { responsive : true, maintainAspectRatio : false, scales: { xAxes: [{ stacked: true, }], yAxes: [{ stacked: true }] }, onClick: function(e, i){ checkCtrl = $('#checkCtrl') var v = i[0]['_model']['label']; if(typeof v == 'undefined') return curVal = checkCtrl.val() idx = curVal.indexOf(v) if (idx == -1){ curVal.push(v) }else{ curVal.splice(idx, 1); } checkCtrl.val(curVal).trigger('change') } } new Chart(stackedBarChartCanvas, { type: 'bar', data: stackedBarChartData, options: stackedBarChartOptions })""") return "\n".join(output) def generateSummaryCardTag(self, summary): text = '' text += self._generateSummaryCardTagHelper(summary.get('downtime', False), 'Have Downtime') text += ' ' + self._generateSummaryCardTagHelper(summary.get('needFullTest', False), 'Testing Required') text += ' ' + self._generateSummaryCardTagHelper(summary.get('slowness', False), 'Performance Impact') text += ' ' + self._generateSummaryCardTagHelper(summary.get('additionalCost', False), 'Cost Incurred') return text def _generateSummaryCardTagHelper(self, flag, text): if flag == False: return '' strx = text color = 'warning' if flag < 0: strx += " (maybe)" color = 'info' return f"{strx}" def _enrichDonutPieData(self, datasets): label = [] arr = { 'data': [], 'backgroundColor': [] } idx = 0 for key, num in datasets.items(): label.append(key) arr['data'].append(num) arr['backgroundColor'].append(self._randomHexColorCode(idx)) idx += 1 return [label, arr] def _enrichChartData(self, datasets): arr = [] idx = 0 for key, num in datasets.items(): arr.append({ 'label': key, 'backgroundColor': self._randomRGB(idx), 'data': num }) idx += 1 return arr def _randomRGB(self, idx): r1Arr = [226, 168, 109, 80 , 51 , 60 , 70 , 89 , 108] r2Arr = [124, 100, 75 , 63 , 51 , 78 , 105, 158, 212] r3Arr = [124, 100, 75 , 63 , 51 , 75 , 100, 148, 197] if idx >= len(r1Arr): r1 = random.randint(1,255) r2 = random.randint(1,255) r3 = random.randint(1,255) else: r1 = r1Arr[idx] r2 = r2Arr[idx] r3 = r3Arr[idx] return "rgba({}, {}, {}, 1)".format(r1, r2, r3) def _randomHexColorCode(self, idx): color = ["#e27c7c", "#a86464", "#6d4b4b", "#503f3f", "#333333", "#3c4e4b", "#466964", "#599e94", "#6cd4c5"] if idx >= len(color): return '#' + str(hex(random.randint(0, 0xFFFFFF))).lstrip('0x').rjust(6, '0') else: return color[idx] def generateTitleWithCategory(self, count, title, category, color='info'): if not category: return title return f"{count}. {title} " def generateTable(self, resource): output = [] for check, attr in resource.items(): criticality = attr['criticality'] checkPrefix = '' if criticality == 'H': checkPrefix = " " elif criticality == 'M': checkPrefix = " " output.append("") output.append("{}{}".format(checkPrefix, check)) output.append("{}".format(attr['value'])) output.append("{}".format(attr['shortDesc'])) output.append("") return "\n".join(output) def _getTemplateByKey(self, key): path = _C.TEMPLATE_DIR + '/' + self.pageTemplate[key] if os.path.exists(path): return path else: _warn(path + ' does not exists') ## # debug_print_backtrace() def buildHeader(self): output = [] #file_get_pre_css headerPreCSS = open(self._getTemplateByKey('header.precss'), 'r').read() headerPreCSS = headerPreCSS.replace('{$ADVISOR_TITLE}', Config.ADVISOR['TITLE']) headerPreCSS = headerPreCSS.replace('{$SERVICE}', self.service.upper()) output.append(headerPreCSS) if self.cssLib: for lib in self.cssLib: output.append("".format(lib)) #file_get_post_css headerPostCSS = open(self._getTemplateByKey('header.postcss'), 'r').read() output.append( headerPostCSS.replace('{$ADVISOR_TITLE}', Config.ADVISOR['TITLE']) ) return output def buildFooter(self): output = [] #file_get_template preInlineJS preJS = open(self._getTemplateByKey('footer.prejs'), 'r').read() preJS = preJS.replace('"', "'") ADMINLTE_VERSION = Config.ADMINLTE['VERSION'] ADMINLTE_DATERANGE = Config.ADMINLTE['DATERANGE'] ADMINLTE_URL = Config.ADMINLTE['URL'] ADMINLTE_TITLE = Config.ADMINLTE['TITLE'] PROJECT_TITLE = Config.ADVISOR['TITLE'] PROJECT_VERSION = Config.ADVISOR['VERSION'] x = preJS.replace('{$ADMINLTE_VERSION}', ADMINLTE_VERSION) x = x.replace('{$ADMINLTE_DATERANGE}', ADMINLTE_DATERANGE) x = x.replace('{$ADMINLTE_URL}', ADMINLTE_URL) x = x.replace('{$ADMINLTE_TITLE}', ADMINLTE_TITLE) x = x.replace('{$PROJECT_TITLE}', PROJECT_TITLE) x = x.replace('{$PROJECT_VERSION}', PROJECT_VERSION) output.append(x) if self.jsLib: for lib in self.jsLib: output.append(f"") if self.js: inlineJS = '; '.join(self.js) output.append(f"") #file_get_template postInlineJS postJS = open(self._getTemplateByKey('footer.postjs'), 'r').read() output.append(postJS) return output def buildBreadcrumb(self): output = [] breadcrumb = open(self._getTemplateByKey('breadcrumb'), 'r').read() breadcrumb = breadcrumb.replace('{$SERVICE}', self.service.upper()) output.append(breadcrumb) return output def buildNav(self): ISHOME = 'active' if self.isHome else '' output = [] #file_getsidebar sidebarPRE = open(self._getTemplateByKey('sidebar.precustom'), 'r').read() sidebarPRE = sidebarPRE.replace('{$ADVISOR_TITLE}', Config.ADVISOR['TITLE']) sidebarPRE = sidebarPRE.replace('{$ISHOME}', ISHOME) output.append(sidebarPRE) arr = self.buildNavCustomItems('Services', self.services) output.append("\n".join(arr)) arr = self.buildNavCustomItems('Frameworks', self.frameworks) output.append("\n".join(arr)) sidebarPOST = open(self._getTemplateByKey('sidebar.postcustom'), 'r').read() output.append(sidebarPOST) return output ## ## Support Framework def buildNavCustomItems(self, title, lists): services = lists activeService = self.service output = [] output.append("".format(title)) if title == 'Frameworks': services = {} for l in lists: services[l] = 0 else: services = lists for name, count in services.items(): if name == activeService: class_ = 'active' else: class_ = '' isFramework = True icon = self.frameworkIcon if name in self.serviceIcon: isFramework = False icon = self._navIcon(name) _count = count if name == 'guardduty' or isFramework == True: _count = '' output.append("".format(name, class_, icon, name.upper(), _count)) return output def _navIcon(self, service): return self.serviceIcon.get(service, 'cog') def addJS(self, js): self.js.append(js) def addJSLib(self, js): self.jsLib.append(js) def addCSSLib(self, css): self.cssLib.append(css) def checkIsLowHangingFruit(self, attr): if attr['downtime'] == 0 and attr['additionalCost'] == 0 and attr['needFullTest'] == 0: return True else: return False def buildContentSummary_default(self): output = [] ## Chart Building summary = self.reporter.cardSummary regions = self.regions labels = [] dataSets = {} for label, attrs in summary.items(): labels.append(label) res = attrs['__affectedResources'] for region in regions: cnt = 0 if region in res: cnt = len(res[region]) dataSets.setdefault(region, []).append(cnt) pid=self.getHtmlId('SummaryChart') html = self.generateBarChart(labels, dataSets) card = self.generateCard(pid, html, cardClass='warning', title='Summary', titleBadge='', collapse=True, noPadding=False) items = [[card, '']] output.append(self.generateRowWithCol(size=12, items=items, rowHtmlAttr="data-context='summaryChart'")) ## Chart completed ## Filter filterTitle = " Filter" filterByCheck = self.generateFilterByCheck(labels) filterRow = self.generateRowWithCol(size=[6, 6, 12], items=self.addSummaryControl_default(), rowHtmlAttr="data-context='summary-control'") output.append(self.generateCard(pid='summary-control', html=filterByCheck + filterRow, cardClass='info', title=filterTitle, titleBadge='', collapse=False, noPadding=False)) ## SummaryCard Building items = [] for label, attrs in summary.items(): body = self.generateSummaryCardContent(attrs) badge = self.generatePriorityPrefix(attrs['criticality'], "style='float:right'") + ' ' + self.generateCategoryBadge(attrs['__categoryMain'], "style='float:right'") card = self.generateCard(pid=self.getHtmlId(label), html=body, cardClass='', title=label, titleBadge=badge, collapse=9, noPadding=False) divHtmlAttr = "data-category='" + attrs['__categoryMain'] + "' data-criticality='" + attrs['criticality'] + "'" if self.checkIsLowHangingFruit(attrs): divHtmlAttr += " data-lhf=1" items.append([card, divHtmlAttr]) output.append(self.generateRowWithCol(size=4, items=items, rowHtmlAttr="data-context='summary'")) return output def buildContentDetail_default(self): output = [] output.append('
Detail
') details = self.reporter.getDetail() count = 1 previousCategory = "" for region, lists in details.items(): items = [] output.append("
{}
".format(region)) for identifierx, attrs in lists.items(): tab = [] identifier = identifierx category = '' checkIfCategoryPresent = identifierx.split('::') if len(checkIfCategoryPresent) == 2: category, identifier = checkIfCategoryPresent if not previousCategory: previousCategory = category tab.append("") tab.append("") tab.append("") tab.append(self.generateTable(attrs)) tab.append("
CheckCurrent ValueRecommendation
") tab = "\n".join(tab) if previousCategory != category and category != '' and count % 2 == 0: items.append([]) item = self.generateCard(pid=self.getHtmlId(identifierx), html=tab, cardClass='warning', title=self.generateTitleWithCategory(count, identifier, category), titleBadge='', collapse=False, noPadding=True) items.append([item, '']) previousCategory = category count += 1 output.append(self.generateRowWithCol(size=6, items=items, rowHtmlAttr="data-context=detail")) str = """ $('span.detailCategory').each(function(){ var t = $(this); t.parent().parent().append(""+t.data('span-category')+""); }) """ self.addJS(str) return output def generateFilterByCheck(self, labels): output = [] opts = [] for label in labels: opts.append("".format(label, label)) options = ''.join(opts) str = """
""".format(options) return str def addSummaryControl_default(self): jsServIdPrefix = "#" + self.service + '-' output = [] output.append('') items = [] str = """
""" items.append([str, '']) str = """
""" items.append([str, '']) str = """
""" items.append([str, '']) js = """ $('.select2').select2() var si = $('div[data-context="summary"] div[data-category]'); var cards = $('[data-context="summary"] div.col-md-4') $('input[name=radio_cs]').change(function(){ var v = $(this).val() var i = cards.find('button > i') if (v == 'expand') { cards.find('.collapsed-card').removeClass('collapsed-card') cards.find('div.card-body').show() i.removeClass('fa-plus').addClass('fa-minus') }else{ tmp = $('[data-context="summary"] div.col-md-4 > div:not(.collapsed-card)') tmp.addClass('collapsed-card') cards.find('div.card-body').hide() i.removeClass('fa-minus').addClass('fa-plus') } }) $('#filter-critical, #filter-pillar, #checkCtrl, #cbLowHangingFruit').change(function(){ var cb_lhf_on = $("#cbLowHangingFruit").is(':checked') var pv = $('#filter-pillar').val(); var fc = $('#filter-critical').val(); var tiArray = $('#checkCtrl').val(); var s = ''; if(pv != '-') s += '[data-category="'+pv+'"]'; if(fc != '-') s += '[data-criticality="'+fc+'"]'; if(tiArray.length > 0){ si.hide() $.each(tiArray, function(k, v){""" txt = "id = \"{}\" + v;".format(jsServIdPrefix) js += txt js += """$(id).parent().addClass('showLater'); }) if(s.length > 0){ $('div[data-context="summary"] .showLater'+s+'').show() }else{ $('.showLater').show() } $('.showLater').removeClass('showLater') }else if(s.length == 0){ si.show(); }else{ si.hide(); $('div[data-context="summary"] div'+s+'').show() } $('[data-context="summary"] .col-md-4:visible').addClass('showLater2') if(cb_lhf_on == true){ $('.showLater2').hide() $('.showLater2[data-lhf=1]').show() $('.showLater2').removeClass('showLater2') } }) """ self.addJS(js) return items