initial import for Open Source 🎉
This commit is contained in:
parent
1898c361f3
commit
9c0dd3b722
2048 changed files with 218743 additions and 0 deletions
281
static/js/directives/repo-view/repo-panel-builds.js
Normal file
281
static/js/directives/repo-view/repo-panel-builds.js
Normal file
|
@ -0,0 +1,281 @@
|
|||
/**
|
||||
* An element which displays the builds panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelBuilds', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-builds.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'builds': '=builds',
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function($scope, $element, $filter, $routeParams, ApiService, TriggerService, UserService, StateService) {
|
||||
StateService.updateStateIn($scope, function(state) {
|
||||
$scope.inReadOnlyMode = state.inReadOnlyMode;
|
||||
});
|
||||
|
||||
var orderBy = $filter('orderBy');
|
||||
|
||||
$scope.TriggerService = TriggerService;
|
||||
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
$scope.options = {
|
||||
'filter': 'recent',
|
||||
'reverse': false,
|
||||
'predicate': 'started_datetime'
|
||||
};
|
||||
|
||||
$scope.currentFilter = null;
|
||||
|
||||
$scope.currentStartTrigger = null;
|
||||
|
||||
$scope.showBuildDialogCounter = 0;
|
||||
$scope.showTriggerStartDialogCounter = 0;
|
||||
|
||||
$scope.triggerCredentialsModalTrigger = null;
|
||||
$scope.triggerCredentialsModalCounter = 0;
|
||||
|
||||
$scope.feedback = null;
|
||||
|
||||
var updateBuilds = function() {
|
||||
if (!$scope.allBuilds) { return; }
|
||||
|
||||
var unordered = $scope.allBuilds.map(function(build_info) {
|
||||
var commit_sha = null;
|
||||
|
||||
if (build_info.trigger_metadata) {
|
||||
commit_sha = TriggerService.getCommitSHA(build_info.trigger_metadata);
|
||||
}
|
||||
|
||||
return $.extend(build_info, {
|
||||
'started_datetime': (new Date(build_info.started)).valueOf() * (-1),
|
||||
'building_tags': build_info.tags || [],
|
||||
'commit_sha': commit_sha
|
||||
});
|
||||
});
|
||||
|
||||
$scope.fullBuilds = orderBy(unordered, $scope.options.predicate, $scope.options.reverse);
|
||||
};
|
||||
|
||||
var loadBuilds = function(opt_forcerefresh) {
|
||||
if (!$scope.builds || !$scope.repository || !$scope.options.filter || !$scope.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: We only refresh if the filter has changed.
|
||||
var filter = $scope.options.filter;
|
||||
if ($scope.buildsResource && filter == $scope.currentFilter && !opt_forcerefresh) {
|
||||
return;
|
||||
}
|
||||
|
||||
var since = null;
|
||||
var limit = 10;
|
||||
|
||||
if ($scope.options.filter == '48hour') {
|
||||
since = Math.floor(moment().subtract(2, 'days').valueOf() / 1000);
|
||||
limit = 100;
|
||||
} else if ($scope.options.filter == '30day') {
|
||||
since = Math.floor(moment().subtract(30, 'days').valueOf() / 1000);
|
||||
limit = 100;
|
||||
} else {
|
||||
since = null;
|
||||
limit = 10;
|
||||
}
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'limit': limit,
|
||||
'since': since
|
||||
};
|
||||
|
||||
$scope.buildsResource = ApiService.getRepoBuildsAsResource(params).get(function(resp) {
|
||||
$scope.allBuilds = resp.builds;
|
||||
$scope.currentFilter = filter;
|
||||
updateBuilds();
|
||||
});
|
||||
};
|
||||
|
||||
var buildsChanged = function() {
|
||||
if (!$scope.allBuilds) {
|
||||
loadBuilds();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.builds || !$scope.repository || !$scope.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace any build records with updated records from the server.
|
||||
var requireReload = false;
|
||||
$scope.builds.map(function(build) {
|
||||
var found = false;
|
||||
for (var i = 0; i < $scope.allBuilds.length; ++i) {
|
||||
var current = $scope.allBuilds[i];
|
||||
if (current.id == build.id && current.phase != build.phase) {
|
||||
$scope.allBuilds[i] = build;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the build was not found, then a new build has started. Reload
|
||||
// the builds list.
|
||||
if (!found) {
|
||||
requireReload = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (requireReload) {
|
||||
loadBuilds(/* force refresh */true);
|
||||
} else {
|
||||
updateBuilds();
|
||||
}
|
||||
};
|
||||
|
||||
var loadBuildTriggers = function() {
|
||||
if (!$scope.repository || !$scope.repository.can_admin || !$scope.isEnabled) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
$scope.triggersResource = ApiService.listBuildTriggersAsResource(params).get(function(resp) {
|
||||
$scope.triggers = resp.triggers;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('repository', loadBuildTriggers);
|
||||
$scope.$watch('repository', loadBuilds);
|
||||
|
||||
$scope.$watch('isEnabled', loadBuildTriggers);
|
||||
$scope.$watch('isEnabled', loadBuilds);
|
||||
|
||||
$scope.$watch('builds', buildsChanged);
|
||||
|
||||
$scope.$watch('options.filter', loadBuilds);
|
||||
$scope.$watch('options.predicate', updateBuilds);
|
||||
$scope.$watch('options.reverse', updateBuilds);
|
||||
|
||||
$scope.tablePredicateClass = function(name, predicate, reverse) {
|
||||
if (name != predicate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'current ' + (reverse ? 'reversed' : '');
|
||||
};
|
||||
|
||||
$scope.orderBy = function(predicate) {
|
||||
if (predicate == $scope.options.predicate) {
|
||||
$scope.options.reverse = !$scope.options.reverse;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.options.reverse = false;
|
||||
$scope.options.predicate = predicate;
|
||||
};
|
||||
|
||||
$scope.showTriggerCredentialsModal = function(trigger) {
|
||||
$scope.triggerCredentialsModalTrigger = trigger;
|
||||
$scope.triggerCredentialsModalCounter++;
|
||||
};
|
||||
|
||||
$scope.askDeleteTrigger = function(trigger) {
|
||||
$scope.deleteTriggerInfo = {
|
||||
'trigger': trigger
|
||||
};
|
||||
};
|
||||
|
||||
$scope.askRunTrigger = function(trigger) {
|
||||
if (!trigger.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trigger.can_invoke) {
|
||||
bootbox.alert('You do not have permission to manually invoke this trigger');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.currentStartTrigger = trigger;
|
||||
$scope.showTriggerStartDialogCounter++;
|
||||
};
|
||||
|
||||
$scope.askToggleTrigger = function(trigger) {
|
||||
if (!trigger.can_invoke) {
|
||||
bootbox.alert('You do not have permission to edit this trigger');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.toggleTriggerInfo = {
|
||||
'trigger': trigger
|
||||
};
|
||||
};
|
||||
|
||||
$scope.toggleTrigger = function(trigger, opt_callback) {
|
||||
if (!trigger) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': trigger.id
|
||||
};
|
||||
|
||||
var data = {
|
||||
'enabled': !trigger.enabled
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not toggle build trigger', function() {
|
||||
opt_callback && opt_callback(false);
|
||||
});
|
||||
|
||||
ApiService.updateBuildTrigger(data, params).then(function(resp) {
|
||||
trigger.enabled = !trigger.enabled;
|
||||
trigger.disabled_reason = 'user_toggled';
|
||||
opt_callback && opt_callback(true);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.deleteTrigger = function(trigger, opt_callback) {
|
||||
if (!trigger) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'trigger_uuid': trigger.id
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not delete build trigger', function() {
|
||||
opt_callback && opt_callback(false);
|
||||
});
|
||||
|
||||
ApiService.deleteBuildTrigger(null, params).then(function(resp) {
|
||||
$scope.triggers.splice($scope.triggers.indexOf(trigger), 1);
|
||||
opt_callback && opt_callback(true);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.showNewBuildDialog = function() {
|
||||
$scope.showBuildDialogCounter++;
|
||||
};
|
||||
|
||||
$scope.handleBuildStarted = function(build) {
|
||||
if ($scope.allBuilds) {
|
||||
$scope.allBuilds.push(build);
|
||||
}
|
||||
updateBuilds();
|
||||
|
||||
$scope.feedback = {
|
||||
'kind': 'info',
|
||||
'message': 'Build {buildid} started',
|
||||
'data': {
|
||||
'buildid': build.id
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
58
static/js/directives/repo-view/repo-panel-info.js
Normal file
58
static/js/directives/repo-view/repo-panel-info.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* An element which displays the information panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelInfo', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-info.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'builds': '=builds',
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function($scope, $element, ApiService, Config, Features, StateService) {
|
||||
StateService.updateStateIn($scope, function(state) {
|
||||
$scope.inReadOnlyMode = state.inReadOnlyMode;
|
||||
});
|
||||
|
||||
$scope.Features = Features;
|
||||
|
||||
$scope.$watch('repository', function(repository) {
|
||||
if (!$scope.repository) { return; }
|
||||
|
||||
var namespace = $scope.repository.namespace;
|
||||
var name = $scope.repository.name;
|
||||
|
||||
$scope.pullCommand = 'docker pull ' + Config.getDomain() + '/' + namespace + '/' + name;
|
||||
});
|
||||
|
||||
$scope.updateDescription = function(content) {
|
||||
$scope.repository.description = content;
|
||||
$scope.repository.put();
|
||||
};
|
||||
|
||||
$scope.getAggregatedUsage = function(stats, days) {
|
||||
if (!stats || !stats.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var startDate = moment().subtract(days + 1, 'days');
|
||||
for (var i = 0; i < stats.length; ++i) {
|
||||
var stat = stats[i];
|
||||
var statDate = moment(stat['date']);
|
||||
if (statDate.isBefore(startDate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
count += stat['count'];
|
||||
}
|
||||
return count;
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
468
static/js/directives/repo-view/repo-panel-mirroring.js
Normal file
468
static/js/directives/repo-view/repo-panel-mirroring.js
Normal file
|
@ -0,0 +1,468 @@
|
|||
import { vendor } from "postcss";
|
||||
|
||||
/**
|
||||
* An element which displays the mirroring panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelMirror', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-mirroring.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controllerAs: 'vm',
|
||||
controller: function ($scope, ApiService, Features) {
|
||||
|
||||
let vm = this;
|
||||
|
||||
// Feature Flagged
|
||||
if (!Features.REPO_MIRROR) { return; }
|
||||
|
||||
// Shared by API Calls
|
||||
let params = { 'repository': $scope.repository.namespace + '/' + $scope.repository.name };
|
||||
|
||||
/**
|
||||
* Mirror Configuration
|
||||
*/
|
||||
vm.isSetup = false;
|
||||
vm.expirationDate = null;
|
||||
vm.isEnabled = null;
|
||||
vm.httpProxy = null;
|
||||
vm.httpsProxy = null;
|
||||
vm.externalReference = null;
|
||||
vm.noProxy = null;
|
||||
vm.retriesRemaining = null;
|
||||
vm.robot = null;
|
||||
vm.status = null;
|
||||
vm.syncInterval = null;
|
||||
vm.syncStartDate = null;
|
||||
vm.tags = null;
|
||||
vm.username = null;
|
||||
vm.verifyTLS = null;
|
||||
|
||||
/**
|
||||
* Fetch the latest Repository Mirror Configuration
|
||||
*/
|
||||
vm.getMirror = function() {
|
||||
ApiService.getRepoMirrorConfig(null, params)
|
||||
.then(function (resp) {
|
||||
vm.isSetup = true;
|
||||
|
||||
vm.isEnabled = resp.is_enabled;
|
||||
vm.externalReference = resp.external_reference;
|
||||
vm.syncInterval = resp.sync_interval;
|
||||
vm.username = resp.external_registry_username;
|
||||
vm.syncStartDate = resp.sync_start_date;
|
||||
vm.status = resp.sync_status;
|
||||
vm.expirationDate = resp.sync_expiration_date;
|
||||
vm.retriesRemaining = resp.sync_retries_remaining;
|
||||
|
||||
vm.robot = {};
|
||||
if (resp.robot_username) {
|
||||
vm.robot = {
|
||||
'name': resp.robot_username,
|
||||
'kind': 'user',
|
||||
'is_robot': true
|
||||
};
|
||||
}
|
||||
|
||||
vm.tags = resp.root_rule.rule_value || []; // TODO: Use RepoMirrorRule-specific endpoint
|
||||
|
||||
// TODO: These are not consistently provided by the API. Correct that in the API.
|
||||
vm.verifyTLS = resp.external_registry_config.verify_tls;
|
||||
if (resp.external_registry_config.proxy) {
|
||||
vm.httpProxy = resp.external_registry_config.proxy.http_proxy;
|
||||
vm.httpsProxy = resp.external_registry_config.proxy.https_proxy;
|
||||
vm.noProxy = resp.external_registry_config.proxy.no_proxy;
|
||||
}
|
||||
}, function (err) { console.info("No repository mirror configuration.", err); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-friendly status messages
|
||||
*/
|
||||
vm.statusLabels = {
|
||||
"NEVER_RUN": "Scheduled",
|
||||
"SYNC_NOW": "Scheduled Now",
|
||||
"SYNCING": "Sync In Progress",
|
||||
"SUCCESS": "Last Sync Succeeded",
|
||||
"FAIL": "Last Sync Failed"
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert (Unix) Timestamp to ISO Formatted Date String used by the API
|
||||
*/
|
||||
vm.timestampToISO = function(ts) {
|
||||
let dt = moment.unix(ts).toISOString().split('.')[0] + 'Z'; // Remove milliseconds
|
||||
return dt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert ISO Date String to (Unix) Timestamp
|
||||
*/
|
||||
vm.timestampFromISO = function(dt) {
|
||||
let ts = moment(dt).unix();
|
||||
return ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* When set to a truthy value, any `cor-confirm-dialog` associated with these variables will
|
||||
* be displayed.
|
||||
*/
|
||||
vm.credentialsChanges = null;
|
||||
vm.httpProxyChanges = null;
|
||||
vm.httpsProxyChanges = null;
|
||||
vm.locationChanges = null;
|
||||
vm.noProxyChanges = null;
|
||||
vm.syncIntervalChanges = null;
|
||||
vm.syncStartDateChanges = null;
|
||||
vm.tagChanges = null;
|
||||
|
||||
/**
|
||||
* The following `show` functions initialize and trigger the display of a modal to modify
|
||||
* configuration attributes.
|
||||
*/
|
||||
|
||||
vm.showChangeSyncInterval = function() {
|
||||
vm.syncIntervalChanges = {
|
||||
'fieldName': 'synchronization interval',
|
||||
'values': {
|
||||
'sync_interval': vm.syncInterval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.showChangeSyncStartDate = function() {
|
||||
let ts = vm.timestampFromISO(vm.syncStartDate);
|
||||
vm.syncStartDateChanges = {
|
||||
'fieldName': 'next synchronization date',
|
||||
'values': {
|
||||
'sync_start_date': ts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.showChangeTags = function() {
|
||||
vm.tagChanges = {
|
||||
'fieldName': 'tag patterns',
|
||||
'values': {
|
||||
'rule_value': vm.tags || []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.showChangeCredentials = function() {
|
||||
vm.credentialsChanges = {
|
||||
'fieldName': 'credentials',
|
||||
'values': {
|
||||
'external_registry_username': vm.username,
|
||||
'external_registry_password': null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.showChangeHTTPProxy = function() {
|
||||
vm.httpProxyChanges = {
|
||||
'fieldName': 'HTTP Proxy',
|
||||
'values': {
|
||||
'external_registry_config': {
|
||||
'proxy': {
|
||||
'http_proxy': vm.httpProxy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.showChangeHTTPsProxy = function() {
|
||||
vm.httpsProxyChanges = {
|
||||
'fieldName': 'HTTPs Proxy',
|
||||
'values': {
|
||||
'external_registry_config': {
|
||||
'proxy': {
|
||||
'https_proxy': vm.httpsProxy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.showChangeNoProxy = function() {
|
||||
vm.noProxyChanges = {
|
||||
'fieldName': 'No Proxy',
|
||||
'values': {
|
||||
'external_registry_config': {
|
||||
'proxy': {
|
||||
'no_proxy': vm.noProxy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm.showChangeExternalRepository = function() {
|
||||
vm.externalRepositoryChanges = {
|
||||
'fieldName': 'External Repository',
|
||||
'values': {
|
||||
'external_reference': vm.externalReference
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit API request to modify repository mirroring attribute(s)
|
||||
*/
|
||||
vm.changeConfig = function(data, callback) {
|
||||
|
||||
let fieldName = data.fieldName || 'configuration';
|
||||
let requestBody = data.values;
|
||||
let errMsg = "Unable to change " + fieldName + '.';
|
||||
let handleError = ApiService.errorDisplay(errMsg, callback);
|
||||
|
||||
let handleSuccess = function() {
|
||||
vm.getMirror(); // Fetch updated configuration
|
||||
if (callback) callback(true);
|
||||
}
|
||||
|
||||
ApiService.changeRepoMirrorConfig(requestBody, params)
|
||||
.then(handleSuccess, handleError);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the DatePicker's Unix timestamp into a string compatible with the API
|
||||
* before attempting to change it.
|
||||
*/
|
||||
vm.changeSyncStartDate = function(data, callback) {
|
||||
let newSyncStartDate = vm.timestampToISO(data.values.sync_start_date);
|
||||
data.values.sync_start_date = newSyncStartDate;
|
||||
return vm.changeConfig(data, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable `Verify TLS`.
|
||||
*/
|
||||
vm.enableVerifyTLS = function() {
|
||||
let data = {
|
||||
'fieldName': 'TLS verification',
|
||||
'values': {
|
||||
'external_registry_config': {
|
||||
'verify_tls': true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vm.changeConfig(data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable `Verify TLS`
|
||||
*/
|
||||
vm.disableVerifyTLS = function() {
|
||||
let data = {
|
||||
'fieldName': 'TLS verification',
|
||||
'values': {
|
||||
'external_registry_config': {
|
||||
'verify_tls': false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vm.changeConfig(data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle `Verify TLS`.
|
||||
*/
|
||||
vm.toggleVerifyTLS = function() {
|
||||
if (vm.verifyTLS) return vm.disableVerifyTLS();
|
||||
else return vm.enableVerifyTLS();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Robot user.
|
||||
*/
|
||||
vm.changeRobot = function(robot) {
|
||||
if (!vm.robot) return;
|
||||
if (!robot || robot.name == vm.robot.name) return;
|
||||
|
||||
let data = {
|
||||
'fieldName': 'robot',
|
||||
'values': {
|
||||
'robot_username': robot.name
|
||||
}
|
||||
}
|
||||
|
||||
return vm.changeConfig(data, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Credentials
|
||||
*/
|
||||
vm.deleteCredentials = function() {
|
||||
let data = {
|
||||
'fieldName': 'credentials',
|
||||
'values': {
|
||||
'external_registry_username': null,
|
||||
'external_registry_password': null
|
||||
}
|
||||
}
|
||||
|
||||
return vm.changeConfig(data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable mirroring configuration.
|
||||
*/
|
||||
vm.enableMirroring = function() {
|
||||
let data = {
|
||||
'fieldName': 'enabled state',
|
||||
'values': {
|
||||
'is_enabled': true
|
||||
}
|
||||
}
|
||||
|
||||
return vm.changeConfig(data, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable mirroring configuration.
|
||||
*/
|
||||
vm.disableMirroring = function() {
|
||||
let data = {
|
||||
'fieldName': 'enabled state',
|
||||
'values': {
|
||||
'is_enabled': false
|
||||
}
|
||||
}
|
||||
|
||||
return vm.changeConfig(data, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle mirroring on/off.
|
||||
*/
|
||||
vm.toggleMirroring = function() {
|
||||
if (vm.isEnabled) return vm.disableMirroring();
|
||||
else return vm.enableMirroring();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Tag-Rules
|
||||
*/
|
||||
vm.changeTagRules = function(data, callback) {
|
||||
let csv = data.values.rule_value;
|
||||
let patterns = csv.split(',');
|
||||
|
||||
patterns.map(s => s.trim()); // Trim excess whitespace
|
||||
patterns = Array.from(new Set(patterns)); // De-duplicate
|
||||
|
||||
if (patterns.length < 1) {
|
||||
bootbox.alert('Rule value required');
|
||||
callback(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
data = {
|
||||
'root_rule': {
|
||||
'rule_type': 'TAG_GLOB_CSV',
|
||||
'rule_value': patterns
|
||||
}
|
||||
}
|
||||
|
||||
let displayError = ApiService.errorDisplay('Could not change Tag Rules', callback);
|
||||
|
||||
ApiService
|
||||
.changeRepoMirrorRule(data, params)
|
||||
.then(function(resp) {
|
||||
vm.getMirror();
|
||||
callback(true);
|
||||
}, displayError);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger Immediate Synchronization
|
||||
*/
|
||||
vm.syncNow = function () {
|
||||
let displayError = ApiService.errorDisplay('Unable to sync now', null);
|
||||
|
||||
ApiService
|
||||
.syncNow(null, params)
|
||||
.then(function(resp) {
|
||||
vm.getMirror(); // Reload latest changes
|
||||
return true;
|
||||
}, displayError);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel In-Progress Synchronization
|
||||
*/
|
||||
vm.syncCancel = function() {
|
||||
let displayError = ApiService.errorDisplay('Unable to cancel sync', null);
|
||||
|
||||
ApiService
|
||||
.syncCancel(null, params)
|
||||
.then(function(resp) {
|
||||
vm.getMirror(); // Reload latest changes
|
||||
return true;
|
||||
}, displayError);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Load the current mirror configuration on initialization
|
||||
if ($scope.repository.state == 'MIRROR') {
|
||||
vm.getMirror();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure mirroing.
|
||||
* TODO: Move this, and the associated template/view, to its own component and use the
|
||||
* wizard-flow instead of a single form.
|
||||
*/
|
||||
vm.setupMirror = function() {
|
||||
|
||||
// Apply transformations
|
||||
let now = vm.timestampToISO(moment().unix());
|
||||
let syncStartDate = vm.timestampToISO(vm.syncStartDate) || now;
|
||||
let patterns = Array.from(new Set(vm.tags.split(',').map(s => s.trim()))); // trim + de-dupe
|
||||
|
||||
let requestBody = {
|
||||
'external_reference': vm.externalReference,
|
||||
'external_registry_username': vm.username,
|
||||
'external_registry_password': vm.password,
|
||||
'sync_interval': vm.syncInterval,
|
||||
'sync_start_date': syncStartDate,
|
||||
'robot_username': vm.robot.name,
|
||||
'external_registry_config': {
|
||||
'verify_tls': vm.verifyTLS || false, // `null` not allowed
|
||||
'proxy': {
|
||||
'http_proxy': vm.httpProxy,
|
||||
'https_proxy': vm.httpsProxy,
|
||||
'no_proxy': vm.noProxy
|
||||
}
|
||||
},
|
||||
'root_rule': {
|
||||
'rule_type': 'TAG_GLOB_CSV',
|
||||
'rule_value': patterns
|
||||
}
|
||||
}
|
||||
|
||||
let successHandler = function(resp) { vm.getMirror(); return true; }
|
||||
let errorHandler = ApiService.errorDisplay('Unable to setup mirror.', null);
|
||||
ApiService.createRepoMirrorConfig(requestBody, params).then(successHandler, errorHandler);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
123
static/js/directives/repo-view/repo-panel-settings.js
Normal file
123
static/js/directives/repo-view/repo-panel-settings.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* An element which displays the settings panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelSettings', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-settings.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'isEnabled': '=isEnabled'
|
||||
},
|
||||
controller: function($scope, $element, $route, ApiService, Config, Features, StateService) {
|
||||
StateService.updateStateIn($scope, function(state) {
|
||||
$scope.inReadOnlyMode = state.inReadOnlyMode;
|
||||
});
|
||||
|
||||
$scope.Features = Features;
|
||||
$scope.deleteDialogCounter = 0;
|
||||
|
||||
var getTitle = function(repo) {
|
||||
return repo.kind == 'application' ? 'application' : 'image';
|
||||
};
|
||||
|
||||
$scope.getBadgeFormat = function(format, repository) {
|
||||
if (!repository) { return ''; }
|
||||
|
||||
var imageUrl = Config.getUrl('/repository/' + repository.namespace + '/' + repository.name + '/status');
|
||||
if (!$scope.repository.is_public) {
|
||||
imageUrl += '?token=' + repository.status_token;
|
||||
}
|
||||
|
||||
var linkUrl = Config.getUrl('/repository/' + repository.namespace + '/' + repository.name);
|
||||
|
||||
switch (format) {
|
||||
case 'svg':
|
||||
return imageUrl;
|
||||
|
||||
case 'md':
|
||||
return '[](' + linkUrl + ')';
|
||||
|
||||
case 'asciidoc':
|
||||
return 'image:' + imageUrl + '["Docker Repository on ' + Config.REGISTRY_TITLE_SHORT + '", link="' + linkUrl + '"]';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
$scope.askDelete = function() {
|
||||
$scope.deleteDialogCounter++;
|
||||
$scope.deleteRepoInfo = {
|
||||
'counter': $scope.deleteDialogCounter
|
||||
};
|
||||
};
|
||||
|
||||
$scope.deleteRepo = function(info, callback) {
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay(
|
||||
'Could not delete ' + getTitle($scope.repository), callback);
|
||||
|
||||
ApiService.deleteRepository(null, params).then(function() {
|
||||
callback(true);
|
||||
setTimeout(function() {
|
||||
document.location = '/repository/';
|
||||
}, 100);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.askChangeAccess = function(newAccess) {
|
||||
var msg = 'Are you sure you want to make this ' + getTitle($scope.repository) + ' ' +
|
||||
newAccess + '?';
|
||||
|
||||
bootbox.confirm(msg, function(r) {
|
||||
if (!r) { return; }
|
||||
$scope.changeAccess(newAccess);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.changeAccess = function(newAccess) {
|
||||
var visibility = {
|
||||
'visibility': newAccess
|
||||
};
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
ApiService.changeRepoVisibility(visibility, params).then(function() {
|
||||
$scope.repository.is_public = newAccess == 'public';
|
||||
}, ApiService.errorDisplay('Could not change visibility'));
|
||||
};
|
||||
|
||||
// Hide State, for now, if mirroring feature-flag is not enabled.
|
||||
if (Features.REPO_MIRROR) {
|
||||
$scope.repoStates = ['NORMAL', 'READ_ONLY'];
|
||||
}
|
||||
|
||||
// Only show MIRROR as an option if the feature-flag is enabled.
|
||||
if (Features.REPO_MIRROR) { $scope.repoStates.push('MIRROR'); }
|
||||
|
||||
// Hide State, for now, if mirroring feature-flag is not enabled.
|
||||
if (Features.REPO_MIRROR) {
|
||||
$scope.selectedState = $scope.repository.state;
|
||||
$scope.changeState = function() {
|
||||
let state = {'state': $scope.selectedState};
|
||||
let params = {'repository': $scope.repository.namespace + '/' + $scope.repository.name};
|
||||
ApiService.changeRepoState(state, params)
|
||||
.then(function() {
|
||||
$scope.repository.state = $scope.selectedState;
|
||||
$route.reload(); // State will eventually affect many UI elements. Reload the view.
|
||||
}, ApiService.errorDisplay('Could not change state'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
499
static/js/directives/repo-view/repo-panel-tags.js
Normal file
499
static/js/directives/repo-view/repo-panel-tags.js
Normal file
|
@ -0,0 +1,499 @@
|
|||
/**
|
||||
* An element which displays the tags panel for a repository view.
|
||||
*/
|
||||
angular.module('quay').directive('repoPanelTags', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/repo-view/repo-panel-tags.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'repository': '=repository',
|
||||
'repositoryTags': '=repositoryTags',
|
||||
'selectedTags': '=selectedTags',
|
||||
'historyFilter': '=historyFilter',
|
||||
'imagesResource': '=imagesResource',
|
||||
'imageLoader': '=imageLoader',
|
||||
|
||||
'isEnabled': '=isEnabled',
|
||||
|
||||
'getImages': '&getImages'
|
||||
},
|
||||
controller: function($scope, $element, $filter, $location, ApiService, UIService,
|
||||
VulnerabilityService, TableService, Features, StateService) {
|
||||
StateService.updateStateIn($scope, function(state) {
|
||||
$scope.inReadOnlyMode = state.inReadOnlyMode;
|
||||
});
|
||||
|
||||
$scope.Features = Features;
|
||||
|
||||
$scope.maxTrackCount = 5;
|
||||
|
||||
$scope.checkedTags = UIService.createCheckStateController([], 'name');
|
||||
$scope.checkedTags.setPage(0);
|
||||
|
||||
$scope.options = {
|
||||
'predicate': 'last_modified_datetime',
|
||||
'reverse': false,
|
||||
'page': 0
|
||||
};
|
||||
|
||||
$scope.iterationState = {};
|
||||
$scope.tagActionHandler = null;
|
||||
$scope.tagsPerPage = 25;
|
||||
|
||||
$scope.expandedView = false;
|
||||
$scope.labelCache = {};
|
||||
|
||||
$scope.manifestVulnerabilities = {};
|
||||
$scope.repoDelegationsInfo = null;
|
||||
|
||||
$scope.defcon1 = {};
|
||||
$scope.hasDefcon1 = false;
|
||||
|
||||
var loadRepoSignatures = function() {
|
||||
if (!$scope.repository || !$scope.repository.trust_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.repoSignatureError = false;
|
||||
$scope.repoDelegationsInfo = null;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name
|
||||
};
|
||||
|
||||
ApiService.getRepoSignatures(null, params).then(function(resp) {
|
||||
$scope.repoDelegationsInfo = resp;
|
||||
}, function() {
|
||||
$scope.repoDelegationsInfo = {'error': true};
|
||||
});
|
||||
};
|
||||
|
||||
var setTagState = function() {
|
||||
if (!$scope.repositoryTags || !$scope.selectedTags) { return; }
|
||||
|
||||
// Build a list of all the tags, with extending information.
|
||||
var allTags = [];
|
||||
for (var tag in $scope.repositoryTags) {
|
||||
if (!$scope.repositoryTags.hasOwnProperty(tag)) { continue; }
|
||||
|
||||
var tagData = $scope.repositoryTags[tag];
|
||||
var tagInfo = $.extend(tagData, {
|
||||
'name': tag,
|
||||
'last_modified_datetime': TableService.getReversedTimestamp(tagData.last_modified),
|
||||
'expiration_date': tagData.expiration ? TableService.getReversedTimestamp(tagData.expiration) : null,
|
||||
});
|
||||
|
||||
allTags.push(tagInfo);
|
||||
}
|
||||
|
||||
// Sort the tags by the predicate and the reverse, and map the information.
|
||||
var imageIDs = [];
|
||||
var ordered = TableService.buildOrderedItems(allTags, $scope.options,
|
||||
['name'], ['last_modified_datetime', 'size']).entries;
|
||||
|
||||
var checked = [];
|
||||
var imageMap = {};
|
||||
var imageIndexMap = {};
|
||||
for (var i = 0; i < ordered.length; ++i) {
|
||||
var tagInfo = ordered[i];
|
||||
if (!tagInfo.image_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!imageMap[tagInfo.image_id]) {
|
||||
imageMap[tagInfo.image_id] = [];
|
||||
imageIDs.push(tagInfo.image_id)
|
||||
}
|
||||
|
||||
imageMap[tagInfo.image_id].push(tagInfo);
|
||||
if ($.inArray(tagInfo.name, $scope.selectedTags) >= 0) {
|
||||
checked.push(tagInfo);
|
||||
}
|
||||
|
||||
if (!imageIndexMap[tagInfo.image_id]) {
|
||||
imageIndexMap[tagInfo.image_id] = {'start': i, 'end': i};
|
||||
}
|
||||
|
||||
imageIndexMap[tagInfo.image_id]['end'] = i;
|
||||
};
|
||||
|
||||
// Calculate the image tracks.
|
||||
var colors = d3.scale.category10();
|
||||
if (Object.keys(imageMap).length > 10) {
|
||||
colors = d3.scale.category20();
|
||||
}
|
||||
|
||||
var imageTracks = [];
|
||||
var imageTrackEntries = [];
|
||||
var trackEntryForImage = {};
|
||||
|
||||
var visibleStartIndex = ($scope.options.page * $scope.tagsPerPage);
|
||||
var visibleEndIndex = (($scope.options.page + 1) * $scope.tagsPerPage);
|
||||
|
||||
imageIDs.sort().map(function(image_id) {
|
||||
if (imageMap[image_id].length >= 2){
|
||||
// Create the track entry.
|
||||
var imageIndexRange = imageIndexMap[image_id];
|
||||
var colorIndex = imageTrackEntries.length;
|
||||
var trackEntry = {
|
||||
'image_id': image_id,
|
||||
'color': colors(colorIndex),
|
||||
'count': imageMap[image_id].length,
|
||||
'tags': imageMap[image_id],
|
||||
'index_range': imageIndexRange,
|
||||
'visible': visibleStartIndex <= imageIndexRange.end && imageIndexRange.start <= visibleEndIndex,
|
||||
};
|
||||
|
||||
trackEntryForImage[image_id] = trackEntry;
|
||||
imageMap[image_id]['color'] = colors(colorIndex);
|
||||
|
||||
// Find the track in which we can place the entry, if any.
|
||||
var existingTrack = null;
|
||||
for (var i = 0; i < imageTracks.length; ++i) {
|
||||
// For the current track, ensure that the start and end index
|
||||
// for the current entry is outside of the range of the track's
|
||||
// entries. If so, then we can add the entry to the track.
|
||||
var currentTrack = imageTracks[i];
|
||||
var canAddToCurrentTrack = true;
|
||||
for (var j = 0; j < currentTrack.entries.length; ++j) {
|
||||
var currentTrackEntry = currentTrack.entries[j];
|
||||
var entryInfo = imageIndexMap[currentTrackEntry.image_id];
|
||||
if (Math.max(entryInfo.start, imageIndexRange.start) <= Math.min(entryInfo.end, imageIndexRange.end)) {
|
||||
canAddToCurrentTrack = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canAddToCurrentTrack) {
|
||||
existingTrack = currentTrack;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the entry to the track or create a new track if necessary.
|
||||
if (existingTrack) {
|
||||
existingTrack.entries.push(trackEntry)
|
||||
existingTrack.entryByImageId[image_id] = trackEntry;
|
||||
existingTrack.endIndex = Math.max(existingTrack.endIndex, imageIndexRange.end);
|
||||
|
||||
for (var j = imageIndexRange.start; j <= imageIndexRange.end; j++) {
|
||||
existingTrack.entryByIndex[j] = trackEntry;
|
||||
}
|
||||
} else {
|
||||
var entryByImageId = {};
|
||||
entryByImageId[image_id] = trackEntry;
|
||||
|
||||
var entryByIndex = {};
|
||||
for (var j = imageIndexRange.start; j <= imageIndexRange.end; j++) {
|
||||
entryByIndex[j] = trackEntry;
|
||||
}
|
||||
|
||||
imageTracks.push({
|
||||
'entries': [trackEntry],
|
||||
'entryByImageId': entryByImageId,
|
||||
'startIndex': imageIndexRange.start,
|
||||
'endIndex': imageIndexRange.end,
|
||||
'entryByIndex': entryByIndex,
|
||||
});
|
||||
}
|
||||
|
||||
imageTrackEntries.push(trackEntry);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.imageMap = imageMap;
|
||||
$scope.imageTracks = imageTracks;
|
||||
$scope.imageTrackEntries = imageTrackEntries;
|
||||
$scope.trackEntryForImage = trackEntryForImage;
|
||||
|
||||
$scope.options.page = 0;
|
||||
|
||||
$scope.tags = ordered;
|
||||
$scope.allTags = allTags;
|
||||
|
||||
$scope.checkedTags = UIService.createCheckStateController(ordered, 'name');
|
||||
$scope.checkedTags.setPage($scope.options.page, $scope.tagsPerPage);
|
||||
|
||||
$scope.checkedTags.listen(function(allChecked, pageChecked) {
|
||||
$scope.selectedTags = allChecked.map(function(tag_info) {
|
||||
return tag_info.name;
|
||||
});
|
||||
|
||||
$scope.fullPageSelected = ((pageChecked.length == $scope.tagsPerPage) &&
|
||||
(allChecked.length != $scope.tags.length));
|
||||
$scope.allTagsSelected = ((allChecked.length > $scope.tagsPerPage) &&
|
||||
(allChecked.length == $scope.tags.length));
|
||||
});
|
||||
|
||||
$scope.checkedTags.setChecked(checked);
|
||||
}
|
||||
|
||||
$scope.$watch('options.predicate', setTagState);
|
||||
$scope.$watch('options.reverse', setTagState);
|
||||
$scope.$watch('options.filter', setTagState);
|
||||
|
||||
$scope.$watch('options.page', function(page) {
|
||||
if (page != null && $scope.checkedTags) {
|
||||
$scope.checkedTags.setPage(page, $scope.tagsPerPage);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$watch('selectedTags', function(selectedTags) {
|
||||
if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; }
|
||||
|
||||
$scope.checkedTags.setChecked(selectedTags.map(function(tag) {
|
||||
return $scope.repositoryTags[tag];
|
||||
}));
|
||||
}, true);
|
||||
|
||||
$scope.$watch('repository', function(updatedRepoObject, previousRepoObject) {
|
||||
// Process each of the tags.
|
||||
setTagState();
|
||||
loadRepoSignatures();
|
||||
});
|
||||
|
||||
$scope.$watch('repositoryTags', function(newTags, oldTags) {
|
||||
if (newTags === oldTags) { return; }
|
||||
// Process each of the tags.
|
||||
setTagState();
|
||||
loadRepoSignatures();
|
||||
}, true);
|
||||
|
||||
$scope.clearSelectedTags = function() {
|
||||
$scope.checkedTags.setChecked([]);
|
||||
};
|
||||
|
||||
$scope.selectAllTags = function() {
|
||||
$scope.checkedTags.setChecked($scope.tags);
|
||||
};
|
||||
|
||||
$scope.constrastingColor = function(backgroundColor) {
|
||||
// From: https://stackoverflow.com/questions/11068240/what-is-the-most-efficient-way-to-parse-a-css-color-in-javascript
|
||||
function parseColor(input) {
|
||||
m = input.match(/^#([0-9a-f]{6})$/i)[1];
|
||||
return [
|
||||
parseInt(m.substr(0,2),16),
|
||||
parseInt(m.substr(2,2),16),
|
||||
parseInt(m.substr(4,2),16)
|
||||
];
|
||||
}
|
||||
|
||||
var rgb = parseColor(backgroundColor);
|
||||
|
||||
// From W3C standard.
|
||||
var o = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000);
|
||||
return (o > 150) ? 'black' : 'white';
|
||||
};
|
||||
|
||||
$scope.getTrackEntryForIndex = function(it, index) {
|
||||
index += $scope.options.page * $scope.tagsPerPage;
|
||||
return it.entryByIndex[index];
|
||||
};
|
||||
|
||||
$scope.trackLineExpandedClass = function(it, index, track_info) {
|
||||
var entry = $scope.getTrackEntryForIndex(it, index);
|
||||
if (!entry) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var adjustedIndex = index + ($scope.options.page * $scope.tagsPerPage);
|
||||
|
||||
if (index < entry.index_range.start) {
|
||||
return 'before';
|
||||
}
|
||||
|
||||
if (index > entry.index_range.end) {
|
||||
return 'after';
|
||||
}
|
||||
|
||||
if (index >= entry.index_range.start && index < entry.index_range.end) {
|
||||
return 'middle';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
$scope.trackLineClass = function(it, index) {
|
||||
var entry = $scope.getTrackEntryForIndex(it, index);
|
||||
if (!entry) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var adjustedIndex = index + ($scope.options.page * $scope.tagsPerPage);
|
||||
|
||||
if (index == entry.index_range.start) {
|
||||
return 'start';
|
||||
}
|
||||
|
||||
if (index == entry.index_range.end) {
|
||||
return 'end';
|
||||
}
|
||||
|
||||
if (index > entry.index_range.start && index < entry.index_range.end) {
|
||||
return 'middle';
|
||||
}
|
||||
|
||||
if (index < entry.index_range.start) {
|
||||
return 'before';
|
||||
}
|
||||
|
||||
if (index > entry.index_range.end) {
|
||||
return 'after';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.tablePredicateClass = function(name, predicate, reverse) {
|
||||
if (name != predicate) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'current ' + (reverse ? 'reversed' : '');
|
||||
};
|
||||
|
||||
$scope.askDeleteTag = function(tag) {
|
||||
$scope.tagActionHandler.askDeleteTag(tag);
|
||||
};
|
||||
|
||||
$scope.askDeleteMultipleTags = function(tags) {
|
||||
if (tags.length == 1) {
|
||||
$scope.askDeleteTag(tags[0].name);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.tagActionHandler.askDeleteMultipleTags(tags);
|
||||
};
|
||||
|
||||
$scope.askChangeTagsExpiration = function(tags) {
|
||||
if ($scope.inReadOnlyMode) {
|
||||
return;
|
||||
}
|
||||
$scope.tagActionHandler.askChangeTagsExpiration(tags);
|
||||
};
|
||||
|
||||
$scope.askAddTag = function(tag) {
|
||||
if ($scope.inReadOnlyMode) {
|
||||
return;
|
||||
}
|
||||
$scope.tagActionHandler.askAddTag(tag.image_id, tag.manifest_digest);
|
||||
};
|
||||
|
||||
$scope.showLabelEditor = function(tag) {
|
||||
if ($scope.inReadOnlyMode) {
|
||||
return;
|
||||
}
|
||||
if (!tag.manifest_digest) { return; }
|
||||
$scope.tagActionHandler.showLabelEditor(tag.manifest_digest);
|
||||
};
|
||||
|
||||
$scope.orderBy = function(predicate) {
|
||||
if (predicate == $scope.options.predicate) {
|
||||
$scope.options.reverse = !$scope.options.reverse;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.options.reverse = false;
|
||||
$scope.options.predicate = predicate;
|
||||
};
|
||||
|
||||
$scope.commitTagFilter = function(tag) {
|
||||
var r = new RegExp('^[0-9a-fA-F]{7}$');
|
||||
return tag.name.match(r);
|
||||
};
|
||||
|
||||
$scope.allTagFilter = function(tag) {
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.noTagFilter = function(tag) {
|
||||
return false;
|
||||
};
|
||||
|
||||
$scope.imageIDFilter = function(image_id, tag) {
|
||||
return tag.image_id == image_id;
|
||||
};
|
||||
|
||||
$scope.setTab = function(tab) {
|
||||
$location.search('tab', tab);
|
||||
};
|
||||
|
||||
$scope.selectTrack = function(it) {
|
||||
$scope.checkedTags.checkByFilter(function(tag) {
|
||||
return $scope.imageIDFilter(it.image_id, tag);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showHistory = function(checked) {
|
||||
if (!checked.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.historyFilter = $scope.getTagNames(checked);
|
||||
$scope.setTab('history');
|
||||
};
|
||||
|
||||
$scope.setExpanded = function(expanded) {
|
||||
$scope.expandedView = expanded;
|
||||
};
|
||||
|
||||
$scope.getTagNames = function(checked) {
|
||||
var names = checked.map(function(tag) {
|
||||
return tag.name;
|
||||
});
|
||||
|
||||
return names.join(',');
|
||||
};
|
||||
|
||||
$scope.handleLabelsChanged = function(manifest_digest) {
|
||||
delete $scope.labelCache[manifest_digest];
|
||||
};
|
||||
|
||||
$scope.loadManifestList = function(tag) {
|
||||
if (tag.manifest_list_loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
tag.manifest_list_loading = true;
|
||||
|
||||
var params = {
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
'manifestref': tag.manifest_digest
|
||||
};
|
||||
|
||||
ApiService.getRepoManifest(null, params).then(function(resp) {
|
||||
tag.manifest_list = JSON.parse(resp['manifest_data']);
|
||||
tag.manifest_list_loading = false;
|
||||
}, ApiService.errorDisplay('Could not load manifest list contents'))
|
||||
};
|
||||
|
||||
$scope.manifestsOf = function(tag) {
|
||||
if (!tag.is_manifest_list) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!tag.manifest_list) {
|
||||
$scope.loadManifestList(tag);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!tag._mapped_manifests) {
|
||||
// Calculate once and cache to avoid angular digest cycles.
|
||||
tag._mapped_manifests = tag.manifest_list.manifests.map(function(manifest) {
|
||||
return {
|
||||
'raw': manifest,
|
||||
'os': manifest.platform.os,
|
||||
'size': manifest.size,
|
||||
'digest': manifest.digest,
|
||||
'description': `${manifest.platform.os} on ${manifest.platform.architecture}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return tag._mapped_manifests;
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
Reference in a new issue