VictoriaMetrics/lib/promscrape/targetstatus.qtpl
Dmytro Kozlov ce8aade80e
lib/promscrape: adds service discovery visualization for /targets page(#2675)
* lib/promscrape: updated template

* lib/promscrape: fixed click on unhealthy and all btns

* app/vmselect: jquery scripts into static folder

Co-authored-by: f41gh7 <nik@victoriametrics.com>
2022-06-04 01:11:23 +03:00

246 lines
12 KiB
Text

{% import (
"net/url"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
) %}
{% stripspace %}
{% func TargetsResponsePlain(jts []jobTargetsStatuses, emptyJobs []string, showOriginLabels, showOnlyUnhealthy bool, err error) %}
{% if err != nil %}
{%s= err.Error() %}
{% return %}
{% endif %}
{% for _, js := range jts %}
{% if showOnlyUnhealthy && js.upCount == js.targetsTotal %}{% continue %}{% endif %}
job={%q= js.job %} ({%d js.upCount %}/{%d js.targetsTotal %}{% space %}up)
{% newline %}
{% for _, ts := range js.targetsStatus %}
{% if showOnlyUnhealthy && ts.up %}{% continue %}{% endif %}
{%s= "\t" %}
state={% if ts.up %}up{% else %}down{% endif %},{% space %}
endpoint={%s= ts.sw.Config.ScrapeURL %},{% space %}
labels={%s= promLabelsString(promrelabel.FinalizeLabels(nil, ts.sw.Config.Labels)) %},{% space %}
{% if showOriginLabels %}originalLabels={%s= promLabelsString(ts.sw.Config.OriginalLabels) %},{% space %}{% endif %}
scrapes_total={%d ts.scrapesTotal %},{% space %}
scrapes_failed={%d ts.scrapesFailed %},{% space %}
last_scrape={%f.3 ts.getDurationFromLastScrape().Seconds() %}s ago,{% space %}
scrape_duration={%d int(ts.scrapeDuration) %}ms,{% space %}
samples_scraped={%d ts.samplesScraped %},{% space %}
error={% if ts.err != nil %}{%s= ts.err.Error() %}{% endif %}
{% newline %}
{% endfor %}
{% endfor %}
{% for _, jobName := range emptyJobs %}
job={%q= jobName %} (0/0 up)
{% newline %}
{% endfor %}
{% endfunc %}
{% func TargetsResponseHTML(scrapeTargets scrapeTargets) %}
{% code
targetsStatuses := scrapeTargets.targetsStatuses
filter := scrapeTargets.requestFilter
if filter.activeTab == "" {
filter.activeTab="targets-tab"
}
%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="static/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<title>Scrape targets</title>
<script>
function collapse_all() {
for (var i = 0; i <= {%d len(targetsStatuses.jobTargetsStatuses) %}; i++) {
["table-"+i, "table-discovery-"+i, "table-empty-"+i].forEach((id) => {
let el = document.getElementById(id);
if (el) {
el.style.display = 'none';
}
})
}
}
function expand_all() {
for (var i = 0; i <= {%d len(targetsStatuses.jobTargetsStatuses) %}; i++) {
["table-"+i, "table-discovery-"+i, "table-empty-"+i].forEach((id) => {
let el = document.getElementById(id);
if (el) {
el.style.display = 'block';
}
});
}
}
</script>
</head>
<body>
<div class="navbar navbar-dark bg-dark box-shadow">
<div class="d-flex justify-content-between">
<a href="#" class="navbar-brand d-flex align-items-center ms-3" title="The High Performance Open Source Time Series Database &amp; Monitoring Solution ">
<svg xmlns="http://www.w3.org/2000/svg" id="VM_logo" viewBox="0 0 464.61 533.89" width="20" height="20" class="me-1"><defs><style>.cls-1{fill:#fff;}</style></defs><path class="cls-1" d="M459.86,467.77c9,7.67,24.12,13.49,39.3,13.69v0h1.68v0c15.18-.2,30.31-6,39.3-13.69,47.43-40.45,184.65-166.24,184.65-166.24,36.84-34.27-65.64-68.28-223.95-68.47h-1.68c-158.31.19-260.79,34.2-224,68.47C275.21,301.53,412.43,427.32,459.86,467.77Z" transform="translate(-267.7 -233.05)"/><path class="cls-1" d="M540.1,535.88c-9,7.67-24.12,13.5-39.3,13.7h-1.6c-15.18-.2-30.31-6-39.3-13.7-32.81-28-148.56-132.93-192.16-172.7v60.74c0,6.67,2.55,15.52,7.09,19.68,29.64,27.18,143.94,131.8,185.07,166.88,9,7.67,24.12,13.49,39.3,13.69v0h1.6v0c15.18-.2,30.31-6,39.3-13.69,41.13-35.08,155.43-139.7,185.07-166.88,4.54-4.16,7.09-13,7.09-19.68V363.18C688.66,403,572.91,507.9,540.1,535.88Z" transform="translate(-267.7 -233.05)"/><path class="cls-1" d="M540.1,678.64c-9,7.67-24.12,13.49-39.3,13.69v0h-1.6v0c-15.18-.2-30.31-6-39.3-13.69-32.81-28-148.56-132.94-192.16-172.7v60.73c0,6.67,2.55,15.53,7.09,19.69,29.64,27.17,143.94,131.8,185.07,166.87,9,7.67,24.12,13.5,39.3,13.7h1.6c15.18-.2,30.31-6,39.3-13.7,41.13-35.07,155.43-139.7,185.07-166.87,4.54-4.16,7.09-13,7.09-19.69V505.94C688.66,545.7,572.91,650.66,540.1,678.64Z" transform="translate(-267.7 -233.05)"/></svg>
<strong>VictoriaMetrics</strong>
</a>
</div>
</div>
<div class="container-fluid">
{% if targetsStatuses.err != nil %}
{%= errorNotification(targetsStatuses.err) %}
{% endif %}
<div class="row">
<main class="col-12">
<h1>Scrape targets</h1>
<hr />
<div class="row g-3 align-items-center mb-3">
<div class="col-auto">
<button id="all-btn" type="button" class="btn{% space %}{% if !filter.showOnlyUnhealthy %}btn-secondary{% else %}btn-success{% endif %}" onclick="location.href='?{%= queryArgs(map[string]string{
"show_only_unhealthy": "false",
"endpoint_search": filter.endpointSearch,
"label_search": filter.labelSearch,
"active_tab": filter.activeTab,
}) %}'">
All
</button>
</div>
<div class="col-auto">
<button id="unhealthy-btn" type="button" class="btn{% space %}{% if filter.showOnlyUnhealthy %}btn-secondary{% else %}btn-danger{% endif %}" onclick="location.href='?{%= queryArgs(map[string]string{
"show_only_unhealthy": "true",
"endpoint_search": filter.endpointSearch,
"label_search": filter.labelSearch,
"active_tab": filter.activeTab,
}) %}'">
Unhealthy
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" onclick="collapse_all()">
Collapse all
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-secondary" onclick="expand_all()">
Expand all
</button>
</div>
<div class="col-auto">
{% if filter.endpointSearch == "" && filter.labelSearch == "" %}
<button type="button" class="btn btn-success" onclick="document.getElementById('filters').style.display='block'">
Filter targets
</button>
{% else %}
<button type="button" class="btn btn-danger" onclick="location.href='?'">
Clear target filters
</button>
{% endif %}
</div>
</div>
<div id="filters" {% if filter.endpointSearch == "" && filter.labelSearch == "" %}style="display:none"{% endif %}>
<form class="form-horizontal">
<div class="form-group mb-3">
<label for="endpoint_search" class="col-sm-10 control-label">Endpoint filter (<a target="_blank" href="https://github.com/google/re2/wiki/Syntax">Regexp</a> is accepted)</label>
<div class="col-sm-10">
<input type="text" id="endpoint_search" name="endpoint_search"
placeholder="For example, 127.0.0.1" class="form-control" value="{%s filter.endpointSearch %}"/>
</div>
</div>
<div class="form-group mb-3">
<label for="label_search" class="col-sm-10 control-label">Labels filter (<a target="_blank" href="https://prometheus.io/docs/prometheus/latest/querying/basics/#time-series-selectors">Arbitrary time series selectors</a> are accepted)</label>
<div class="col-sm-10">
<input type="text" id="label_search" name="label_search"
placeholder="For example, {instance=~'.+:9100'}" class="form-control" value="{%s filter.labelSearch %}"/>
</div>
</div>
<input type="hidden" name="show_only_unhealthy" value="{%v filter.showOnlyUnhealthy %}"/>
<input id="tab_input" type="hidden" name="active_tab" value="{%s filter.activeTab %}" />
<button type="submit" class="btn btn-success mb-3">Submit</button>
</form>
</div>
<hr />
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button id="targets-tab" class="nav-link {%if filter.activeTab=="targets-tab"%} {% space %}active {%endif%}" data-bs-toggle="tab" data-bs-target="#targets" type="button" role="tab" aria-controls="home" aria-selected="true">Targets</button>
</li>
<li class="nav-item" role="presentation">
<button id="discovery-tab" class="nav-link {%if filter.activeTab=="discovery-tab"%} {% space %}active {%endif%}" data-bs-toggle="tab" data-bs-target="#discovery" type="button" role="tab" aria-controls="profile" aria-selected="false">Service Discovery</button>
</li>
</ul>
<div class="tab-content">
<div id="targets" class="tab-pane {%if filter.activeTab=="targets-tab"%} {% space %}active{%endif%}" role="tabpanel" aria-labelledby="targets-tab">
{%= Targets(targetsStatuses.jobTargetsStatuses, targetsStatuses.emptyJobs, filter.showOnlyUnhealthy) %}
</div>
<div id="discovery" class="tab-pane {%if filter.activeTab=="discovery-tab"%} {% space %}active{%endif%}" role="tabpanel" aria-labelledby="profile-tab">
{%= ServiceDiscovery(targetsStatuses.jobTargetsStatuses, targetsStatuses.emptyJobs, filter.showOnlyUnhealthy, targetsStatuses.droppedKeyStatuses) %}
</div>
</div>
</main>
</div>
</div>
<script src="static/js/jquery-3.6.0.min.js" type="text/javascript"></script>
<script src="static/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<script>
(function(){
const navBtns = document.querySelectorAll(".nav-link");
const tabInput = document.getElementById("tab_input");
const unhealthyBtn = document.getElementById("unhealthy-btn");
const allBtn = document.getElementById("all-btn");
navBtns.forEach((btn) => {
if (btn) {
btn.addEventListener("click", (e) => {
if (window.history.replaceState) {
var url = new URL(window.location.href);
url.searchParams.set("active_tab", e.target.id);
tabInput.value = e.target.id;
unhealthyBtn.onclick = () => {
url.searchParams.set("show_only_unhealthy", "true");
window.location.href=url;
};
allBtn.onclick = () => {
url.searchParams.set("show_only_unhealthy", "false");
window.location.href=url;
};
window.history.replaceState({}, "", url);
}
});
}
})
})()
</script>
</body>
</html>
{% endfunc %}
{% func queryArgs(m map[string]string) %}
{% code
qa := make(url.Values, len(m))
for k, v := range m {
qa[k] = []string{v}
}
%}
{%s qa.Encode() %}
{% endfunc %}
{% func formatLabel(labels []prompbmarshal.Label) %}
{
{% for i, label := range labels %}
{%s label.Name %}={%q label.Value %}
{% if i+1 < len(labels) %},{% space %}{% endif %}
{% endfor %}
}
{% endfunc %}
{% func errorNotification(err error) %}
<div class="alert alert-danger d-flex align-items-center" role="alert">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:">
<use xlink:href="#exclamation-triangle-fill"/></svg>
<div>
{%s err.Error() %}
</div>
</div>
{% endfunc %}
{% endstripspace %}