diff --git a/Documentation/User Documentation/9. create_metrics.md b/Documentation/User Documentation/9. create_metrics.md new file mode 100644 index 0000000..bea06a1 --- /dev/null +++ b/Documentation/User Documentation/9. create_metrics.md @@ -0,0 +1,101 @@ +# The New Metrics User Interface + The new Metrics User Interface for ZEBRA allows users to create and delete custom metrics for prometheus. It contains two forms: New metrics and Existing metrics form. It also displays Metrics already created by the user and users can view and delete these metrics. + +# Terminilogies + **Metric** : This represented an RMF III report and its LPAR. its format is LPAR_REPORT e.g RPRT_CPC, RPRT_CHANNEL, RPRT_SYSINFO. Only one metric can be created for each report in a single LPAR. This means only one CPC report can exist for RPRT LPAR. Below is an example of a CPC metric +``` + "RPRT_CPC": { + "lpar": "RPRT", + "request": { + "report": "CPC", + "resource": ",RPRT,MVS_IMAGE" + }, + "identifiers": [ + + ] + } +``` + + **Identifiers** : This represent the parameters required to build a prometheus client metric from a Report. e.g parameters from CPC report needed to create a Total Utilization or effective utilization prometheus metric in ZEBRA. Each Metric can have multiple identifiers. Identifiers has 5 parameters: + - **Identifier Key**: This represent the parameter in a report that has a Unique value you will like to monitor. e.g CPCHPNAM in That represent Name of partition that collected the data in CPC report + - **Identifier Value**: This takes either ALL(to represent all Identifier key values) or a specific value for the identifier key (e.g VIRPT for CPCHPNAM in CPC report to signify intrest in only VIRPT LPAR data) + - **Metric ID (m_id)**: This is a unique ID selected by user to represent the Identifier (e.g TOU for Total Utilization, EFU for effective utilization) + - **Field**: This represent a numeric field in the report from which the number will be used to plot a chart in grafana. e.g CPCPLTOU which represent Logical processor total utilization, CPCPLEFU which represent Logical processor effective utilization in CPC Report + - **Description (desc)**: This is a user choosen description for the identifier + + Below is an example of CPC metric with Total Utilization and Effective Utilization Identifiers +``` + "RPRT_CPC": { + "lpar": "RPRT", + "request": { + "report": "CPC", + "resource": ",RPRT,MVS_IMAGE" + }, + "identifiers": [ + { + "field": "CPCPLTOU", + "key": "CPCPPNAM", + "value": "ALL", + "m_id": "TOU", + "desc": "CPC Total Utilization" + }, + { + "key": "CPCPPNAM", + "value": "ALL", + "field": "CPCPLEFU", + "m_id": "EFU", + "desc": "Effective Utilization" + } + ] + } +``` + **Prometheus Metric** : These are the final metrics created by ZEBRA and exposed through http(s)://ZEBRA Ip:port/prommetric Endpoint. They are of the folloeing format: + ``` + {LPAR}_{IDENTIFIER_VALUE}_{METRIC_ID} + ``` + + example of ZEBRA Prometheus Metrics + +``` +# HELP RPRT_TRNG_TOU CPC Total Utilization +# TYPE RPRT_TRNG_TOU gauge +RPRT_TRNG_TOU{parm="CPCPLTOU"} 0.4 + +# HELP RPRT_VIDVLP_TOU CPC Total Utilization +# TYPE RPRT_VIDVLP_TOU gauge +RPRT_VIDVLP_TOU{parm="CPCPLTOU"} 0.5 + +# HELP RPRT_VIRPT_TOU CPC Total Utilization +# TYPE RPRT_VIRPT_TOU gauge +RPRT_VIRPT_TOU{parm="CPCPLTOU"} 0.5 + +# HELP RPRT_TRNG_EFU Effective Utilization +# TYPE RPRT_TRNG_EFU gauge +RPRT_TRNG_EFU{parm="CPCPLEFU"} 0.4 + +# HELP RPRT_VIDVLP_EFU Effective Utilization +# TYPE RPRT_VIDVLP_EFU gauge +RPRT_VIDVLP_EFU{parm="CPCPLEFU"} 0.4 + +# HELP RPRT_VIRPT_EFU Effective Utilization +# TYPE RPRT_VIRPT_EFU gauge +RPRT_VIRPT_EFU{parm="CPCPLEFU"} 0.5 +``` + +# How To create a Metric +- Using the metrics user interface (http://zebraIP:port/metrics) +- Click on New Metric. Use this form to add a new metric with one identifier +- Click Save + +# How to Add Identifiers +- Using the metrics user interface (http://zebraIP:port/metrics) +- Click on Existing Metric +- Select The Metric from the drop Down +- Fill the form +- Click Save + +# View and Delete Metric/Identifier +- From the dropdown under metrics added in the center of the page +- Select a metric to view it details +- Click Delete ID to delete a single identifier +- Click Delete Metric to delete the metric and its Identifiers diff --git a/src/.dockerignore b/src/.dockerignore index e42e335..1949651 100644 --- a/src/.dockerignore +++ b/src/.dockerignore @@ -3,6 +3,7 @@ db data my.db my.dbrefresh +admin.db # node modules node_modules diff --git a/src/Auth.js b/src/Auth.js index 7f966b6..aff954f 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -49,7 +49,6 @@ module.exports.token = function(req, res){ res.json({accessToken: accessToken}) }) }); - }catch(err){ res.send("Token Generation Failed"); } diff --git a/src/admin.db b/src/admin.db new file mode 100644 index 0000000..5bc02da Binary files /dev/null and b/src/admin.db differ diff --git a/src/app.js b/src/app.js index 0efc8bc..3e9eb55 100644 --- a/src/app.js +++ b/src/app.js @@ -27,6 +27,7 @@ var path = require('path'); var cookieParser = require('cookie-parser'); const fs = require('fs'); var session = require('express-session'); +const tls = require('tls'); const cors = require('cors'); const bodyParser = require('body-parser'); @@ -73,6 +74,7 @@ if(lpar_prom.length > 0){ } //require("./Eureka_conn"); process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0; +tls.DEFAULT_MIN_VERSION = "TLSv1.1"; var mainRouter = require('./app_server/routes/mainRouter'); var rmf3Router = require('./app_server/routes/rmf3Router'); var rmfppRouter = require('./app_server/routes/rmfppRouter'); diff --git a/src/app_server/routes/mainRouter.js b/src/app_server/routes/mainRouter.js index 373a6b1..79b20db 100644 --- a/src/app_server/routes/mainRouter.js +++ b/src/app_server/routes/mainRouter.js @@ -57,14 +57,14 @@ function parameters(fn){ usePrometheus: Zconfig.usePrometheus, https: Zconfig.https, grafanaurl: Zconfig.grafanaurl, - grafanaport: Zconfig.grafanaport + grafanaport: Zconfig.grafanaport } fn(parms); //return the parameters } -router.get('/pwdd', (req, res) => { //remember to delete +/* router.get('/pwdd', (req, res) => { //remember to delete res.render("login", {data: "pwd"}) -}) +}) */ // Checks if user login session is available in browser var sessionChecker = (req, res, next) => { @@ -96,6 +96,49 @@ router.post('/delmtr', (req, res) => { }); }); +router.post('/delid', (req, res) => { + fs.readFile('metrics.json', (err, data) => { + if (err) throw err; + let metricsfile = JSON.parse(data); + var id_len = metricsfile[req.body.ky]["identifiers"].length; + for(i=0; i < id_len; i++){ + if (metricsfile[req.body.ky]["identifiers"][i]["m_id"] == req.body.id){ + metricsfile[req.body.ky]["identifiers"].splice(i,1); + break; + } + } + fs.writeFile("metrics.json", JSON.stringify(metricsfile, null, '\t'), 'utf-8', function(err, data) { + res.send("Metric ID Deleted Successfully"); + }); + }); +}); + + +router.get('/getmetr', (req, res) => { + fs.readFile('metrics.json', (err, data) => { + if (err) throw err; + let metricsfile = JSON.parse(data); + res.send({sc:Object.keys(metricsfile)}); + }); +}) + +router.post('/getmetricdis', (req, res) => { + try{ + var metric = req.body.metric; + fs.readFile('metrics.json', (err, data) => { + if (err) throw err; + let metricsfile = JSON.parse(data); + let mtr = metricsfile[metric] + res.send({sysid: mtr.lpar, rpt: mtr.request.report, rsc: mtr.request.resource, ids:mtr.identifiers}); + }); + + }catch(err){ + res.send("error") + + } + +}) + router.post('/savemtr', (req, res) => { try{ var lpar = req.body.lpar; @@ -106,7 +149,7 @@ router.post('/savemtr', (req, res) => { var umi = req.body.umi; var umd = req.body.umd; var rst = req.body.rst; - var key = `${lpar}_${snvl}_${umi}`; + var key = `${lpar}_${rpt}`; var mtr = JSON.parse(`{ "lpar": "${lpar}", "request": { @@ -116,18 +159,56 @@ router.post('/savemtr', (req, res) => { "identifiers": [ { "key": "${nid}", - "value": "${snvl}" + "value": "${snvl}", + "field": "${vid}", + "m_id": "${umi}", + "desc": "${umd}" } - ], - "field": "${vid}", - "desc": "${umd}" + ] }`) fs.readFile('metrics.json', (err, data) => { if (err) throw err; let metricsfile = JSON.parse(data); - metricsfile[`${key}`] = mtr + metrkeys = Object.keys(metricsfile); + if(metrkeys.includes(key)){ + res.status(304).send(); + }else { + metricsfile[`${key}`] = mtr + fs.writeFile("metrics.json", JSON.stringify(metricsfile, null, '\t'), 'utf-8', function(err, data) { + res.send("Metric Added Successfully"); + }); + } + }); + + }catch(err){ + res.send("error") + } +}) + +router.post('/saveexmtr', (req, res) => { + try{ + var metr = req.body.metr; + var nidex = req.body.nidex; + var snvlex = req.body.snvlex; + var videx = req.body.videx; + var umiex = req.body.umiex; + var umdex = req.body.umdex; + var idf = JSON.parse(` + { + + "key": "${nidex}", + "value": "${snvlex}", + "field": "${videx}", + "m_id": "${umiex}", + "desc": "${umdex}" + } + `) + fs.readFile('metrics.json', (err, data) => { + if (err) throw err; + let metricsfile = JSON.parse(data); + (metricsfile[`${metr}`]["identifiers"]).push(idf) fs.writeFile("metrics.json", JSON.stringify(metricsfile, null, '\t'), 'utf-8', function(err, data) { - res.send("Metric Added Successfully"); + res.send("Metric Identifier Successfully"); }); }); @@ -201,9 +282,9 @@ router.get('/metrics', sessionChecker, (req, res) => { } //console.log(c); if(req.session.name){ //Check if User login session is available - res.render("metrics",{msg:"Admin", resources:resource, lpars:lpar, reports:REPORTS.RMFM3}); // render the metrics page wih Admin previledge + res.render("metricdev",{msg:"Admin", resources:resource, lpars:lpar, reports:REPORTS.RMFM3}); // render the metrics page wih Admin previledge }else{ - res.render("metrics", {resources:resource, lpars:lpar, reports:REPORTS.RMFM3}); + res.render("metricdev", {resources:resource, lpars:lpar, reports:REPORTS.RMFM3}); } }) diff --git a/src/app_server/views/metricdev.pug b/src/app_server/views/metricdev.pug new file mode 100644 index 0000000..071fd48 --- /dev/null +++ b/src/app_server/views/metricdev.pug @@ -0,0 +1,478 @@ +extends layout + +block header + link(rel='stylesheet', href='/stylesheets/slider.css') + style. + .center { + margin: 0; + position: absolute; + top: 50%; + left: 50%; + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + } + + .container { + height: 200px; + position: relative; + } + + form { + --background: white; + --border: rgba(0, 0, 0, 0.125); + --borderDark: rgba(0, 0, 0, 0.25); + --borderDarker: rgba(0, 0, 0, 0.5); + --bgColorH: 0; + --bgColorS: 0%; + --bgColorL: 98%; + --fgColorH: 210; + --fgColorS: 50%; + --fgColorL: 38%; + --shadeDark: 0.3; + --shadeLight: 0.7; + --shadeNormal: 0.5; + --borderRadius: 0.125rem; + --highlight: #306090; + background: white; + border: 1px solid var(--border); + border-radius: var(--borderRadius); + box-shadow: 0 1rem 1rem -0.75rem var(--border); + display: flex; + flex-direction: column; + padding: 1rem; + position: relative; + overflow: hidden; + width:80%; + } + + .topa{ + width:80%; + margin: auto; + border: 1%; + padding: 10px; + } + + table { + font-family: arial, sans-serif; + border-collapse: collapse; + width: 96%; + } + + td, th { + border: 1px solid #dddddd; + text-align: left; + padding: 8px; + } + + tr:nth-child(even) { + background-color: #dddddd; + } + + /* Style the tab */ + .tab { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; + } + + /* Style the buttons inside the tab */ + .tab button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 14px 16px; + transition: 0.3s; + font-size: 17px; + } + + /* Change background color of buttons on hover */ + .tab button:hover { + background-color: #ddd; + } + + /* Create an active/current tablink class */ + .tab button.active { + background-color: #ccc; + } + + /* Style the tab content */ + .tabcontent { + display: none; + padding: 6px 12px; + border: 1px solid #ccc; + border-top: none; + } + +block content + div(class="sidefm") + .salis(style={"width":"96%", "margin-top":"0px", "margin-bottom":"30px", "margin-left": "5%"}) + + div(class="tab") + button(class="tablinks" onclick="openTab(event, 'New')") New Metric + button(class="tablinks" onclick="openTab(event, 'Existing')") Existing Metric + div(id="New" class="tabcontent") + .row + form() + .row + .col-md-6 + label(style={"margin-top":"5px", "margin-right": "10px", "font-size":"12px"} for=`sysid`) SYSID + select.custom-select(class="form-control-sm" name="lpar" id="lpar" style={"margin-right": "10px"}) + option(selected='') Select LPAR + - for(dat of lpars) + option(value=`${dat}`) !{dat} + .col-md-6 + label(style={"margin-top":"5px", "margin-right": "10px", "font-size":"12px"} for=`rpt`) Report + select.custom-select(class="form-control-sm" name="rpt" id="rpt" style={"margin-right": "10px"} onchange="getrpt()") + option(selected='') Select + each report in reports + option(value=report) #{report} + label(style={"margin-top":"5px", "margin-right": "10px"} for="mid") Unique Metric id + input(type="text" class="form-control-sm" id=`umi` style={"margin-right": "10px"} placeholder=`e.g TOU for Total utilization`) + label(style={"margin-top":"5px", "margin-right": "10px"}) Resource + select.custom-select(class="form-control-sm" name="rst" id="rst" style={"margin-right": "10px"}) + option(selected='') Select Resource + - for(dt of resources) + option(value=`${dt}`) !{dt} + label(style={"margin-top":"5px", "margin-right": "10px"}) Identifier Key + select.custom-select(class="form-control-sm" name="prm" id="nid" style={"margin-right": "10px"} onchange="getnvl()") + option(selected='') Select + label(style={"margin-top":"5px", "margin-right": "10px"}) Identifier Value + select.custom-select(class="form-control-sm" name="nvl" id="snvl" style={"margin-right": "10px"}) + option(selected='*') ALL + label(style={"margin-top":"5px", "margin-right": "10px"}) Field + select.custom-select(class="form-control-sm" name="vrm" id="vid" style={"margin-right": "10px"}) + option(selected='') Select + label(style={"margin-top":"5px", "margin-right": "10px"}) Metric Description + input(type="text" class="form-control-sm" id=`umd` style={"margin-right": "10px"} placeholder=`A simple metrics description`) + + a(href='javascript:savemtr()' class='btn btn-primary' id="rmf3b", style={"margin-top":"13px", "width":"98%", "color":"white"}) Save + + div(id="Existing" class="tabcontent") + .salis(style={"width":"96%", "margin-top":"0px", "margin-bottom":"30px", "margin-left": "5%"}) + .row + form() + label(style={"margin-top":"5px", "margin-right": "10px"}) Metric(s) + select.custom-select(class="form-control-sm" name="metr" id="metr" style={"margin-right": "10px"} onchange="getrptex()") + option(selected='') Select Metric + label(style={"margin-top":"5px", "margin-right": "10px"} for="mid") Unique Metric id + input(type="text" class="form-control-sm" id=`umiex` style={"margin-right": "10px"} placeholder=`e.g TOU for Total utilization`) + label(style={"margin-top":"5px", "margin-right": "10px"}) Identifier Key + select.custom-select(class="form-control-sm" name="prmex" id="nidex" style={"margin-right": "10px"} onchange="getnvlex()") + option(selected='') Select + label(style={"margin-top":"5px", "margin-right": "10px"}) Identifier Value + select.custom-select(class="form-control-sm" name="nvlex" id="snvlex" style={"margin-right": "10px"}) + option(selected='*') ALL + label(style={"margin-top":"5px", "margin-right": "10px"}) Field + select.custom-select(class="form-control-sm" name="vrmex" id="videx" style={"margin-right": "10px"}) + option(selected='') Select + label(style={"margin-top":"5px", "margin-right": "10px"}) Metric Description + input(type="text" class="form-control-sm" id=`umdex` style={"margin-right": "10px"} placeholder=`A simple metrics description`) + + + a(href='javascript:saveexmtr()' class='btn btn-primary' id="rmf3b", style={"margin-top":"13px", "width":"98%", "color":"white"}) Save + + + div(class="mainfm", style={"overflow" : "auto"}) + h5 Add Custom Metrics Page + p Use the form to add customize ZEBRA Prometheus Metrics. Note, you will need to first configure ZEBRA and have a working internet connection + br + p Full information on how the form works can be found in the ZEBRA documentation. + h5 Metrics Added + div(style={"overflow":"auto", "max-height":"50vh"}) + .row(style={"margin-right": "30px", "margin-left": "10px"}) + label(style={"margin-top":"5px", "margin-right": "10px"}) Metric(s) + select.custom-select(class="form-control-sm" name="metrdis" id="metrdis" style={"margin-right": "10px"} onchange="getmetrdis()") + option(selected='') Select Metric + div(style={"clear": "both", "margin-top":"10px"}) + p(style={"float": "left", "font-size": "18px"}) SYSID: + p(id="sysid" style={"font-weight": "bold", "float": "right", "margin-left":"5px", "font-size": "18px"}) + hr + div(style={"clear": "both", "margin-top":"10px"}) + p(style={"float": "left", "font-size": "18px"}) Report: + p(id="rptdis" style={"font-weight": "bold", "float": "right", "margin-left":"5px", "font-size": "18px"}) + hr + div(style={"clear": "both", "margin-top":"10px"}) + p(style={"float": "left", "font-size": "18px"}) Resource: + p(id="rsc" style={"font-weight": "bold", "float": "right", "margin-left":"5px", "font-size": "18px"}) + hr + div(style={"clear": "both", "margin-top":"10px"}) + a(href='javascript:delmtr()' class='btn btn-danger' id="rmf3b", style={ "width":"100%", "float": "left", "color":"white"}) Delete Metric + + table(id="sctable") + tr + th Identifier Name + th Identifier Value + th Field + th Metric ID + th Description + th Action + + + +block scripts + script(src='https://code.jquery.com/jquery-3.5.1.min.js') + script(src='/stylesheets/bootstrap/js/bootstrap.min.js') + script(src='/stylesheets/bootstrap/js/bootstrap.bundle.min.js') + script. + + function getmetrdis() { + var data = {metric: $("#metrdis").val()}; + $.ajax({ + dataType: "json", + type: 'post', + url: '/getmetricdis', + data: data, + }) + .done(function(e){ + //window.reloadTable("sctable"); + //$("#sctable > tr").empty(); + $('#sysid').text(`${e.sysid}`) + $('#rptdis').text(`${e.rpt}`) + $('#rsc').text(`${e.rsc}`) + $("#sctable").find("tr:gt(0)").remove(); + $.each(e.ids, function(i){ + var len = $("#sctable tr").toArray().length; + var newRow = "