blob: 0e4286c02aa77eb8d4e4342d5bc2bb65fab1d49a [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html ng-app="main">
<head>
<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
<script src="/chromium-common/web/angular-1.2.0/angular.min.js"></script>
<script src="/chromium-common/web/jquery-2.1.1/jquery-2.1.1.min.js"></script>
<script src="/chromium-common/web/ng-grid/ng-grid.min.js"></script>
<link rel="stylesheet" href="/chromium-common/web/ng-grid/ng-grid.min.css" />
<link rel="stylesheet" href="/chromium-common/web/bootstrap-3.2.0/bootstrap.min.css" />
<style>
body, html {
height: 95%;
width: 95%;
margin: 0;
margin-left: auto;
margin-right: auto;
}
.wrapper {
display: table;
width: 100%;
height: 100%;
}
.loader-spinner {
width: 30px;
height: 30px;
margin-left: auto;
margin-right: auto;
}
.header {
height: 70px;
display: table-row;
background: OldLace;
}
.gridStyle {
display: table-row;
height: 100% !important;
}
</style>
<script language="javascript">
// Split a string in 2 parts. The first is the leading number, if any,
// the second is the string following the numbers.
function splitNum(s) {
var results = new Array();
results[0] = 'None';
for (var i = 0; i < s.length; i++) {
var substr = s.substr(0, i+1)
if (isNaN(substr)) {
// Not a number anymore.
results[1] = s.substr(i)
break;
} else {
// This is a number. update the results.
results[0] = parseFloat(substr);
}
}
return results;
}
// Compare 2 strings using a custom alphanumerical algorithm.
// This is similar to a normal string sort, except that we sort
// first by leading digits, if any.
// For example:
// 100hello > 2goodbye
// Numbers anywhere else in the string are compared using the normal
// sort algorithm.
function alphanumCompare(a, b) {
var parsedA = splitNum(a);
var parsedB = splitNum(b);
var numA = parsedA[0];
var numB = parsedB[0];
var strA = parsedA[1];
var strB = parsedB[1];
if (! (isNaN(numA) || isNaN(numB))) {
// They both start with numbers.
cmp = (numA - numB);
if (cmp !== 0) {
return cmp;
}
// Identical. Fallback to string.
return (strA < strB) ? -1 : (strA > strB ? 1 : 0)
}
// If only one starts with a number, we start with that one as
// the lowest.
if (isNaN(numA) == false) return -1
if (isNaN(numB) == false) return 1
// They are both strings.
return (a < b) ? -1 : (a > b ? 1 : 0)
}
var TYPE_ORDER = ["parent", "prefix", "storageObject"];
function entryCompare(a, b) {
var aType = TYPE_ORDER.indexOf(a.type);
var bType = TYPE_ORDER.indexOf(b.type);
if (aType != bType) {
return aType - bType;
}
return alphanumCompare(a.name, b.name);
}
function sizeCompare(a, b) {
return ((a || 0) - (b || 0));
}
// Helper function to retrieve the value of a GET query parameter.
// Greatly inspired from http://alturl.com/8rj7a
function getParameter(parameterName) {
// Add '=' to the parameter name (i.e. parameterName=value)
var parameterName = parameterName + '=';
var queryString = window.location.search.substring(1);
if (queryString.length <= 0) {
return '';
}
// Find the beginning of the string
begin = queryString.indexOf(parameterName);
// If the parameter name is not found, skip it, otherwise return the
// value.
if (begin == -1) {
return '';
}
// Add the length (integer) to the beginning.
begin += parameterName.length;
// Multiple parameters are separated by the '&' sign.
end = queryString.indexOf ('&', begin);
if (end == -1) {
end = queryString.length;
}
// Return the string.
return escape(unescape(queryString.substring(begin, end)));
}
String.prototype.endsWith = function(suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
var CR_COMMIT_POSITION_NUMBER_KEY = 'Cr-Commit-Position-Number';
var CR_GIT_COMMIT_KEY = 'Cr-Git-Commit';
function getDisplayName($scope, objectName) {
if (objectName.indexOf($scope.prefix) == 0) {
objectName = objectName.substring($scope.prefix.length);
}
if (objectName.endsWith("/")) {
objectName = objectName.substring(0, (objectName.length - 1));
}
return objectName;
}
function getMetadata(storageObject, key) {
// 'gsutil' uploads keys as lowercase
return (storageObject.metadata || {})[key.toLowerCase()]
}
function getCommitPositionNumber(storageObject) {
return getMetadata(storageObject, CR_COMMIT_POSITION_NUMBER_KEY);
}
function getGitCommit(storageObject) {
return getMetadata(storageObject, CR_GIT_COMMIT_KEY);
}
function getCommitPositionNumberURL(commitPositionNumber) {
return 'https://crrev.com/' + escape(commitPositionNumber);
}
function getGitCommitURL(gitCommit) {
return 'https://crrev.com/' + escape(gitCommit);
}
function formatSizeUnits(bytes){
if (bytes>=1000000000) {bytes=(bytes/1000000000).toFixed(2)+' GB';}
else if (bytes>=1000000) {bytes=(bytes/1000000).toFixed(2)+' MB';}
else if (bytes>=1000) {bytes=(bytes/1000).toFixed(2)+' KB';}
else if (bytes>1) {bytes=bytes+' bytes';}
else if (bytes==1) {bytes=bytes+' byte';}
else {bytes='0 byte';}
return bytes;
}
function createEntry(type, name, icon) {
var entry = {
type: type,
name: name,
icon: icon,
};
entry.this = entry;
return entry;
}
function getParentRowEntry($scope) {
var backpath = location.pathname;
// If there is more than one section delimited by '/' in the current
// prefix we truncate the last section and append the rest to
// backpath.
var delimiter = $scope.prefix.lastIndexOf('/');
if (delimiter >= 0) {
delimiter = $scope.prefix.substr(0, delimiter).lastIndexOf('/');
if (delimiter >= 0) {
backpath += '?prefix=';
backpath += escape($scope.prefix.substr(0, delimiter+1));
}
}
var entry = createEntry("parent", "Parent Directory", "back.gif");
entry.url = backpath;
return entry;
}
function getPrefixEntries($scope, prefixes) {
var entries = Array();
prefixes.forEach(function(prefix) {
var link = location.pathname.substr(0,
location.pathname.indexOf('?')) + '?prefix=' + prefix;
var entry = createEntry(
"prefix",
getDisplayName($scope, prefix),
"folder.gif"
);
entry.prefix = prefix;
entry.url = link;
entries.push(entry);
});
return entries;
}
function getStorageObjectEntries($scope, items) {
// Set up the variables.
var entries = Array();
items.forEach(function(storageObject) {
// If this object _is_ the current prefix, skip it.
if(
(storageObject.kind !== 'storage#object') ||
(storageObject.name === $scope.prefix)) {
return;
}
var link = storageObject.mediaLink;
var filename = getDisplayName($scope, storageObject.name);
var lastModified = storageObject.updated.replace('T', ' ');
lastModified = lastModified.substr(0, lastModified.indexOf('.'));
if (filename == '') {
return;
}
var entry = createEntry(
"storageObject",
filename,
"binary.gif"
);
entry.so = storageObject;
entry.url = link;
entry.lastModified = lastModified;
entry.size = storageObject.size;
entry.commit = getGitCommit(storageObject);
entry.commitPositionNumber = getCommitPositionNumber(storageObject);
entries.push(entry);
});
return entries;
}
function fetchAndDisplayObjects($scope, $http) {
var gsAPIBase = 'https://www.googleapis.com/storage/v1';
var fieldsParam = 'items(kind,mediaLink,metadata,name,size,updated),' +
'kind,prefixes,nextPageToken';
var gsAPIListObjects = gsAPIBase + '/b/' + $scope.bucket + '/o';
var url = gsAPIListObjects + '?delimiter=/&prefix=' + $scope.prefix +
'&fields=' + fieldsParam;
// Initial page load
$scope.loading = true;
addNextPage('')
function addNextPage(nextPageToken) {
var reqURL = url
if (nextPageToken != '') {
reqURL += '&pageToken=' + nextPageToken;
}
var req = $http.get(reqURL).
success(function(data, status, headers, config) {
if(data.kind != 'storage#objects') {
console.log("JSON API did not return 'storage#objects' object");
return;
}
var entries = Array();
// Augment prefixes
if (data.prefixes !== undefined) {
entries.push.apply(
entries,
getPrefixEntries($scope, data.prefixes)
);
}
// Augment items
if (data.items !== undefined) {
entries.push.apply(
entries,
getStorageObjectEntries($scope, data.items)
);
}
// Add all generated entries to our table data.
$scope.data.push.apply($scope.data, entries);
// Load next page
if(data.nextPageToken !== undefined) {
return addNextPage(data.nextPageToken);
}
// Finished loading.
$scope.loading = false;
}).
error(function(data, status, headers, config) {
console.log("HTTP request failed with [" + status + "]");
});
return req;
}
}
var app = angular.module('main', ['ngGrid'])
app.controller('ChromeGSIndex', function($scope, $http, $sce) {
$scope.data = new Array();
var lastSlash = location.pathname.lastIndexOf("/");
$scope.root = location.pathname.substring(0, lastSlash);
$scope.prefix = getParameter('prefix');
$scope.iconPath = "/chromium-common/images";
$scope.loading = false;
var bucket = $scope.root;
if(bucket[0] == "/") {
bucket = bucket.substring(1);
}
$scope.bucket = bucket;
// Add a parent directory entry, if applicable
if ($scope.prefix != "") {
$scope.data.push(getParentRowEntry($scope));
}
$scope.filterOptions = {
filterText: '',
};
$scope.commitPositionNumberHtml = function(entry) {
var html;
if (entry.type != "storageObject") {
html = "";
} else if (entry.commitPositionNumber) {
var link = getCommitPositionNumberURL(entry.commitPositionNumber);
html = "<a href='" + link + "'>" +
entry.commitPositionNumber +
"</a>";
} else {
html = "-";
}
return $sce.trustAsHtml(html);
};
$scope.commitHtml = function(entry) {
var html;
if (entry.type != "storageObject") {
html = "";
} else if (entry.commit) {
var link = getGitCommitURL(entry.commit);
html = "<a href='" + link + "'>" +
entry.commit +
"</a>";
} else {
html = "-";
}
return $sce.trustAsHtml(html);
};
$scope.getSizeHtml = function(value) {
var html;
// This checks for value in 'undefined' and 'null'.
if (value == null) {
html = "";
} else {
html = formatSizeUnits(value);
}
return $sce.trustAsHtml(html);
};
$scope.gridOptions = {
data: 'data',
enableRowSelection: false,
enableColumnResize: true,
filterOptions: $scope.filterOptions,
columnDefs: [
{
displayName: "",
field: "icon",
width: "20px",
cellTemplate: "<img src='{{iconPath}}/{{row.entity.icon}}' />",
},
{
displayName: "Name",
field: "this",
width: "***",
sortFn: entryCompare,
cellTemplate:
"<div>" +
"<a href='{{row.entity.url}}'>{{row.entity.name}}</a>" +
"</div>",
},
{
displayName: "Last Modified",
field: "lastModified",
},
{
displayName: "Size",
field: "size",
sortFn: sizeCompare,
cellTemplate:
"<div ng-bind-html='getSizeHtml(row.entity.size)'></div>",
},
{
displayName: "Commit Position",
field: "commitPositionNumber",
cellTemplate:
"<div ng-bind-html='commitPositionNumberHtml(row.entity)'></div>",
},
{
displayName: "Commit",
field: "commit",
width: "**",
cellTemplate:
"<div ng-bind-html='commitHtml(row.entity)'></div>",
},
],
};
fetchAndDisplayObjects($scope, $http);
});
</script>
</head>
<body ng-controller="ChromeGSIndex">
<script type="text/ng-template" id="header">
<h1>
Index of <em>{{bucket}}</em>/{{prefix}}</span>
<img class="loader-spinner" ng-show="loading"
src="/chromium-common/images/spinner.gif"></img>
</h1>
</script>
<div class="wrapper">
<div class="header" ng-include="'header'"></div>
<strong>Filter:</strong><input type="text" ng-model="filterOptions.filterText" />
<div class="gridStyle" ng-grid="gridOptions"></div>
</div>
</body>
</html>