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("")
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("\n"
"\n"
" \n"
"{} {}
\n"
" \n"
" ".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("Check Current Value Recommendation ")
tab.append(" ")
tab.append(self.generateTable(attrs))
tab.append("
")
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 = """
Pillar
All
Operation Excellence
Reliablity
Security
Performance Efficiency
Cost Optimization
*Text*
"""
items.append([str, ''])
str = """
Criticality
All
High
Medium
Low
Informational
"""
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