'server',
'rds' => 'database',
's3' => 'hdd',
'iam' => 'users',
'guardduty' => 'shield-alt',
'opensearch' => 'warehouse'
];
protected $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',
];
protected $isHome = false;
private $js = [];
private $jsLib = [];
private $cssLib = [];
function __construct($service, $reporter, $services, $regions){
$this->service = $service;
$this->services = $services;
$this->regions = $regions;
$this->reporter = $reporter;
$this->idPrefix = $this->service.'-';
}
function getHtmlId($el=''){
$el = $el ?? substr(uniqid(rand(10000,99999)), 0, 11);
return $this->idPrefix . $el;
}
function buildPage(){
$this->init();
$output = [];
$output[] = $this->buildHeader();
$output[] = $this->buildNav();
$output[] = $this->buildBreadcrumb();
$output[] = $this->buildContentSummary();
$output[] = $this->buildContentDetail();
$output[] = $this->buildFooter();
$finalHTML = "";
foreach($output as $arrayOfText){
if(!empty($arrayOfText))
$finalHTML .= implode("\n", $arrayOfText);
}
file_put_contents(HTML_DIR.'/'.$this->service.'.html', $finalHTML);
}
function init(){
$this->template = 'default';
}
function buildContentSummary(){
$method = 'buildContentSummary_' . $this->template;
if(method_exists($this, $method)){
return $this->$method();
}else{
$cls = get_class($this);
__info("[$cls] Template for ContentSummary not found: " . $method);
}
}
function buildContentDetail(){
$method = 'buildContentDetail_' . $this->template;
if(method_exists($this, $method)){
return $this->$method();
}else{
$cls = get_class($this);
__info("[$cls] Template for ContentDetail not found: " . $method);
}
}
function generateRowWithCol($size=12, $items, $rowHtmlAttr=''){
$output = [];
$output[] = "
";
$__size = $size;
foreach($items as $ind => $item){
if(is_array($size)){
$i = $ind % sizeof($size);
$__size = $size[$i];
}
$output[] = $this->generateCol($__size, $item);
}
$output[] = "
";
return implode("\n", $output);
}
function generateCol($size=12, $item){
$output = [];
if(empty($item)){
$output[] = "";
}else{
list($html, $divAttr) = $item;
$output[] = "
";
$output[] = $html;
$output[] = "
";
}
return implode("\n", $output);
}
function generateCard($id, $html, $cardClass='warning', $title='', $titleBadge='',$collapse=false, $noPadding=false){
$output = [];
$lteCardClass = empty($cardClass) ? '' : "card-$cardClass";
$defaultCollapseClass = ($collapse === 9) ? "collapsed-card" : "";
$defaultCollapseIcon = ($collapse === 9) ? "plus" : "minus";
$output[] = "
";
if(!empty($title)){
$output[] = "";
}
$noPadClass = $noPadding ? 'p-0' : '';
$output[] = "
";
$output[] = $html;
$output[] = "
";
$output[] = "
";
return implode("\n", $output);
}
function generateCategoryBadge($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( !in_array($category, $validCategory)){
$category = 'X';
$color = 'info';
$name = 'Suggestion';
}else{
$indexOf = array_search($category, $validCategory);
$color = $colorByCategory[$indexOf];
$name = $nameByCategory[$indexOf];
}
return "
$name";
}
function generatePriorityPrefix($criticality, $addtionalHtmlAttr){
$validCategory = ['I', 'L', 'M', 'H'];
$colorByCategory = ['info', 'primary', 'warning', 'danger'];
$iconByCategory = ['info-circle', 'eye', 'exclamation-triangle', 'ban'];
$criticality = in_array($criticality, $validCategory) ? $criticality : $validCategory[0];
$indexOf = array_search($criticality, $validCategory);
$color = $colorByCategory[$indexOf];
$icon = $iconByCategory[$indexOf];
# return "
$criticality";
return "
";
}
function generateSummaryCardContent($summary){
$output = [];
$resources = $summary['__affectedResources'];
$resHtml = [];
foreach($resources as $region => $resource){
$items = [];
$resHtml[] = "
$region: ";
foreach($resource as $identifier){
$items[] = "$identifier";
}
$resHtml[] = implode(" | ", $items);
$resHtml[] = "";
}
$output[] = "
- Description
- ".$summary['^description']."
- Resources
".implode("", $resHtml);
$hasTags = $this->generateSummaryCardTag($summary);
if(strlen(trim($hasTags)) > 0){
$output[] = "- Label
- $hasTags
";
}
if(!empty($summary['__links'])){
$output[] = "- Recommendation
";
$output[] = "- " . implode("
- ", $summary['__links']) . "
";
}
$output[] = "
";
return implode("\n", $output);
}
function generateDonutPieChart($datasets, $idPrefix = '', $type='doughnut'){
$id = $idPrefix . $type . uniqid(get_class($this));
$output = [];
$output[] = "
";
list($labels, $enriched) = $this->__enrichDonutPieData($datasets);
$this->addJS(
"var donutPieChartCanvas = $('#$id').get(0).getContext('2d')
var donutPieData = {
labels: ".json_encode($labels).",
datasets: [".json_encode($enriched)."]
}");
$this->addJS(
"var donutPieOptions = {
maintainAspectRatio : false,
responsive : true,
}
new Chart(donutPieChartCanvas, {
type: '$type',
data: donutPieData,
options: donutPieOptions
})");
return implode("\n", $output);
}
function generateBarChart($labels, $datasets, $idPrefix = ''){
$id = $idPrefix . 'bar' . uniqid(get_class($this));
$output = [];
$output[] = "
";
$enriched = $this->__enrichChartData($datasets);
$this->addJS("var areaChartData = {
labels: ".json_encode($labels).",
datasets: ".json_encode($enriched)."
}");
$this->addJS("var barChartData = $.extend(true, {}, areaChartData)
var stackedBarChartCanvas = $('#$id').get(0).getContext('2d')
var stackedBarChartData = $.extend(true, {}, barChartData)");
$this->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 implode("\n", $output);
}
function generateSummaryCardTag($summary){
$text = '';
$text .= $this->__generateSummaryCardTagHelper($summary['downtime'] ?? false, 'Have Downtime');
$text .= ' ' . $this->__generateSummaryCardTagHelper($summary['needFullTest'] ?? false, 'Testing Required');
$text .= ' ' . $this->__generateSummaryCardTagHelper($summary['slowness'] ?? false, 'Performance Impact');
$text .= ' ' . $this->__generateSummaryCardTagHelper($summary['additionalCost'] ?? false, 'Cost Incurred');
return $text;
}
function __generateSummaryCardTagHelper($flag, $text){
if($flag == false)
return;
$str = $text;
$color = 'warning';
if($flag < 0){
$str .= " (maybe)";
$color = 'info';
}
return "
$str";
}
function __enrichDonutPieData($datasets){
$label = [];
$arr = [
'data' => [],
'backgroundColor' => []
];
$idx = 0;
foreach($datasets as $key => $num){
$label[] = $key;
$arr['data'][] = $num;
$arr['backgroundColor'][] = $this->__randomHexColorCode($idx);
$idx++;
}
return [$label, $arr];
}
function __enrichChartData($datasets){
$arr = [];
$idx = 0;
foreach($datasets as $key => $num){
$arr[] = [
'label' => $key,
'backgroundColor' => $this->__randomRGB($idx),
'data' => $num
];
$idx++;
}
return $arr;
}
function __randomRGB($idx){
# ["#e27c7c", "#a86464", "#6d4b4b", "#503f3f", "#333333", "#3c4e4b", "#466964", "#599e94", "#6cd4c5"]
# only random if more than > 9
$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 >= sizeof($r1Arr)){
$r1 = rand(1,255);
$r2 = rand(1,255);
$r3 = rand(1,255);
}else{
$r1 = $r1Arr[$idx];
$r2 = $r2Arr[$idx];
$r3 = $r3Arr[$idx];
}
return "rgba($r1, $r2, $r3, 1)";
}
function __randomHexColorCode($idx){
$color = ["#e27c7c", "#a86464", "#6d4b4b", "#503f3f", "#333333", "#3c4e4b", "#466964", "#599e94", "#6cd4c5"];
if($idx >= sizeof($color))
return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);
else
return $color[$idx];
}
function generateTitleWithCategory($count, $title, $category, $color='info'){
if(empty($category))
return $title;
return "$count. $title
";
}
function generateTable($resource){
$output = [];
foreach($resource as $check => $attr){
$criticality = $attr['criticality'];
$checkPrefix = '';
if($criticality == 'H'){
$checkPrefix = "
";
} else if($criticality == 'M'){
$checkPrefix = "
";
}
$output[] = "
";
$output[] = "$checkPrefix$check | ";
$output[] = "" . $attr['value'] . " | ";
$output[] = "" . $attr['shortDesc'] . " | ";
$output[] = "
";
}
return implode("\n", $output);
}
function __getTemplateByKey($key){
$path = TEMPLATE_DIR . '/' . $this->pageTemplate[$key];
if(file_exists($path)){
return $path;
}else{
__warn[$path . ' does not exists'];
debug_print_backtrace();
}
}
### ---------- HEADER & FOOTER ----------
function buildHeader(){
$output = [];
#file_get_pre_css
$headerPreCSS = file_get_contents( $this->__getTemplateByKey('header.precss') );
$output[] = str_replace(
['{$ADVISOR_TITLE}', '{$SERVICE}'],
[CONFIG::ADVISOR['TITLE'], strtoupper($this->service)],
$headerPreCSS
);
if(!empty($this->cssLib)){
foreach($this->cssLib as $lib){
$output[] = "
";
}
}
#file_get_post_css
$headerPostCSS = file_get_contents( $this->__getTemplateByKey('header.postcss') );
$output[] = str_replace(
['{$ADVISOR_TITLE}'],
[CONFIG::ADVISOR['TITLE']],
$headerPostCSS);
return $output;
}
function buildFooter(){
$output = [];
#file_get_template preInlineJS
$preJS = file_get_contents ( $this->__getTemplateByKey('footer.prejs') );
$preJS = str_replace('"', "'", $preJS);
$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'];
eval("\$x = \"$preJS\";");
$output[] = $x;
if(!empty($this->jsLib)){
foreach($jsLib as $lib)
$output[] = "";
}
if(!empty($this->js)){
$inlineJS = implode("; ", $this->js);
$output[] = "";
}
#file_get_template postInlineJS
$postJS = file_get_contents ( $this->__getTemplateByKey('footer.postjs') );
$output[] = $postJS;
return $output;
}
### [END] ---------- HEADER & FOOTER ----------
### ---------- NAVIGATION ----------
function buildBreadcrumb(){
$output=[];
$breadcrumb = file_get_contents( $this->__getTemplateByKey('breadcrumb') );
$output[] = str_replace(
['{$SERVICE}'],
[strtoupper($this->service)],
$breadcrumb
);
return $output;
}
function buildNav(){
$ISHOME = $this->isHome ? 'active' : '';
$output = [];
#file_getsidebar
$sidebarPRE = file_get_contents( $this->__getTemplateByKey('sidebar.precustom') );
$output[] = str_replace(
['{$ADVISOR_TITLE}', '{$ISHOME}'],
[CONFIG::ADVISOR['TITLE'], $ISHOME],
$sidebarPRE
);
$arr = $this->buildNavCustomItems();
$output[] = implode("\n", $arr);
$sidebarPOST = file_get_contents( $this->__getTemplateByKey('sidebar.postcustom') );
$output[] = $sidebarPOST;
return $output;
}
function buildNavCustomItems(){
$services = $this->services;
$activeService = $this->service;
$output = [];
$output[] = "";
foreach($services as $name => $count){
$class = $name == $activeService ? 'active' : '';
$icon = $this->__navIcon($name);
$__count = $count;
if($name=='guardduty') $__count = '';
$output[] = "
".strtoupper($name)." {$__count}
";
}
return $output;
}
function __navIcon($service){
return self::serviceIcon[$service] ?? 'cog';
}
### [END] ---------- NAVIGATION ----------
function addJS($js){
$this->js[] = $js;
}
function addJSLib($js){
$this->jslib[] = $js;
}
function addCSSLib($css){
$this->cssLib[] = $css;
}
########### TEMPLATE HERE #############
function checkIsLowHangingFruit($attr){
if($attr['downtime'] == 0 && $attr['additionalCost'] == 0 && $attr['needFullTest'] == 0)
return true;
return false;
}
function buildContentSummary_default(){
$output = [];
## Chart Building
$summary = $this->reporter->cardSummary;
$regions = $this->regions;
$labels = [];
$dataSets = [];
foreach($summary as $label => $attrs){
$labels[] = $label;
$res = $attrs['__affectedResources'];
foreach($regions as $region){
$cnt = 0;
if(!empty($res[$region]))
$cnt = sizeof($res[$region]);
$dataSets[$region][] = $cnt;
}
}
$html = $this->generateBarChart($labels, $dataSets);
$card = $this->generateCard($id=$this->getHtmlId('SummaryChart'), $html, $cardClass='warning', $title='Summary', '', $collapse=true);
$items = [[$card, '']];
$output[] = $this->generateRowWithCol($size=12, $items, "data-context='summaryChart'");
## Chart completed
## Filter
$filterTitle = "
Filter";
$filterByCheck = $this->generateFilterByCheck($labels);
$filterRow = $this->generateRowWithCol($size=[6, 6, 12], $this->addSummaryControl_default(), "data-context='summary-control'");
$output[] = $this->generateCard($id='summary-control', $filterByCheck . $filterRow, 'info', $filterTitle, '');
## SummaryCard Building
$items = [];
foreach($summary as $label => $attrs){
$body = $this->generateSummaryCardContent($attrs);
$badge = $this->generatePriorityPrefix($attrs['criticality'], "style='float:right'")
. ' ' . $this->generateCategoryBadge($attrs['__categoryMain'], "style='float:right'");
$card = $this->generateCard($id=$this->getHtmlId($label), $body, '', $label, $titleBadge=$badge, $collapse=9);
$divHtmlAttr = "data-category='".$attrs['__categoryMain']."' data-criticality='".$attrs['criticality']."'";
if($this->checkIsLowHangingFruit($attrs))
$divHtmlAttr .= " data-lhf=1";
$items[] = [$card, $divHtmlAttr];
}
$output[] = $this->generateRowWithCol($size=4, $items, "data-context='summary'");
return $output;
}
function buildContentDetail_default(){
$output = [];
$output[] = '
Detail
';
$details = $this->reporter->getDetail();
$count = 1;
$previousCategory = "";
foreach($details as $region => $lists){
$items = [];
$output[] = "
$region
";
foreach($lists as $identifierx => $attrs){
$tab = [];
$identifier = $identifierx;
$category = '';
$checkIfCategoryPresent = explode('::', $identifierx);
if(sizeof($checkIfCategoryPresent) == 2){
list($category, $identifier) = $checkIfCategoryPresent;
if(empty($previousCategory))
$previousCategory = $category;
}
$tab[] = "
";
$tab[] = "Check | Current Value | Recommendation | ";
$tab[] = "
";
$tab[] = $this->generateTable($attrs);
$tab[] = "
";
$tab = implode("\n", $tab);
if($previousCategory != $category && $category != '' && $count%2 == 0){
$items[] = [];
}
$item = $this->generateCard($id=$this->getHtmlId($identifierx), $tab, $cardClass='warning', $title=$this->generateTitleWithCategory($count, $identifier, $category), '', false, true);
$items[] = [$item, $divHtmlAttr=''];
$previousCategory = $category;
$count++;
}
$output[] = $this->generateRowWithCol($size=6, $items, "data-context=detail");
}
$str = <<
"+t.data('span-category')+"");
})
EOL;
$this->addJS($str);
return $output;
}
function generateFilterByCheck($labels){
$output = [];
$opts = [];
foreach($labels as $label)
$opts[] = "";
$options = implode("", $opts);
$str = <<
EOL;
return $str;
}
function addSummaryControl_default(){
$jsServIdPrefix = "#" . $this->service . '-';
$output=[];
$output[] = '';
$items = [];
$str = <<