mirror of
https://github.com/michenriksen/gitrob.git
synced 2025-12-31 02:39:37 -06:00
407 lines
13 KiB
JavaScript
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");
|
|
});
|
|
});
|
|
}
|
|
});
|