| <!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> |