Fixed author and resource detail page (#59)

This commit is contained in:
DuncanSmith1126 2019-11-14 12:49:21 -08:00 коммит произвёл Thomas Hargrove
Родитель 630fbc9d83
Коммит 18de6643a5
4 изменённых файлов: 287 добавлений и 229 удалений

Просмотреть файл

@ -87,7 +87,12 @@ For full license text, see LICENSE.txt file in the repo root or https://opensour
<a href="{{.Url}}" target="_blank">{{.Text}}</a><br/>
{{end}}
</div><div id="d3_here" class="svg-container" style='width: 100%; height:100%;'>
</div>
<div id="d3_here" class="svg-container" style='width: 100%; height:100%;'>
<div id="tooltip_container">
</div>
</div>
<script src="https://d3js.org/d3.v5.js"></script>
<script

Просмотреть файл

@ -15,6 +15,10 @@ html, body {
width: 100%;
}
#resource_container {
width: 100%;
}
#resource_event_table td, #resource_event_table th {
border: 1px solid lightgrey;
padding: 4px;

Просмотреть файл

@ -20,214 +20,218 @@ For full license text, see LICENSE.txt file in the repo root or https://opensour
</head>
<body>
<h2>Details</h2>
<b>Name</b>: {{.Name}}<br>
<b>Namespace</b>: {{.Namespace}}<br>
<b>Kind</b>: {{.Kind}}<br><br>
<div id="resource_container">
<a href="{{.SelfUrl}}" target="_blank">Open In New Tab</a><br>
<h2>Details</h2>
<b>Name</b>: {{.Name}}<br>
<b>Namespace</b>: {{.Namespace}}<br>
<b>Kind</b>: {{.Kind}}<br><br>
<a href="{{.SelfUrl}}" target="_blank">Open In New Tab</a><br>
{{if ne (len .Links) 0 }}
<h2>Links</h2>
{{range .Links}}<a href="{{.Url}}" target="_blank">{{.Text}}</a><br>{{end}}
{{end}}
{{if ne (len .Links) 0 }}
<h2>Links</h2>
{{range .Links}}<a href="{{.Url}}" target="_blank">{{.Text}}</a><br>{{end}}
{{end}}
<div id="resource_payload">
<h2>Payloads from ${ getStartTime} to ${getEndTime}</h2>
<table id="resource_event_table">
<tr>
<th>Payload Link</th>
<th @click="sort('payloadTime')">PayloadTime &#x21C5</th>
</tr>
<tr v-for="res in sortedResPayloads">
<td><a :href ="res.origValue | get_payload_url" target="_blank">Details</a></td>
<td>${ res.payloadTime | get_formatted_time}</td>
</tr>
</table>
</div>
<div id="resource_payload">
<h2>Payloads from ${ getStartTime} to ${getEndTime}</h2>
<table id="resource_event_table">
<tr>
<th>Payload Link</th>
<th @click="sort('payloadTime')">PayloadTime &#x21C5</th>
</tr>
<tr v-for="res in sortedResPayloads">
<td><a :href ="res.origValue | get_payload_url" target="_blank">Details</a></td>
<td>${ res.payloadTime | get_formatted_time}</td>
</tr>
</table>
</div>
<script>
new Vue({
el: '#resource_payload',
delimiters: ['${', '}'],
data: {
resPayload: [],
currentSortBy: 'payloadTime',
currentSortDirection: 'asc',
startTime: ''
new Vue({
el: '#resource_payload',
delimiters: ['${', '}'],
data: {
resPayload: [],
currentSortBy: 'payloadTime',
currentSortDirection: 'asc',
startTime: ''
},
filters: {
get_formatted_time(unixnano_time) {
return new Date(unixnano_time/1000000).toISOString().split('T').join(' ');
},
filters: {
get_formatted_time(unixnano_time) {
return new Date(unixnano_time/1000000).toISOString().split('T').join(' ');
},
get_payload_url(value) {
return "/debug/view/?k=" + value.payloadKey;
},
get_payload_url(value) {
return "/debug/view/?k=" + value.payloadKey;
},
mounted() {
NProgress.configure({
easing: 'ease',
minimum: 0.3,
parent: '#resource_payload'
});
},
mounted() {
NProgress.configure({
easing: 'ease',
minimum: 0.3,
parent: '#resource_payload'
});
axios.interceptors.request.use(config => {
NProgress.start();
return config;
});
axios.interceptors.request.use(config => {
NProgress.start();
return config;
});
axios.interceptors.response.use(response => {
NProgress.done();
return response;
});
axios.interceptors.response.use(response => {
NProgress.done();
return response;
});
axios
.get('{{.PayloadUrl}}')
.then(response => {
if (response.data) {
this.resPayload = response.data.map(function (val) {
return {
payloadTime: val.payloadTime,
origValue: val
};
});
} else {
console.log("No payloads found for period")
}
})
},
methods: {
sort: function (sortBy) {
if (sortBy === this.currentSortBy) {
this.currentSortDirection = (this.currentSortDirection === 'asc') ? 'desc' : 'asc';
axios
.get('{{.PayloadUrl}}')
.then(response => {
if (response.data) {
this.resPayload = response.data.map(function (val) {
return {
payloadTime: val.payloadTime,
origValue: val
};
});
} else {
console.log("No payloads found for period")
}
this.currentSortBy = sortBy;
},
})
},
methods: {
sort: function (sortBy) {
if (sortBy === this.currentSortBy) {
this.currentSortDirection = (this.currentSortDirection === 'asc') ? 'desc' : 'asc';
}
this.currentSortBy = sortBy;
},
computed: {
sortedResPayloads: function () {
return this.resPayload.sort((a, b) => {
let direction = (this.currentSortDirection === 'asc') ? 1 : -1;
if (a[this.currentSortBy] < b[this.currentSortBy]) return -1 * direction;
if (a[this.currentSortBy] > b[this.currentSortBy]) return direction;
return 0;
});
},
getStartTime: function () {
payloadTimeList = this.resPayload.map(function (val) {
formattedTime = new Date(val.payloadTime/1000000).toISOString().split('T').join(' ');
return formattedTime;
});
return payloadTimeList[0];
},
getEndTime: function () {
payloadTimeList = this.resPayload.map(function (val) {
formattedTime = new Date(val.payloadTime/1000000).toISOString().split('T').join(' ');
return formattedTime;
});
return payloadTimeList[payloadTimeList.length-1];
},
}
});
},
computed: {
sortedResPayloads: function () {
return this.resPayload.sort((a, b) => {
let direction = (this.currentSortDirection === 'asc') ? 1 : -1;
if (a[this.currentSortBy] < b[this.currentSortBy]) return -1 * direction;
if (a[this.currentSortBy] > b[this.currentSortBy]) return direction;
return 0;
});
},
getStartTime: function () {
payloadTimeList = this.resPayload.map(function (val) {
formattedTime = new Date(val.payloadTime/1000000).toISOString().split('T').join(' ');
return formattedTime;
});
return payloadTimeList[0];
},
getEndTime: function () {
payloadTimeList = this.resPayload.map(function (val) {
formattedTime = new Date(val.payloadTime/1000000).toISOString().split('T').join(' ');
return formattedTime;
});
return payloadTimeList[payloadTimeList.length-1];
},
}
});
</script>
<div id="resource_events">
<h2>Events</h2>
<table id="resource_event_table">
<tr>
<th>Payload Link</th>
<th @click="sort('message')">Message &#x21C5</th>
<th @click="sort('reason')">Reason &#x21C5</th>
<th @click="sort('source')">Source &#x21C5</th>
<th @click="sort('count')">Count &#x21C5</th>
<th @click="sort('firstSeen')">First seen &#x21C5</th>
<th @click="sort('lastSeen')">Last seen &#x21C5</th>
</tr>
<tr v-for="resEvent in sortedResEvents">
<td><a :href ="resEvent.origValue | get_payload_url" target="_blank">Details</a></td>
<td>${ resEvent.message }</td>
<td>${ resEvent.reason }</td>
<td>${ resEvent.source }</td>
<td>${ resEvent.count }</td>
<td>${ resEvent.firstSeen | get_formatted_date }</td>
<td>${ resEvent.lastSeen | get_formatted_date }</td>
</tr>
</table>
<h2>Events</h2>
<table id="resource_event_table">
<tr>
<th>Payload Link</th>
<th @click="sort('message')">Message &#x21C5</th>
<th @click="sort('reason')">Reason &#x21C5</th>
<th @click="sort('source')">Source &#x21C5</th>
<th @click="sort('count')">Count &#x21C5</th>
<th @click="sort('firstSeen')">First seen &#x21C5</th>
<th @click="sort('lastSeen')">Last seen &#x21C5</th>
</tr>
<tr v-for="resEvent in sortedResEvents">
<td><a :href ="resEvent.origValue | get_payload_url" target="_blank">Details</a></td>
<td>${ resEvent.message }</td>
<td>${ resEvent.reason }</td>
<td>${ resEvent.source }</td>
<td>${ resEvent.count }</td>
<td>${ resEvent.firstSeen | get_formatted_date }</td>
<td>${ resEvent.lastSeen | get_formatted_date }</td>
</tr>
</table>
</div>
<script>
new Vue({
el: '#resource_events',
delimiters: ['${', '}'],
data: {
resEvents: [],
currentSortBy: 'firstSeen',
currentSortDirection: 'asc'
new Vue({
el: '#resource_events',
delimiters: ['${', '}'],
data: {
resEvents: [],
currentSortBy: 'firstSeen',
currentSortDirection: 'asc'
},
filters: {
get_formatted_date(value) {
return value.split('T').join(' ');
},
filters: {
get_formatted_date(value) {
return value.split('T').join(' ');
},
get_payload_url(value) {
return "/debug/view/?k=" + value.eventKey;
}
},
mounted() {
NProgress.configure({
easing: 'ease',
minimum: 0.3,
parent: '#resource_events'
});
axios.interceptors.request.use(config => {
NProgress.start();
return config;
});
axios.interceptors.response.use(response => {
NProgress.done();
return response;
});
axios
.get('{{.EventsUrl}}')
.then(response => {
if (response.data) {
this.resEvents = response.data.map(function (val) {
let parsedVal = JSON.parse(val.payload);
return {
message: parsedVal.message,
source: parsedVal.source.host,
reason: parsedVal.reason,
count: parsedVal.count,
firstSeen: parsedVal.firstTimestamp,
lastSeen: parsedVal.lastTimestamp,
origValue: val
};
});
} else {
console.log("No events found for period")
}
})
},
methods: {
sort: function (sortBy) {
if (sortBy === this.currentSortBy) {
this.currentSortDirection = (this.currentSortDirection === 'asc') ? 'desc' : 'asc';
}
this.currentSortBy = sortBy;
}
},
computed: {
sortedResEvents: function () {
return this.resEvents.sort((a, b) => {
let direction = (this.currentSortDirection === 'asc') ? 1 : -1;
if (a[this.currentSortBy] < b[this.currentSortBy]) return -1 * direction;
if (a[this.currentSortBy] > b[this.currentSortBy]) return direction;
return 0;
});
}
get_payload_url(value) {
return "/debug/view/?k=" + value.eventKey;
}
});
},
mounted() {
NProgress.configure({
easing: 'ease',
minimum: 0.3,
parent: '#resource_events'
});
axios.interceptors.request.use(config => {
NProgress.start();
return config;
});
axios.interceptors.response.use(response => {
NProgress.done();
return response;
});
axios
.get('{{.EventsUrl}}')
.then(response => {
if (response.data) {
this.resEvents = response.data.map(function (val) {
let parsedVal = JSON.parse(val.payload);
return {
message: parsedVal.message,
source: parsedVal.source.host,
reason: parsedVal.reason,
count: parsedVal.count,
firstSeen: parsedVal.firstTimestamp,
lastSeen: parsedVal.lastTimestamp,
origValue: val
};
});
} else {
console.log("No events found for period")
}
})
},
methods: {
sort: function (sortBy) {
if (sortBy === this.currentSortBy) {
this.currentSortDirection = (this.currentSortDirection === 'asc') ? 'desc' : 'asc';
}
this.currentSortBy = sortBy;
}
},
computed: {
sortedResEvents: function () {
return this.resEvents.sort((a, b) => {
let direction = (this.currentSortDirection === 'asc') ? 1 : -1;
if (a[this.currentSortBy] < b[this.currentSortBy]) return -1 * direction;
if (a[this.currentSortBy] > b[this.currentSortBy]) return direction;
return 0;
});
}
}
});
</script>
</body>
</html>

Просмотреть файл

@ -6,6 +6,7 @@
*/
const displayMaxX = document.documentElement.clientWidth;
const displayMaxY = document.documentElement.clientHeight;
let topAxis, bottomAxis, xAxisScale, yAxisBand, data, theTime, axisTop, axisBottom, smallBarOffset;
@ -167,7 +168,6 @@ function processAndSortResources(result) {
}).sort(cmpFn);
return data
}
}
function compareKind(a, b) {
@ -196,15 +196,12 @@ function appendAxes(svg) {
}
function renderTooltip() {
tooltip = d3.select(
document.createElement("div"))
tooltip = d3.select("#tooltip_container")
.append("div")
.call(createTooltip);
document.querySelector("body").appendChild(tooltip.node());
}
function bindMouseEvents(svg) {
svg.on("mousemove", function () {
let [x, y] = d3.mouse(this);
@ -216,9 +213,9 @@ function bindMouseEvents(svg) {
line.attr("transform", `translate(${x} 0)`);
theTime = xAxisScale.invert(x);
if (!detailedToolTipIsVisible) {
tooltip
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY + 20 + "px")
let tooltipX = d3.event.pageX;
let tooltipY = d3.event.pageY;
positionTooltip(tooltipX, tooltipY);
}
}
});
@ -246,34 +243,7 @@ function bindMouseEvents(svg) {
))
}
}).on("click", function (d) {
if (detailedToolTipIsVisible) {
resourceBarHtml = getResourceBarContent(
{
title: d.text,
kind: d.kind,
namespace: d.namespace,
time: theTime
}
);
tooltip.html(resourceBarHtml);
detailedToolTipIsVisible = false
} else {
let [x, y] = d3.mouse(this);
$.ajax({
url: "/resource",
data: {
click_time: xAxisScale.invert(x).getTime(),
name: d.text,
namespace: d.namespace,
kind: d.kind,
},
success: function (result) {
detailedToolTipIsVisible = true;
tooltip.html(result);
evalJSFromHtml(result);
}
});
}
showDetailedTooltip(d, d3.event, this);
});
// Intuitively 'd' should be the 'heatmap' element - but for whatever reason
// the event binds correctly but 'd' is the resource element. Not sure why - I think
@ -312,6 +282,8 @@ function bindMouseEvents(svg) {
d3.select(this).attr("fill", severityColorGenFunc(thisOverlay.severity));
tooltip.style("opacity", 0)
}
}).on("click", function (d) {
showDetailedTooltip(d, d3.event, this);
});
}
@ -351,7 +323,6 @@ function createResourceBar(d) {
let w = Math.max(xAxisScale(d.end) - xAxisScale(d.start), 10);
const isLabelRight = (sx > displayMaxX / 2 ? sx + w < displayMaxX : sx - w > 0);
el
.append("rect")
.attr("x", sx)
@ -385,6 +356,7 @@ function createResourceBar(d) {
.attr("fill", d3.color(severityColorGenFunc(overlay.severity)))
.attr("title", text)
.attr("transform", `translate(0 ${-smallBarOffset})`)
.style("cursor", "pointer")
.classed("heatmap", true)
.attr("index", n++)
}
@ -420,7 +392,8 @@ function createResourceBar(d) {
.attr("x", isLabelRight ? sx - 5 : sx + w + 5)
.attr("fill", "black")
.style("text-anchor", isLabelRight ? "end" : "start")
.style("dominant-baseline", "hanging");
.style("dominant-baseline", "hanging")
.style("font-size", "14");
}
function evalJSFromHtml(html) {
@ -432,6 +405,8 @@ function evalJSFromHtml(html) {
}
}
// I think the detailed tooltip should probably be moved to
// a modal dialog - it's getting to be too large and unwieldy
function createTooltip(el) {
el
.style("position", "absolute")
@ -443,5 +418,75 @@ function createTooltip(el) {
.style("padding", "10px")
.style("line-height", "1.3")
.style("z-index", 1)
.style("font", "11px sans-serif")
.style("font", "12px sans-serif")
.style("max-height", "50%")
.style("max-width", "50%")
.style("overflow-y", "scroll")
}
function positionTooltip(x, y) {
let tooltipX = x;
let tooltipY = y;
if (x > displayMaxX / 2) {
tooltip.style("right", (displayMaxX - tooltipX) + "px");
tooltip.style("left", "")
} else {
tooltip.style("left", tooltipX + "px");
tooltip.style("right", "")
}
if (y > displayMaxY / 2) {
tooltip.style("bottom", (displayMaxY - tooltipY) + "px");
tooltip.style("top", "")
} else {
// It looks really goofy if you don't. 20px is about the size of the mouse on a 1080 scaled display
tooltip.style("top", tooltipY + "px");
tooltip.style("bottom", "")
}
if (detailedToolTipIsVisible) {
tooltip.style("pointer-events", "auto")
} else {
tooltip.style("pointer-events", "none")
}
}
function showDetailedTooltip(d, event, parent) {
let tooltipX = event.pageX;
let tooltipY = event.pageY;
if (detailedToolTipIsVisible) {
let resourceBarHtml = getResourceBarContent(
{
title: d.text,
kind: d.kind,
namespace: d.namespace,
time: theTime
}
);
tooltip.html(resourceBarHtml);
positionTooltip(tooltipX, tooltipY);
detailedToolTipIsVisible = false
} else {
let [x, y] = d3.mouse(parent);
let tooltipX = event.pageX;
let tooltipY = event.pageY;
$.ajax({
url: "/resource",
data: {
click_time: xAxisScale.invert(x).getTime(),
name: d.text,
namespace: d.namespace,
kind: d.kind,
},
success: function (result) {
detailedToolTipIsVisible = true;
tooltip.html(result);
evalJSFromHtml(result);
positionTooltip(tooltipX, tooltipY)
}
});
}
}