Files
gitrob/static/javascripts/application.js
Michael Henriksen d2c43395e5 Genesis.
2018-06-09 13:20:19 +02:00

407 lines
13 KiB
JavaScript

var Stats = Backbone.Model.extend({
url: "/stats",
defaults: {
"Status": "initializing",
"StartedAt": null,
"FinishedAt": null,
"Progress": 0,
"Targets": 0,
"Repositories": 0,
"Commits": 0,
"Files": 0,
"Findings": 0,
},
isFinished: function() {
return this.get("Status") === "finished";
},
duration: function() {
if (this.get("StartedAt") === null) {
return "00:00:00";
}
var end;
var start = Date.parse(this.get("StartedAt"));
if (this.isFinished()) {
end = Date.parse(this.get("FinishedAt"));
} else {
end = Date.now();
}
var millis = end - start;
var seconds = Math.floor(millis / 1000);
var nullDate = new Date(null);
nullDate.setSeconds(seconds);
return nullDate.toISOString().substr(11, 8);
},
});
window.stats = new Stats;
var Finding = Backbone.Model.extend({
idAttribute: "Id",
testFileIndicators: ["test", "_spec", "fixture", "mock", "stub", "fake", "demo", "sample"],
shortCommitHash: function() {
return this.get("CommitHash").substr(0, 7);
},
trimmedCommitMessage: function() {
var message = this.get("CommitMessage").split("-----END PGP SIGNATURE-----", 2).pop();
return message.replace(/^\s\s*/, "").replace(/\s\s*$/, "")
},
isTestRelated: function() {
var path = this.get("FilePath").toLowerCase();
for (var i = 0; i < this.testFileIndicators.length; i++) {
if (path.indexOf(this.testFileIndicators[i]) > -1) {
return true;
}
}
return false;
},
fileContentsUrl: function() {
return ["/files", this.get("RepositoryOwner"), this.get("RepositoryName"), this.get("CommitHash"), this.get("FilePath")].join("/");
},
fileContents: function(callback, error) {
$.ajax({
url: this.fileContentsUrl(),
success: callback,
error: error
});
},
});
var Findings = Backbone.Collection.extend({
url: "/findings",
model: Finding,
});
window.findings = new Findings();
var StatsView = Backbone.View.extend({
id: "stats_container",
model: stats,
pollingTicker: null,
durationTicker: null,
pollingInterval: 500,
initialize: function() {
this.listenTo(this.model, "change", this.render)
this.startDurationTicker();
this.startPolling();
},
render: function() {
if (this.model.isFinished()) {
this.stopPolling();
this.stopDurationTicker();
}
if (this.model.hasChanged("Progress")) {
this.updateProgress();
}
if (this.model.hasChanged("Findings")) {
this.updateFindings();
}
if (this.model.hasChanged("Files")) {
this.updateFiles();
}
if (this.model.hasChanged("Commits")) {
this.updateCommits();
}
if (this.model.hasChanged("Repositories")) {
this.updateRepositories();
}
if (this.model.hasChanged("Targets")) {
this.updateTargets();
}
},
startPolling: function() {
this.pollingTicker = setInterval(function() {
statsView.model.fetch();
}, this.pollingInterval);
},
stopPolling: function() {
if (this.pollingTicker !== null) {
clearInterval(this.pollingTicker);
}
},
startDurationTicker: function() {
this.DurationTicker = setInterval(function() {
statsView.updateDuration()
}, 1000);
},
stopDurationTicker: function() {
this.updateDuration();
if (this.durationTicker !== null) {
clearInterval(this.durationTicker);
}
},
updateDuration: function() {
$("#card_duration_value").text(this.model.duration());
},
updateProgress: function() {
var status = this.statusToHuman();
$("title").text("Gitrob: " + status);
$("#progress_bar").text(status).css("width", this.model.get("Progress") + "%");
if (this.model.isFinished()) {
$("#progress_bar").removeClass("progress-bar-animated progress-bar-striped").css("width", "100%");
}
},
updateFindings: function() {
$("#card_findings_value").hide().text(this.model.get("Findings").toLocaleString()).fadeIn("fast");
},
updateFiles: function() {
$("#card_files_value").hide().text(this.model.get("Files").toLocaleString()).fadeIn("fast");
},
updateCommits: function() {
$("#card_commits_value").hide().text(this.model.get("Commits").toLocaleString()).fadeIn("fast");
},
updateRepositories: function() {
$("#card_repositories_value").hide().text(this.model.get("Repositories").toLocaleString()).fadeIn("fast");
},
updateTargets: function() {
$("#card_targets_value").hide().text(this.model.get("Targets").toLocaleString()).fadeIn("fast");
},
statusToHuman: function() {
var status;
switch(this.model.get("Status")) {
case "initializing":
status = "Initializing";
break;
case "gathering":
status = "Gathering repositories";
break;
case "analyzing":
status = "Analyzing repositories"
break;
case "finished":
status = "Finished";
break;
default:
status = "Unknown";
break;
}
return status + " (" + parseInt(this.model.get("Progress")) + "%)";
}
});
window.statsView = new StatsView({el: $("#stats_container")});
var FindingView = Backbone.View.extend({
tagName: "tr",
events: {
"click td.col-path a": "showFinding",
},
template: _.template($("#template_finding").html()),
render: function() {
this.$el.html(this.template(this.model.attributes)).data("finding", this.model);
if (this.model.isTestRelated()) {
this.$el.addClass("test-related");
}
return this;
},
formattedFilePath: function() {
var splits = this.model.get("FilePath").split("/");
var filename = splits.pop();
var directory = this.ellipsisize(splits.join("/"), 60, 25);
if (directory === "") {
return "<strong>" + _.escape(filename) + "</strong>";
}
return _.escape(directory) + "/" + "<strong>" + _.escape(filename) + "</strong>";
},
ellipsisize: function(str, minLength, edgeLength) {
str = String(str);
if (str.length < minLength || str.length <= (edgeLength * 2)) {
return str;
}
var edge = Array(edgeLength + 1).join(".");
var midLength = str.length - edgeLength * 2;
var pattern = "(" + edge + ").{" + midLength + "}(" + edge + ")";
return str.replace(new RegExp(pattern), "$1…$2");
},
showFinding: function(e) {
e.preventDefault();
this.markAsSelected();
var modalView = new FindingModal({
model: this.model,
el: "#finding_modal .modal-content"
});
modalView.render();
$("#finding_modal").modal();
modalView.fetchFileContents();
},
markAsSelected: function() {
this.$el.closest("tbody").find("tr.table-selected").removeClass("table-selected");
this.$el.addClass("table-selected");
},
});
var FindingsView = Backbone.View.extend({
collection: findings,
initialize: function() {
this.listenTo(this.collection, "add", this.renderFinding);
this.listenTo(stats, "change:Findings", _.debounce(this.update, 500));
$("#findings_search").on("keyup", _.debounce(this.searchFindings, 200));
$("#finding_modal").on("show.bs.modal", function(event) {
$(document).on("keydown", function(e) {
switch(e.keyCode) {
case 37:
var finding = findingsView.previousFinding();
break;
case 39:
var finding = findingsView.nextFinding();
break;
default:
return;
}
if (finding.length === 0) {
return;
}
findingsView.activeFinding().removeClass("table-selected");
finding.addClass("table-selected");
var modalView = new FindingModal({
model: finding.data("finding"),
el: "#finding_modal .modal-content"
});
modalView.render();
$("#finding_modal").modal();
modalView.fetchFileContents();
});
})
.on("hidden.bs.modal", function(event) {
$(document).unbind("keydown");
});
},
update: function() {
this.collection.fetch();
},
renderFinding: function(finding) {
var findingEl = new FindingView({model: finding}).render().el;
$(findingEl).appendTo(this.$el);
},
activeFinding: function() {
return this.$el.find("tr.table-selected");
},
nextFinding: function() {
return this.activeFinding().nextAll("tr").not(".d-none").first();
},
previousFinding: function() {
return this.activeFinding().prevAll("tr").not(".d-none").first();
},
searchFindings: function() {
var needle = $.trim($("#findings_search").val()).toLowerCase();
if (needle == "") {
$("#table_findings tbody tr").removeClass("d-none");
return;
}
$("#table_findings tbody tr").each(function() {
var path = $(this).find("td.col-path").text().toLowerCase();
var commit = $(this).find("td.col-commit").text().toLowerCase();
var repository = $(this).find("td.col-repository").text().toLowerCase();
if (path.indexOf(needle) > -1 || commit.indexOf(needle) > -1 || repository.indexOf(needle) > -1) {
$(this).removeClass("d-none");
} else {
$(this).addClass("d-none");
}
});
}
});
window.findingsView = new FindingsView({el: "#table_findings tbody"});
var FindingModal = Backbone.View.extend({
template: _.template($("#template_finding_modal").html()),
interestingStringPatterns: [
/((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))/gmi,
/([a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)/gmi,
/((\w*:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6}))/gmi,
/([a-f0-9\-\$\/]{20,})/gmi,
/(username)/gmi,
/(secret)/gmi,
/(passw(or)?d)/gmi,
/(cred(s|ential))/gmi,
/(access(_|-|.)?token)/gmi,
],
events: {
"click #finding_view_raw": "showRawContents",
"click #finding_view_hexdump": "showHexDumpContents",
},
render: function() {
this.$el.html(this.template(this.model.attributes));
new ClipboardJS('.btn', {
container: document.getElementById('finding_modal')
});
return this;
},
showRawContents: function() {
$("#finding_view_raw").addClass("active");
$("#finding_view_hexdump").removeClass("active");
$("#modal_file_hexdump").hide();
$("#modal_file_contents").show();
},
showHexDumpContents: function() {
$("#finding_view_raw").removeClass("active");
$("#finding_view_hexdump").addClass("active");
$("#modal_file_contents").hide();
$("#modal_file_hexdump").show();
},
truncatedCommitMessage: function() {
var message = this.model.trimmedCommitMessage();
if (message.length <= 150) {
return _.escape(message);
}
return _.escape(message.substr(0, 150)) + "…";
},
isTestRelated: function() {
return this.model.isTestRelated();
},
isBinary: function(data) {
return /[\x00-\x08\x0E-\x1F]/.test(data);
},
highlightInterestingStrings: function(haystack) {
this.interestingStringPatterns.forEach(function(pattern) {
haystack = haystack.replace(pattern, "<mark>$1</mark>");
});
return haystack;
},
fetchFileContents: function() {
if (this.model.get("Action") == "Delete") {
$("#modal_file_spinner_container").fadeOut("fast", function() {
$("#modal_file_contents_container").html("<div class='alert alert-info' role='alert'>View commit on GitHub to see contents of deleted files.</div>").fadeIn("fast");
});
return;
}
var context = this;
this.model.fileContents(function(data) {
var worker = new Worker("/javascripts/highlight_worker.js");
worker.onmessage = function(event) {
$("#modal_file_spinner_container").fadeOut("fast", _.bind(function() {
var content = this.highlightInterestingStrings(event.data);
$("#modal_file_contents").html(content);
new Hexdump(data, {
container: "modal_file_hexdump",
base: "hex",
width: 8,
byteGrouping: 1,
html: true,
ascii: true,
lineNumbers: true,
style: {
lineNumberLeft: '',
lineNumberRight: ':',
stringLeft: '|',
stringRight: '|',
hexLeft: '',
hexRight: '',
hexNull: '.',
nonPrintable: '.',
stringNull: '.',
}
});
$("#modal_file_contents_container").fadeIn("fast");
if (this.isBinary(data)) {
this.showHexDumpContents();
} else {
this.showRawContents();
}
}, context));
}
worker.postMessage(data);
}, function() {
$("#modal_file_spinner_container").fadeOut("fast", function() {
$("#modal_file_contents_container").html("<div class='alert alert-warning' role='alert'>File size too large to display inline. View file on GitHub.</div>").fadeIn("fast");
});
});
}
});