Commit 81e7ec1b by Jeffrey Phillips

Provide two common modules: openshiftCommonServices, openshiftCommonUI

Update module name for services to openshiftCommonServices
Add Projects service and AlertsMessage service

Add openshiftCommonUI module
Add common filters
Add create/edit project directives
Add delete-link directive
parent 38059826
......@@ -30,7 +30,7 @@ module.exports = function (grunt) {
separator: ';'
},
dist: {
src: ['src/**/*.module.js', 'src/**/*.js'],
src: ['src/**/*.module.js', 'dist/scripts/templates.js', 'src/**/*.js'],
dest: 'dist/origin-web-common.js'
}
},
......@@ -41,7 +41,36 @@ module.exports = function (grunt) {
browsers: ['PhantomJS']
}
},
// ng-annotate tries to make the code safe for minification automatically
less: {
production: {
files: {
'dist/origin-web-common.css': 'src/styles/main.less'
},
options: {
cleancss: true,
paths: ['src/styles', 'bower_components/']
}
}
},
htmlmin: {
dist: {
options: {
preserveLineBreaks: true,
collapseWhitespace: true,
conservativeCollapse: false,
collapseBooleanAttributes: false,
removeComments: true,
removeCommentsFromCDATA: true,
removeOptionalTags: false,
keepClosingSlash: true
},
files: [{
expand: true,
src: ['src/components/{,*/}*.html'],
dest: 'dist'
}]
}
}, // ng-annotate tries to make the code safe for minification automatically
// by using the Angular long form for dependency injection.
ngAnnotate: {
dist: {
......@@ -51,6 +80,17 @@ module.exports = function (grunt) {
}]
}
},
ngtemplates: {
dist: {
src: 'src/components/**/*.html',
dest: 'dist/scripts/templates.js',
options: {
module: 'openshiftCommonUI',
standalone: false,
htmlmin: ''
}
}
},
uglify: {
options: {
mangle: false
......@@ -78,16 +118,16 @@ module.exports = function (grunt) {
});
} else {
concatSrc = 'src/**/*.js';
concatSrc = ['src/**/*.module.js', 'src/**/*.js'];
}
grunt.task.run(['clean', 'concat', 'ngAnnotate', 'uglify:build', 'test']);
grunt.task.run(['clean', 'ngtemplates', 'concat', 'ngAnnotate', 'less', 'uglify:build', 'test']);
});
// Runs all the tasks of build with the exception of tests
grunt.registerTask('deploy', 'Prepares the project for deployment. Does not run unit tests', function () {
var concatSrc = 'src/**/*.js';
grunt.task.run(['clean', 'concat', 'ngAnnotate', 'uglify:build']);
grunt.task.run(['clean', 'ngtemplates', 'concat', 'ngAnnotate', 'less', 'uglify:build']);
});
grunt.registerTask('default', ['build']);
......
{
"name": "origin-web-comon",
"name": "origin-web-common",
"version": "0.0.5",
"main": [
"dist/origin-web-common.js"
"dist/origin-web-common.js",
"dist/origin-web-common.css"
],
"authors": [
"Red Hat"
......@@ -35,10 +36,14 @@
"jquery": "~2.1.4",
"lodash": "~3.10.1",
"messenger": "~1.4.1",
"patternfly": "~3.21.0",
"uri.js": "~1.18.0"
},
"devDependencies": {
"angular-mocks": "~1.5.11",
"angular-scenario": "~1.5.11"
},
"resolutions": {
"jquery": "~2.1.4"
}
}
.delete-resource-modal {
background-color: #f1f1f1;
}
.delete-resource-modal h1 {
font-size: 21px;
font-weight: 500;
margin-bottom: 20px;
word-wrap: break-word;
word-break: break-word;
overflow-wrap: break-word;
min-width: 0;
}
.delete-resource-modal p {
font-size: 15px;
}
.delete-resource-modal .help-block {
margin-top: 0px;
margin-bottom: 10px;
}
.dialog-btn {
float: right !important;
margin-right: 10px;
}
.dialog-btn:first-of-type {
margin-right: 0;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -19,6 +19,7 @@
"grunt-contrib-copy": "0.5.0",
"grunt-contrib-cssmin": "0.9.0",
"grunt-contrib-jshint": "0.7.0",
"grunt-contrib-less": "1.3.0",
"grunt-contrib-uglify": "0.2.5",
"grunt-contrib-watch": "0.5.3",
"grunt-eslint": "~17.1.0",
......
<form name="createProjectForm">
<fieldset ng-disabled="disableInputs">
<div class="form-group">
<label for="name" class="required">Name</label>
<span ng-class="{'has-error': (createProjectForm.name.$error.pattern && createProjectForm.name.$touched) || nameTaken}">
<input class="form-control input-lg"
name="name"
id="name"
placeholder="my-project"
type="text"
required
take-focus
minlength="2"
maxlength="63"
pattern="[a-z0-9]([-a-z0-9]*[a-z0-9])?"
aria-describedby="nameHelp"
ng-model="name"
ng-model-options="{ updateOn: 'default blur' }"
ng-change="nameTaken = false"
autocorrect="off"
autocapitalize="off"
spellcheck="false">
</span>
<div>
<span class="help-block">A unique name for the project.</span>
</div>
<div class="has-error">
<span id="nameHelp" class="help-block" ng-if="createProjectForm.name.$error.minlength && createProjectForm.name.$touched">
Name must have at least two characters.
</span>
</div>
<div class="has-error">
<span id="nameHelp" class="help-block" ng-if="createProjectForm.name.$error.pattern && createProjectForm.name.$touched">
Project names may only contain lower-case letters, numbers, and dashes.
They may not start or end with a dash.
</span>
</div>
<div class="has-error">
<span class="help-block" ng-if="nameTaken">
This name is already in use. Please choose a different name.
</span>
</div>
</div>
<div class="form-group">
<label for="displayName">Display Name</label>
<input class="form-control input-lg"
name="displayName"
id="displayName"
placeholder="My Project"
type="text"
ng-model="displayName">
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control input-lg"
name="description"
id="description"
placeholder="A short description."
ng-model="description"></textarea>
</div>
<div class="button-group">
<button type="submit"
class="btn btn-primary btn-lg"
ng-class="{'dialog-btn': isDialog}"
ng-click="createProject()"
ng-disabled="createProjectForm.$invalid || nameTaken || disableInputs"
value="">
{{submitButtonLabel}}
</button>
<button
class="btn btn-default btn-lg"
ng-class="{'dialog-btn': isDialog}"
back
ng-click="cancelCreateProject()">
Cancel
</button>
</div>
</fieldset>
</form>
"use strict";
angular.module("openshiftCommonUI")
.directive("createProject", function() {
return {
restrict: 'E',
scope: {
alerts: '=',
submitButtonLabel: '@',
redirectAction: '&',
onCancel: '&?',
isDialog: '@'
},
templateUrl: 'src/components/create-project/createProject.html',
controller: function($scope, $filter, $location, DataService) {
if(!($scope.submitButtonLabel)) {
$scope.submitButtonLabel = 'Create';
}
$scope.isDialog = $scope.isDialog === 'true';
$scope.createProject = function() {
$scope.disableInputs = true;
if ($scope.createProjectForm.$valid) {
DataService
.create('projectrequests', null, {
apiVersion: "v1",
kind: "ProjectRequest",
metadata: {
name: $scope.name
},
displayName: $scope.displayName,
description: $scope.description
}, $scope)
.then(function(data) {
// angular is actually wrapping the redirect action
var cb = $scope.redirectAction();
if (cb) {
cb(encodeURIComponent(data.metadata.name));
} else {
$location.path("project/" + encodeURIComponent(data.metadata.name) + "/create");
}
}, function(result) {
$scope.disableInputs = false;
var data = result.data || {};
if (data.reason === 'AlreadyExists') {
$scope.nameTaken = true;
} else {
var msg = data.message || 'An error occurred creating the project.';
$scope.alerts['error-creating-project'] = {type: 'error', message: msg};
}
});
}
};
$scope.cancelCreateProject = function() {
if ($scope.onCancel) {
var cb = $scope.onCancel();
if (cb) {
cb();
}
}
};
},
};
});
<div class="actions">
<!-- Avoid whitespace inside the link -->
<a href=""
ng-click="$event.stopPropagation(); openDeleteModal()"
role="button"
class="action-button"
ng-attr-aria-disabled="{{disableDelete ? 'true' : undefined}}"
ng-class="{ 'disabled-link': disableDelete }"
><i class="fa fa-trash-o" aria-hidden="true"
></i><span class="sr-only">Delete {{kind | humanizeKind}} {{resourceName}}</span></a>
</div>
<a href="javascript:void(0)"
ng-click="openDeleteModal()"
role="button"
ng-attr-aria-disabled="{{disableDelete ? 'true' : undefined}}"
ng-class="{ 'disabled-link': disableDelete }"
>{{label || 'Delete'}}</a>
<div class="delete-resource-modal">
<!-- Use a form so that the enter key submits when typing a project name to confirm. -->
<form>
<div class="modal-body">
<h1>Are you sure you want to delete the {{typeDisplayName || (kind | humanizeKind)}}
'<strong>{{displayName ? displayName : resourceName}}</strong>'?</h1>
<div ng-if="replicas" class="alert alert-warning">
<span class="pficon pficon-warning-triangle-o" aria-hidden="true"></span>
<span class="sr-only">Warning:</span>
<strong>{{resourceName}}</strong> has running pods. Deleting the
{{typeDisplayName || (kind | humanizeKind)}} will <strong>not</strong> delete the pods
it controls. Consider scaling the {{typeDisplayName || (kind | humanizeKind)}} down to
0 before continuing.
</div>
<p>This<span ng-if="isProject"> will <strong>delete all resources</strong> associated with
the project {{displayName ? displayName : resourceName}} and</span> <strong>cannot be
undone</strong>. Make sure this is something you really want to do!</p>
<div ng-show="typeNameToConfirm">
<p>Type the name of the {{typeDisplayName || (kind | humanizeKind)}} to confirm.</p>
<p>
<label class="sr-only" for="resource-to-delete">{{typeDisplayName || (kind | humanizeKind)}} to delete</label>
<input
ng-model="confirmName"
id="resource-to-delete"
type="text"
class="form-control input-lg"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
autofocus>
</p>
</div>
<div ng-switch="kind">
<div ng-switch-when="Deployment">
<strong>Note:</strong> None of the replica sets created by this deployment will be deleted.
To delete the deployment and all of its replica sets, you can run the command
<pre class="code prettyprint mar-top-md">oc delete deployment {{resourceName}} -n {{projectName}}</pre>
Learn more about the <a href="command-line">command line tools</a>.
</div>
<div ng-switch-when="DeploymentConfig">
<strong>Note:</strong> None of the deployments created by this deployment config will be deleted.
To delete the deployment config and all of its deployments, you can run the command
<pre class="code prettyprint mar-top-md">oc delete dc {{resourceName}} -n {{projectName}}</pre>
Learn more about the <a href="command-line">command line tools</a>.
</div>
<div ng-switch-when="BuildConfig">
<strong>Note:</strong> None of the builds created by this build config will be deleted.
To delete the build config and all of its builds, you can run the command
<pre class="code prettyprint mar-top-md">oc delete bc {{resourceName}} -n {{projectName}}</pre>
Learn more about the <a href="command-line">command line tools</a>.
</div>
</div>
<!--
If this is a deployment config or replication controller with associated HPAs, prompt to
delete the HPAs as well.
-->
<div ng-if="hpaList.length > 0">
<p>
<span ng-if="hpaList.length === 1">
This resource has an autoscaler associated with it.
It is recommended you delete the autoscaler with the resource it scales.
</span>
<span ng-if="hpaList.length > 1">
This resource has autoscalers associated with it.
It is recommended you delete the autoscalers with the resource they scale.
</span>
</p>
<label>
<input type="checkbox" ng-model="options.deleteHPAs">
Delete
<span ng-if="hpaList.length === 1">
Horizontal Pod Autoscaler '<strong>{{hpaList[0].metadata.name}}</strong>'
</span>
<span ng-if="hpaList.length > 1">
{{hpaList.length}} associated Horizontal Pod Autoscalers
</span>
</label>
</div>
</div>
<div class="modal-footer">
<button ng-disabled="typeNameToConfirm && confirmName !== resourceName && confirmName !== displayName" class="btn btn-lg btn-danger" type="submit" ng-click="delete();">Delete</button>
<button class="btn btn-lg btn-default" type="button" ng-click="cancel();">Cancel</button>
</div>
</form>
</div>
'use strict';
angular.module("openshiftCommonUI")
.directive("deleteLink", function ($uibModal, $location, $filter, $q, hashSizeFilter, APIService, DataService, AlertMessageService, Logger) {
return {
restrict: "E",
scope: {
// Resource Kind to delete (e.g., "Pod" or "ReplicationController").
kind: "@",
// Optional resource group.
group: "@?",
// Optional display name for kind.
typeDisplayName: "@?",
// Name of the resource to delete.
resourceName: "@",
// The name of the resource's project. Optional if kind === "Project".
projectName: "@",
// Alerts object for success and error alerts.
alerts: "=",
// Optional display name of the resource to delete.
displayName: "@",
// Set to true to disable the delete button.
disableDelete: "=?",
// Force the user to enter the name before we'll delete the resource (e.g. for projects).
typeNameToConfirm: "=?",
// Optional link label. Defaults to "Delete".
label: "@?",
// Only show a delete icon with no text.
buttonOnly: "@",
// Stay on the current page without redirecting to the resource list.
stayOnCurrentPage: "=?",
// Optional replica count for a resource like a ReplicationController or ReplicaSet to display a warning.
replicas: '=?',
// Array of associated HPAs for this resource. If set, prompts the user to delete the HPA resources as well.
hpaList: "=?",
// Optional callback when the delete succeeds
success: "=?",
// Optional redirect URL when the delete succeeds
redirectUrl: "@?"
},
templateUrl: function(elem, attr) {
if (angular.isDefined(attr.buttonOnly)) {
return "src/components/delete-link/delete-button.html";
}
return "src/components/delete-link/delete-link.html";
},
// Replace so ".dropdown-menu > li > a" styles are applied.
replace: true,
link: function(scope, element, attrs) {
if (attrs.kind === 'Project') {
scope.isProject = true;
}
// Checkbox value
scope.options = {
deleteHPAs: true
};
var showAlert = function(alert) {
if (scope.stayOnCurrentPage) {
scope.alerts[alert.name] = alert.data;
} else {
AlertMessageService.addAlert(alert);
}
};
var deleteHPA = function(hpa) {
return DataService.delete({
resource: 'horizontalpodautoscalers',
group: 'extensions'
}, hpa.metadata.name, { namespace: scope.projectName })
.then(function() {
showAlert({
name: hpa.metadata.name,
data: {
type: "success",
message: "Horizontal Pod Autoscaler " + hpa.metadata.name + " was marked for deletion."
}
});
})
.catch(function(err) {
showAlert({
name: hpa.metadata.name,
data: {
type: "error",
message: "Horizontal Pod Autoscaler " + hpa.metadata.name + " could not be deleted."
}
});
Logger.error("HPA " + hpa.metadata.name + " could not be deleted.", err);
});
};
var navigateToList = function() {
if (scope.stayOnCurrentPage) {
return;
}
if (scope.redirectUrl) {
$location.url(scope.redirectUrl);
return;
}
if (scope.kind !== 'Project') {
$location.url(this.resourceListURL(APIService.kindToResource(scope.kind), scope.projectName));
return;
}
if ($location.path() === '/') {
scope.$emit('deleteProject');
return;
}
var homeRedirect = URI('/');
$location.url(homeRedirect);
};
scope.openDeleteModal = function() {
if (scope.disableDelete) {
return;
}
// opening the modal with settings scope as parent
var modalInstance = $uibModal.open({
animation: true,
templateUrl: 'src/components/delete-link/delete-resource.html',
controller: 'DeleteModalController',
scope: scope
});
modalInstance.result.then(function() {
// upon clicking delete button, delete resource and send alert
var kind = scope.kind;
var resourceName = scope.resourceName;
var typeDisplayName = scope.typeDisplayName || $filter('humanizeKind')(kind);
var formattedResource = typeDisplayName + ' ' + "\'" + (scope.displayName ? scope.displayName : resourceName) + "\'";
var context = (scope.kind === 'Project') ? {} : {namespace: scope.projectName};
DataService.delete({
resource: APIService.kindToResource(kind),
// group or undefined
group: scope.group
}, resourceName, context)
.then(function() {
showAlert({
name: resourceName,
data: {
type: "success",
message: _.capitalize(formattedResource) + " was marked for deletion."
}
});
if (scope.success) {
scope.success();
}
// Delete any associated HPAs if requested.
var promises = [];
if (scope.options.deleteHPAs) {
_.forEach(scope.hpaList, function(hpa) {
promises.push(deleteHPA(hpa));
});
}
if (!promises.length) {
navigateToList();
} else {
// Wait until all promises resolve so that we can add alerts to
// AlertMessageService before navigating, otherwise they aren't
// displayed.
$q.all(promises).then(navigateToList);
}
})
.catch(function(err) {
// called if failure to delete
scope.alerts[resourceName] = {
type: "error",
message: _.capitalize(formattedResource) + "\'" + " could not be deleted.",
details: $filter('getErrorDetails')(err)
};
Logger.error(formattedResource + " could not be deleted.", err);
});
});
};
}
};
});
'use strict';
/* jshint unused: false */
/**
* @ngdoc function
* @name openshifgCommonUI.controller:DeleteModalController
*/
angular.module('openshiftCommonUI')
.controller('DeleteModalController', function ($scope, $uibModalInstance) {
$scope.delete = function() {
$uibModalInstance.close('delete');
};
$scope.cancel = function() {
$uibModalInstance.dismiss('cancel');
};
});
<form name="editProjectForm">
<fieldset ng-disabled="disableInputs">
<div class="form-group">
<label for="displayName">Display Name</label>
<input class="form-control input-lg"
name="displayName"
id="displayName"
placeholder="My Project"
type="text"
ng-model="editableFields.displayName">
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea class="form-control input-lg"
name="description"
id="description"
placeholder="A short description."
ng-model="editableFields.description"></textarea>
</div>
<div class="button-group">
<button type="submit"
class="btn btn-primary btn-lg"
ng-class="{'dialog-btn': isDialog}"
ng-click="update()"
ng-disabled="editProjectForm.$invalid || disableInputs"
value="">{{submitButtonLabel}}</button>
<button
class="btn btn-default btn-lg"
ng-class="{'dialog-btn': isDialog}"
back
ng-click="cancelEditProject()">
Cancel
</button>
</div>
</fieldset>
</form>
"use strict";
angular.module("openshiftCommonUI")
.directive("editProject", function() {
return {
restrict: 'E',
scope: {
project: '=',
alerts: '=',
submitButtonLabel: '@',
redirectAction: '&',
onCancel: '&',
isDialog: '@'
},
templateUrl: 'src/components/edit-project/editProject.html',
controller: function($scope, $filter, $location, DataService, annotationNameFilter) {
if(!($scope.submitButtonLabel)) {
$scope.submitButtonLabel = 'Save';
}
$scope.isDialog = $scope.isDialog === 'true';
var annotation = $filter('annotation');
var annotationName = $filter('annotationName');
var editableFields = function(resource) {
return {
description: annotation(resource, 'description'),
displayName: annotation(resource, 'displayName')
};
};
var mergeEditable = function(project, editable) {
var toSubmit = angular.copy(project);
toSubmit.metadata.annotations[annotationName('description')] = editable.description;
toSubmit.metadata.annotations[annotationName('displayName')] = editable.displayName;
return toSubmit;
};
var cleanEditableAnnotations = function(resource) {
var paths = [
annotationNameFilter('description'),
annotationNameFilter('displayName')
];
_.each(paths, function(path) {
if(!resource.metadata.annotations[path]) {
delete resource.metadata.annotations[path];
}
});
return resource;
};
$scope.editableFields = editableFields($scope.project);
$scope.update = function() {
$scope.disableInputs = true;
if ($scope.editProjectForm.$valid) {
DataService
.update(
'projects',
$scope.project.metadata.name,
cleanEditableAnnotations(mergeEditable($scope.project, $scope.editableFields)),
{projectName: $scope.project.name},
{errorNotification: false})
.then(function() {
// angular is actually wrapping the redirect action :/
var cb = $scope.redirectAction();
if (cb) {
cb(encodeURIComponent($scope.project.metadata.name));
}
}, function(result) {
$scope.disableInputs = false;
$scope.editableFields = editableFields($scope.project);
$scope.alerts["update"] = {
type: "error",
message: "An error occurred while updating the project",
details: $filter('getErrorDetails')(result)
};
});
}
};
$scope.cancelEditProject = function() {
var cb = $scope.onCancel();
if (cb) {
cb();
}
};
},
};
});
'use strict';
angular.module('openshiftCommonUI')
// The HTML5 `autofocus` attribute does not work reliably with Angular,
// so define our own directive
.directive('takeFocus', function($timeout) {
return {
restrict: 'A',
link: function(scope, element) {
// Add a delay to allow other asynchronous components to load.
$timeout(function() {
$(element).focus();
}, 300);
}
};
});
'use strict';
/* jshint unused: false */
angular.module('openshiftCommonUI')
.filter('annotationName', function() {
// This maps an annotation key to all known synonymous keys to insulate
// the referring code from key renames across API versions.
var annotationMap = {
"buildConfig": ["openshift.io/build-config.name"],
"deploymentConfig": ["openshift.io/deployment-config.name"],
"deployment": ["openshift.io/deployment.name"],
"pod": ["openshift.io/deployer-pod.name"],
"deployerPod": ["openshift.io/deployer-pod.name"],
"deployerPodFor": ["openshift.io/deployer-pod-for.name"],
"deploymentStatus": ["openshift.io/deployment.phase"],
"deploymentStatusReason": ["openshift.io/deployment.status-reason"],
"deploymentCancelled": ["openshift.io/deployment.cancelled"],
"encodedDeploymentConfig": ["openshift.io/encoded-deployment-config"],
"deploymentVersion": ["openshift.io/deployment-config.latest-version"],
"displayName": ["openshift.io/display-name"],
"description": ["openshift.io/description"],
"buildNumber": ["openshift.io/build.number"],
"buildPod": ["openshift.io/build.pod-name"],
"jenkinsBuildURL": ["openshift.io/jenkins-build-uri"],
"jenkinsLogURL": ["openshift.io/jenkins-log-url"],
"jenkinsStatus": ["openshift.io/jenkins-status-json"],
"idledAt": ["idling.alpha.openshift.io/idled-at"],
"idledPreviousScale": ["idling.alpha.openshift.io/previous-scale"],
"systemOnly": ["authorization.openshift.io/system-only"]
};
return function(annotationKey) {
return annotationMap[annotationKey] || null;
};
})
.filter('annotation', function(annotationNameFilter) {
return function(resource, key) {
if (resource && resource.metadata && resource.metadata.annotations) {
// If the key's already in the annotation map, return it.
if (resource.metadata.annotations[key] !== undefined) {
return resource.metadata.annotations[key];
}
// Try and return a value for a mapped key.
var mappings = annotationNameFilter(key) || [];
for (var i=0; i < mappings.length; i++) {
var mappedKey = mappings[i];
if (resource.metadata.annotations[mappedKey] !== undefined) {
return resource.metadata.annotations[mappedKey];
}
}
// Couldn't find anything.
return null;
}
return null;
};
});
'use strict';
angular
.module('openshiftCommonUI')
.filter('canI', function(AuthorizationService) {
return function(resource, verb, projectName) {
return AuthorizationService.canI(resource, verb, projectName);
};
})
.filter('canIAddToProject', function(AuthorizationService) {
return function(namespace) {
return AuthorizationService.canIAddToProject(namespace);
};
});
'use strict';
angular.module('openshiftCommonUI')
.filter('orderObjectsByDate', function(toArrayFilter) {
return function(items, reverse) {
items = toArrayFilter(items);
/*
* Note: This is a hotspot in our code. We sort frequently by date on
* the overview and browse pages.
*/
items.sort(function (a, b) {
if (!a.metadata || !a.metadata.creationTimestamp || !b.metadata || !b.metadata.creationTimestamp) {
throw "orderObjectsByDate expects all objects to have the field metadata.creationTimestamp";
}
// The date format can be sorted using straight string comparison.
// Compare as strings for performance.
// Example Date: 2016-02-02T21:53:07Z
if (a.metadata.creationTimestamp < b.metadata.creationTimestamp) {
return reverse ? 1 : -1;
}
if (a.metadata.creationTimestamp > b.metadata.creationTimestamp) {
return reverse ? -1 : 1;
}
return 0;
});
return items;
};
});
'use strict';
/* jshint unused: false */
angular.module('openshiftCommonUI')
// this filter is intended for use with the "track by" in an ng-repeat
// when uid is not defined it falls back to object identity for uniqueness
.filter('uid', function() {
return function(resource) {
if (resource && resource.metadata && resource.metadata.uid) {
return resource.metadata.uid;
}
else {
return resource;
}
};
})
.filter('labelName', function() {
var labelMap = {
'buildConfig' : ["openshift.io/build-config.name"],
'deploymentConfig' : ["openshift.io/deployment-config.name"]
};
return function(labelKey) {
return labelMap[labelKey];
};
})
.filter('description', function(annotationFilter) {
return function(resource) {
// Prefer `openshift.io/description`, but fall back to `kubernetes.io/description`.
return annotationFilter(resource, 'openshift.io/description') ||
annotationFilter(resource, 'kubernetes.io/description');
};
})
.filter('displayName', function(annotationFilter) {
// annotationOnly - if true, don't fall back to using metadata.name when
// there's no displayName annotation
return function(resource, annotationOnly) {
var displayName = annotationFilter(resource, "displayName");
if (displayName || annotationOnly) {
return displayName;
}
if (resource && resource.metadata) {
return resource.metadata.name;
}
return null;
};
})
.filter('uniqueDisplayName', function(displayNameFilter){
function countNames(projects){
var nameCount = {};
angular.forEach(projects, function(project, key){
var displayName = displayNameFilter(project);
nameCount[displayName] = (nameCount[displayName] || 0) + 1;
});
return nameCount;
}
return function (resource, projects){
if (!resource) {
return '';
}
var displayName = displayNameFilter(resource);
var name = resource.metadata.name;
if (displayName !== name && countNames(projects)[displayName] > 1 ){
return displayName + ' (' + name + ')';
}
return displayName;
};
})
.filter('label', function() {
return function(resource, key) {
if (resource && resource.metadata && resource.metadata.labels) {
return resource.metadata.labels[key];
}
return null;
};
})
.filter('humanizeKind', function (startCaseFilter) {
// Changes "ReplicationController" to "replication controller".
// If useTitleCase, returns "Replication Controller".
return function(kind, useTitleCase) {
if (!kind) {
return kind;
}
var humanized = _.startCase(kind);
if (useTitleCase) {
return humanized;
}
return humanized.toLowerCase();
};
});
'use strict';
angular.module('openshiftCommonUI')
.filter('camelToLower', function() {
return function(str) {
if (!str) {
return str;
}
// Use the special logic in _.startCase to handle camel case strings, kebab
// case strings, snake case strings, etc.
return _.startCase(str).toLowerCase();
};
})
.filter('upperFirst', function() {
// Uppercase the first letter of a string (without making any other changes).
// Different than `capitalize` because it doesn't lowercase other letters.
return function(str) {
if (!str) {
return str;
}
return str.charAt(0).toUpperCase() + str.slice(1);
};
})
.filter('sentenceCase', function(camelToLowerFilter, upperFirstFilter) {
// Converts a camel case string to sentence case
return function(str) {
if (!str) {
return str;
}
// Unfortunately, _.lowerCase() and _.upperFirst() aren't in our lodash version.
var lower = camelToLowerFilter(str);
return upperFirstFilter(lower);
};
})
.filter('startCase', function () {
return function(str) {
if (!str) {
return str;
}
// https://lodash.com/docs#startCase
return _.startCase(str);
};
})
.filter('capitalize', function() {
return function(input) {
return _.capitalize(input);
};
})
.filter('isMultiline', function() {
return function(str, ignoreTrailing) {
if (!str) {
return false;
}
var index = str.search(/\r|\n/);
if (index === -1) {
return false;
}
// Ignore a final, trailing newline?
if (ignoreTrailing) {
return index !== (str.length - 1);
}
return true;
};
});
'use strict';
angular.module('openshiftCommonUI')
.filter("toArray", function() {
return function (items) {
if (!items) {
return [];
}
if (angular.isArray(items)) {
return items;
}
var itemsArray = [];
angular.forEach(items, function (item) {
itemsArray.push(item);
});
return itemsArray;
};
})
.filter('hashSize', function() {
return function(hash) {
if(!hash) { return 0; }
return Object.keys(hash).length;
};
});
/**
* @name openshiftCommon
* @name openshiftCommonServices
*
* @description
* Base module for openshiftCommon.
* Base module for openshiftCommonServices.
*/
angular.module('openshiftCommon', ['ab-base64'])
angular.module('openshiftCommonServices', ['ab-base64'])
.config(function(AuthServiceProvider) {
AuthServiceProvider.UserStore('MemoryUserStore');
})
......@@ -24,7 +24,7 @@ angular.module('openshiftCommon', ['ab-base64'])
RedirectLoginServiceProvider.OAuthRedirectURI(URI(AUTH_CFG.oauth_redirect_base).segment("oauth").toString());
});
hawtioPluginLoader.addModule('openshiftCommon');
hawtioPluginLoader.addModule('openshiftCommonServices');
// API Discovery, this runs before the angular app is bootstrapped
// TODO we want this to be possible with a single request against the API instead of being dependent on the numbers of groups and versions
......
/**
* @name openshiftCommonUI
*
* @description
* Base module for openshiftCommonUI.
*/
angular.module('openshiftCommonUI', []);
hawtioPluginLoader.addModule('openshiftCommonUI');
'use strict';
angular.module("openshiftCommonServices")
.service("AlertMessageService", function(){
var alerts = [];
var alertHiddenKey = function(alertID, namespace) {
if (!namespace) {
return 'hide/alert/' + alertID;
}
return 'hide/alert/' + namespace + '/' + alertID;
};
return {
addAlert: function(alert) {
alerts.push(alert);
},
getAlerts: function() {
return alerts;
},
clearAlerts: function() {
alerts = [];
},
isAlertPermanentlyHidden: function(alertID, namespace) {
var key = alertHiddenKey(alertID, namespace);
return localStorage.getItem(key) === 'true';
},
permanentlyHideAlert: function(alertID, namespace) {
var key = alertHiddenKey(alertID,namespace);
localStorage.setItem(key, 'true');
}
};
});
......@@ -38,7 +38,7 @@ ResourceGroupVersion.prototype.equals = function(resource, group, version) {
return true;
};
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.factory('APIService', function(API_CFG,
APIS_CFG,
AuthService,
......
'use strict';
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
// In a config step, set the desired user store and login service. For example:
// AuthServiceProvider.setUserStore('LocalStorageUserStore')
// AuthServiceProvider.setLoginService('RedirectLoginService')
......
'use strict';
angular.module("openshiftCommon")
angular.module("openshiftCommonServices")
.factory("AuthorizationService", function($q, $cacheFactory, Logger, $interval, APIService, DataService){
var currentProject = null;
......
'use strict';
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.factory('base64util', function() {
return {
pad: function(data){
......
'use strict';
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.factory('Constants', function() {
var constants = _.clone(window.OPENSHIFT_CONSTANTS || {});
var version = _.clone(window.OPENSHIFT_VERSION || {});
......
'use strict';
/* jshint eqeqeq: false, unused: false, expr: true */
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.factory('DataService', function($cacheFactory, $http, $ws, $rootScope, $q, API_CFG, APIService, Notification, Logger, $timeout, base64, base64util) {
function Data(array) {
......
'use strict';
// Logout strategies
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.provider('DeleteTokenLogoutService', function() {
this.$get = function($q, $injector, Logger) {
......
'use strict';
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.provider('Logger', function() {
this.$get = function() {
// Wraps the global Logger from https://github.com/jonnyreeves/js-logger
......
......@@ -2,7 +2,7 @@
/* jshint unused: false */
// UserStore objects able to remember user and tokens for the current user
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.provider('MemoryUserStore', function() {
this.$get = function(Logger){
var authLogger = Logger.get("auth");
......
'use strict';
/* jshint unused: false */
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.factory('Notification', function($rootScope) {
function Notification() {
this.messenger = Messenger({
......
'use strict';
angular.module('openshiftCommonServices')
.factory('ProjectsService',
function($location, $q, AuthService, DataService, annotationNameFilter, AuthorizationService) {
var cleanEditableAnnotations = function(resource) {
var paths = [
annotationNameFilter('description'),
annotationNameFilter('displayName')
];
_.each(paths, function(path) {
if(!resource.metadata.annotations[path]) {
delete resource.metadata.annotations[path];
}
});
return resource;
};
return {
get: function(projectName) {
return AuthService
.withUser()
.then(function() {
var context = {
// TODO: swap $.Deferred() for $q.defer()
projectPromise: $.Deferred(),
projectName: projectName,
project: undefined
};
return DataService
.get('projects', projectName, context, {errorNotification: false})
.then(function(project) {
return AuthorizationService
.getProjectRules(projectName)
.then(function() {
context.project = project;
context.projectPromise.resolve(project);
// TODO: fix need to return context & projectPromise
return [project, context];
});
}, function(e) {
context.projectPromise.reject(e);
var description = 'The project could not be loaded.';
var type = 'error';
if(e.status === 403) {
description = 'The project ' + context.projectName + ' does not exist or you are not authorized to view it.';
type = 'access_denied';
} else if (e.status === 404) {
description = 'The project ' + context.projectName + ' does not exist.';
type = 'not_found';
}
$location
.url(
URI('error')
.query({
"error" : type,
"error_description": description
})
.toString());
});
});
},
update: function(projectName, data) {
return DataService
.update('projects', projectName, cleanEditableAnnotations(data), {projectName: projectName}, {errorNotification: false});
},
canCreate: function() {
return DataService.get("projectrequests", null, {}, { errorNotification: false});
}
};
});
'use strict';
// Login strategies
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.provider('RedirectLoginService', function() {
var _oauth_client_id = "";
var _oauth_authorize_uri = "";
......
......@@ -10,7 +10,7 @@
//
// $ws.available()
// returns true if WebSockets are available to use
angular.module('openshiftCommon')
angular.module('openshiftCommonServices')
.provider('$ws', function($httpProvider) {
// $get method is called to build the $ws service
......
.delete-resource-modal {
background-color: @gray-lighter;
h1 {
font-size: 21px;
font-weight: 500;
margin-bottom: 20px;
word-wrap: break-word; // firefox, IE
word-break: break-word; // non-standard for webkit
overflow-wrap: break-word; // new name as per the CSS3 spec
min-width: 0; // firefox (1)
}
p {
font-size: @font-size-large + 1;
}
.help-block {
margin-top: 0px;
margin-bottom: 10px;
}
}
.dialog-btn {
float: right !important;
margin-right: 10px;
&:first-of-type {
margin-right: 0;
}
}
@bower-components-path: "bower_components";
@pf-less-path: "@{bower-components-path}/patternfly/dist/less";
// PatternFly
@import "@{pf-less-path}/color-variables.less";
@import "@{pf-less-path}/variables.less";
@import "_core.less";
......@@ -31,6 +31,9 @@ module.exports = function(config) {
"bower_components/hawtio-core/dist/hawtio-core.js",
"bower_components/kubernetes-container-terminal/dist/container-terminal.js",
"bower_components/hawtio-extension-service/dist/hawtio-extension-service.js",
'src/config.js',
'src/**/*module.js',
'dist/scripts/templates.js',
'src/**/*.js',
'test/spec/spec-helper.js',
'test/spec/fixtures/api-discovery.js',
......
"use strict";
//load the module
beforeEach(module('openshiftCommon'));
beforeEach(module('openshiftCommonServices', 'openshiftCommonUI'));
beforeEach(module(function ($provide) {
$provide.provider("HawtioNavBuilder", function() {
......@@ -34,7 +34,7 @@ beforeEach(module(function ($provide) {
$('head').append($('<base href="/">'));
}
angular.module('openshiftCommon').config(function(AuthServiceProvider) {
angular.module('openshiftCommonServices').config(function(AuthServiceProvider) {
AuthServiceProvider.UserStore('MemoryUserStore');
})
.constant("API_CFG", _.get(window.OPENSHIFT_CONFIG, "api", {}))
......@@ -45,6 +45,6 @@ beforeEach(module(function ($provide) {
AuthServiceProvider.LogoutService('DeleteTokenLogoutService');
AuthServiceProvider.UserStore('LocalStorageUserStore');
});
hawtioPluginLoader.addModule('openshiftCommon');
hawtioPluginLoader.addModule('openshiftCommonServices');
}));
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment