No subscription is associated to this plan. You can delete it safely.
-
There are {{subscriptions}} active subscriptions associated to this plan.
+
+
No subscription is associated to this plan. You can delete it safely.
+
There are {{subscriptions}} active subscription(s) associated to this plan.
- By closing this plan, all relative subscriptions will also be closed and associated api-keys will be no longer
- available.
+ By closing this plan, all relative subscriptions will also be closed
+ and associated api-keys will be no longer available.
-
+
Are you sure to close the plan {{plan.name}}?
diff --git a/src/management/api/plans/closePlanDialog.controller.ts b/src/management/api/plans/closePlanDialog.controller.ts
index 8763428ec5..34845470da 100644
--- a/src/management/api/plans/closePlanDialog.controller.ts
+++ b/src/management/api/plans/closePlanDialog.controller.ts
@@ -25,7 +25,7 @@ function DialogClosePlanController($scope, $rootScope, $mdDialog, ApiService, No
};
$scope.close = function () {
- if ($scope.plan.security === 'api_key' && $scope.subscriptions === 0) {
+ if ($scope.plan.security !== 'key_less' && $scope.subscriptions === 0) {
ApiService.deletePlan($scope.apiId, $scope.plan.id).then(function() {
NotificationService.show('Plan ' + plan.name + ' has been deleted');
}).catch(function (error) {
diff --git a/src/management/api/plans/list-plans.component.ts b/src/management/api/plans/list-plans.component.ts
new file mode 100644
index 0000000000..94085d0e52
--- /dev/null
+++ b/src/management/api/plans/list-plans.component.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const ApiListPlansComponent: ng.IComponentOptions = {
+ bindings: {
+ plans: '<'
+ },
+ controller: 'ApiListPlansController',
+ template: require('./list-plans.html')
+};
+
+export default ApiListPlansComponent;
diff --git a/src/management/api/plans/list-plans.controller.ts b/src/management/api/plans/list-plans.controller.ts
new file mode 100644
index 0000000000..8a820233ad
--- /dev/null
+++ b/src/management/api/plans/list-plans.controller.ts
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import _ = require('lodash');
+import angular = require('angular');
+import UserService from '../../../services/user.service';
+import ApiService from "../../../services/api.service";
+import NotificationService from "../../../services/notification.service";
+
+class ApiListPlansController {
+ private api: any;
+ private plans: any;
+ private dndEnabled: boolean;
+ private statusFilters: string[];
+ private selectedStatus: string[];
+ private countByStatus: any;
+ private filteredPlans: any;
+
+ constructor(
+ private $scope: ng.IScope,
+ private $rootScope: ng.IRootScopeService,
+ private $state: ng.ui.IStateService,
+ private $stateParams: ng.ui.IStateParamsService,
+ private $mdDialog: angular.material.IDialogService,
+ private dragularService,
+ private NotificationService: NotificationService,
+ private UserService: UserService,
+ private ApiService: ApiService
+ ) {
+ 'ngInject';
+ this.dndEnabled = UserService.isUserHasPermissions(['api-plan-u']);
+ this.statusFilters = ['staging', 'published', 'closed'];
+ this.selectedStatus = ['published'];
+
+ this.countByStatus = {};
+
+/*
+ var that = this;
+ $scope.configure = function (plan) {
+ that.resetPlan();
+ if (!$mdSidenav('plan-edit').isOpen()) {
+ $scope.plan = plan;
+ if (plan['excluded_groups']) {
+ $scope.plan.authorizedGroups = _.difference(_.map(that.groups, "id"), plan["excluded_groups"]);
+ } else {
+ $scope.plan.authorizedGroups = _.map(that.groups, "id");
+ }
+ if ($scope.plan.paths['/']) {
+ _.forEach($scope.plan.paths['/'], function (path) {
+ if (path['rate-limit']) {
+ $scope.rateLimit = path['rate-limit'].rate;
+ }
+ if (path['quota']) {
+ $scope.quota = path['quota'].quota;
+ }
+ if (path['resource-filtering']) {
+ $scope.resourceFiltering.whitelist = path['resource-filtering'].whitelist;
+ }
+ });
+ }
+ $mdSidenav('live-preview').toggle();
+ $mdSidenav('plan-edit').toggle();
+ }
+ };
+
+ $scope.rateLimitTimeUnits = ['SECONDS', 'MINUTES'];
+ $scope.quotaTimeUnits = ['HOURS', 'DAYS', "WEEKS", "MONTHS"];
+
+ $scope.methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS', 'TRACE', 'CONNECT'];
+
+ this.resetResourceFiltering();
+
+ $scope.$watch('livePreviewIsOpen', function (livePreviewIsOpen) {
+ if (livePreviewIsOpen === false && $mdSidenav('plan-edit').isOpen()) {
+ $mdSidenav('plan-edit').toggle();
+ }
+ });
+
+ $scope.$on('planChangeSuccess', function(event, args) {
+ that.changeFilter(args.state);
+ });
+ */
+ }
+
+ $onInit() {
+ /*
+ if (this.api.visibility === "private") {
+ if (this.api.groups) {
+ const apiGroupIds = this.api.groups;
+ this.groups = _.filter(this.groups, (group) => {
+ return apiGroupIds.indexOf(group["id"]) > -1;
+ });
+ } else {
+ this.groups = [];
+ }
+ }
+ */
+
+ let that = this;
+
+ let d = document.querySelector('.plans');
+ this.dragularService([d], {
+ moves: function () {
+ return that.dndEnabled;
+ },
+ scope: this.$scope,
+ containersModel: this.plans,
+ nameSpace: 'plan'
+ });
+
+ this.$scope.$on('dragulardrop', function(e, el, target, source, dragularList, index, targetModel, dropIndex) {
+ let movedPlan = that.filteredPlans[index];
+ movedPlan.order = dropIndex+1;
+
+ that.ApiService.savePlan(that.$stateParams.apiId, movedPlan).then(function () {
+ // sync list from server because orders has been changed
+ that.list();
+ that.NotificationService.show('Plans have been reordered successfully');
+ });
+ });
+
+ if (this.$stateParams.state) {
+ if (_.includes(this.statusFilters, this.$stateParams.state)) {
+ this.changeFilter(this.$stateParams.state);
+ } else {
+ this.applyFilters();
+ }
+ } else {
+ this.applyFilters();
+ }
+ }
+
+ list() {
+ this.ApiService.getApiPlans(this.$stateParams.apiId).then(response => {
+ let that = this;
+ this.$scope.$applyAsync(function(){
+ that.plans.length = 0;
+ Array.prototype.push.apply(that.plans, response.data);
+
+ that.applyFilters();
+ });
+ });
+ }
+
+ changeFilter(statusFilter) {
+ this.selectedStatus = statusFilter;
+ this.dndEnabled = (statusFilter === 'published');
+
+ if (_.includes(this.selectedStatus, statusFilter)) {
+ _.pull(this.selectedStatus, statusFilter);
+ } else {
+ this.selectedStatus.push(statusFilter);
+ }
+ this.$state.transitionTo(
+ this.$state.current,
+ _.merge(this.$state.params, {
+ state: statusFilter
+ }));
+ this.applyFilters();
+ }
+
+ applyFilters() {
+ this.countPlansByStatus();
+ var that = this;
+ this.filteredPlans = _.sortBy(
+ _.filter(this.plans, function (plan: any) {
+ return _.includes(that.selectedStatus, plan.status);
+ }), "order");
+ }
+
+ addPlan() {
+ this.$state.go('management.apis.detail.plans.new');
+ }
+
+ editPlan(planId: string) {
+ this.$state.go('management.apis.detail.plans.plan', {planId: planId});
+ }
+
+ /*
+ save() {
+ var that = this;
+
+ this.$scope.plan.paths = {
+ '/': []
+ };
+ // set resource filtering whitelist
+ _.remove(this.$scope.resourceFiltering.whitelist, function (whitelistItem: any) {
+ return !whitelistItem.pattern;
+ });
+ if (this.$scope.resourceFiltering.whitelist.length) {
+ that.$scope.plan.paths['/'].push({
+ 'methods': that.$scope.methods,
+ 'resource-filtering': {
+ 'whitelist': this.$scope.resourceFiltering.whitelist
+ }
+ });
+ }
+ // set rate limit policy
+ if (this.$scope.rateLimit && this.$scope.rateLimit.limit) {
+ this.$scope.plan.paths['/'].push({
+ 'methods': this.$scope.methods,
+ 'rate-limit': {
+ 'rate': this.$scope.rateLimit
+ }
+ });
+ }
+ // set quota policy
+ if (this.$scope.quota && this.$scope.quota.limit) {
+ this.$scope.plan.paths['/'].push({
+ 'methods': this.$scope.methods,
+ 'quota': {
+ 'quota': this.$scope.quota,
+ 'addHeaders': true
+ }
+ });
+ }
+ // convert authorized groups to excludedGroups
+ that.$scope.plan.excludedGroups = [];
+ if (that.groups) {
+ that.$scope.plan.excludedGroups = _.difference(_.map(that.groups, "id"), that.$scope.plan.authorizedGroups);
+ }
+
+ that.ApiService.savePlan(that.$stateParams.apiId, that.$scope.plan).then(function (response) {
+ var createMode = !that.planAlreadyCreated(response.data.id);
+ that.$scope.$parent.apiCtrl.checkAPISynchronization({id: that.$stateParams.apiId});
+ that.NotificationService.show('The plan ' + that.$scope.plan.name + ' has been saved with success');
+ that.ApiService.getApiPlans(that.$stateParams.apiId).then(function (response) {
+ that.plans = response.data;
+ that.$mdSidenav('plan-edit').toggle();
+ that.$mdSidenav('live-preview').toggle();
+ if (createMode) {
+ that.changeFilter('staging');
+ } else {
+ that.applyFilters();
+ }
+ });
+ });
+ }
+
+ planAlreadyCreated(planId) {
+ return _.some(this.plans, function (plan: any) {
+ return plan.id === planId;
+ });
+ }
+ */
+
+ close(plan, ev) {
+ this.ApiService.getPlanSubscriptions(this.$stateParams.apiId, plan.id).then( (response) => {
+ this.$mdDialog.show({
+ controller: 'DialogClosePlanController',
+ template: require('./closePlan.dialog.html'),
+ parent: angular.element(document.body),
+ targetEvent: ev,
+ clickOutsideToClose: true,
+ locals: {
+ apiId: this.$stateParams.apiId,
+ plan: plan,
+ subscriptions: response.data.page.size
+ }
+ }).then((plan) => {
+ if (plan) {
+ this.$scope.$parent.apiCtrl.checkAPISynchronization({id: this.$stateParams.apiId});
+ this.selectedStatus = ['closed'];
+ this.list();
+ }
+ }, function() {
+ // You cancelled the dialog
+ });
+ });
+ }
+
+ publish(plan, ev) {
+ this.$mdDialog.show({
+ controller: 'DialogPublishPlanController',
+ template: require('./publishPlan.dialog.html'),
+ parent: angular.element(document.body),
+ targetEvent: ev,
+ clickOutsideToClose: true,
+ locals: {
+ plan: plan
+ }
+ }).then( (plan) => {
+ if (plan) {
+ this.ApiService.publishPlan(this.$stateParams.apiId, plan.id).then( () => {
+ this.NotificationService.show('Plan ' + plan.name + ' has been published');
+ this.$rootScope.$broadcast("planChangeSuccess", { state: "published"});
+ this.$scope.$parent.apiCtrl.checkAPISynchronization({id: this.$stateParams.apiId});
+ this.selectedStatus = ['published'];
+ this.list();
+ }).catch( (error) => {
+ this.NotificationService.showError(error);
+ });
+ }
+ }, function() {
+ // You cancelled the dialog
+ });
+ }
+
+ countPlansByStatus() {
+ this.countByStatus = _.countBy(this.plans, 'status');
+ }
+
+}
+
+export default ApiListPlansController;
diff --git a/src/management/api/plans/list-plans.html b/src/management/api/plans/list-plans.html
new file mode 100644
index 0000000000..dbdab28bda
--- /dev/null
+++ b/src/management/api/plans/list-plans.html
@@ -0,0 +1,111 @@
+
+
+
+ Plans
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{plan.name}}
+
+ Authentication: {{plan.security}}
+
+
+
+
+
+ Configure plan
+
+
+
+
+
+
+ Publish plan
+
+
+
+
+
+
+ Close plan
+
+
+
+
+
+
+
+
+
{{plan.description}}
+
+
+
{{characteristic}}
+
+
+
+
+
+
+
+ {{plan.alreadySubscribed ? 'Subscribed' : (plan.validation === 'auto'?'Subscribe': 'Request for subscription')}}
+
+
+ No subscription required
+
+
+
+
+
+
+
+ Plans can be re-ordered by dragging & dropping them.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/management/api/plans/plan/edit-plan.component.ts b/src/management/api/plans/plan/edit-plan.component.ts
new file mode 100644
index 0000000000..097bf82291
--- /dev/null
+++ b/src/management/api/plans/plan/edit-plan.component.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const ApiEditPlanComponent: ng.IComponentOptions = {
+ bindings: {
+ plan: '<'
+ },
+ controller: 'ApiEditPlanController',
+ template: require('./edit-plan.html')
+};
+
+export default ApiEditPlanComponent;
diff --git a/src/management/api/plans/plan/edit-plan.controller.ts b/src/management/api/plans/plan/edit-plan.controller.ts
new file mode 100644
index 0000000000..1e21348a10
--- /dev/null
+++ b/src/management/api/plans/plan/edit-plan.controller.ts
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import _ = require('lodash');
+import angular = require('angular');
+import ApiService from "../../../../services/api.service";
+import NotificationService from "../../../../services/notification.service";
+
+class ApiEditPlanController {
+
+ plan: any;
+
+ vm: {
+ selectedStep: number;
+ stepProgress: number;
+ maxStep: number;
+ showBusyText: boolean;
+ stepData: {
+ step: number;
+ label?: string;
+ completed: boolean;
+ optional: boolean;
+ data: any
+ }[]
+ };
+
+ constructor(
+ private $state: ng.ui.IStateService,
+ private $stateParams: ng.ui.IStateParamsService,
+ private $timeout: ng.ITimeoutService,
+ private ApiService: ApiService,
+ private NotificationService: NotificationService
+ ) {
+ 'ngInject';
+
+ this.vm = {
+ selectedStep: 0,
+ stepProgress: 1,
+ maxStep: 4,
+ showBusyText: false,
+ stepData: [
+ {step: 1, completed: false, optional: false, data: {}},
+ {step: 2, completed: false, optional: false, data: {}},
+ {step: 3, completed: false, optional: true, data: {}},
+ {step: 4, completed: false, optional: true, data: {}}
+ ]
+ };
+ }
+
+ $onInit() {
+ if (!this.plan) {
+ this.plan = {characteristics: []};
+ }
+ }
+
+ moveToNextStep(step: any) {
+ this.submitCurrentStep(step);
+ }
+
+ moveToPreviousStep() {
+ if (this.vm.selectedStep > 0) {
+ this.vm.selectedStep = this.vm.selectedStep - 1;
+ }
+ }
+
+ selectStep(step) {
+ this.vm.selectedStep = step;
+ }
+
+ submitCurrentStep(stepData) {
+ this.vm.showBusyText = true;
+ if (!stepData.completed) {
+ if (this.vm.selectedStep !== 4) {
+ this.vm.showBusyText = false;
+ //move to next step when success
+ stepData.completed = true;
+ this.enableNextStep();
+ }
+ } else {
+ this.vm.showBusyText = false;
+ this.enableNextStep();
+ }
+ }
+
+ enableNextStep() {
+ //do not exceed into max step
+ if (this.vm.selectedStep >= this.vm.maxStep) {
+ return;
+ }
+ //do not increment vm.stepProgress when submitting from previously completed step
+ if (this.vm.selectedStep === this.vm.stepProgress - 1) {
+ this.vm.stepProgress = this.vm.stepProgress + 1;
+ }
+
+ this.$timeout(() => this.vm.selectedStep = this.vm.selectedStep + 1);
+ }
+
+ saveOrUpdate() {
+ this.ApiService.savePlan(this.$stateParams.apiId, this.plan).then( () => {
+ this.NotificationService.show(this.plan.name + ' has been saved successfully');
+ this.$state.go(
+ 'management.apis.detail.plans.list',
+ this.plan.id === undefined ? {'state': 'staging'} : {});
+ });
+ }
+}
+
+export default ApiEditPlanController;
diff --git a/src/management/api/plans/plan/edit-plan.html b/src/management/api/plans/plan/edit-plan.html
new file mode 100644
index 0000000000..b3eb0d6d5e
--- /dev/null
+++ b/src/management/api/plans/plan/edit-plan.html
@@ -0,0 +1,25 @@
+
+
+ Plan
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Preview
+
+
+
+
+
diff --git a/src/management/application/dialog/applicationDialog.controller.ts b/src/management/api/plans/plan/plan-wizard-general.component.ts
similarity index 52%
rename from src/management/application/dialog/applicationDialog.controller.ts
rename to src/management/api/plans/plan/plan-wizard-general.component.ts
index d33b952713..6fcd32c083 100644
--- a/src/management/application/dialog/applicationDialog.controller.ts
+++ b/src/management/api/plans/plan/plan-wizard-general.component.ts
@@ -13,22 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-function DialogApplicationController($scope, $mdDialog, ApplicationService, NotificationService) {
- 'ngInject';
+const ApiPlanWizardGeneralComponent: ng.IComponentOptions = {
+ require: {
+ parent: '^editPlan'
+ },
+ template: require("./plan-wizard-general.html"),
+ controller: function() {
+ 'ngInject';
- $scope.hide = function () {
- $mdDialog.cancel();
- };
+ this.gotoNextStep = function() {
+ this.parent.vm.stepData[0].data = this.plan;
+ this.parent.moveToNextStep(this.parent.vm.stepData[0]);
+ };
+ }
+};
- $scope.create = function (application) {
- ApplicationService.create(application).then(function (result) {
- NotificationService.show('Application created');
- $mdDialog.hide(result);
- }).catch(function (error) {
- NotificationService.show('Error while creating the application');
- $scope.error = error.data.message;
- });
- };
-}
-
-export default DialogApplicationController;
+export default ApiPlanWizardGeneralComponent;
diff --git a/src/management/api/plans/plan/plan-wizard-general.html b/src/management/api/plans/plan/plan-wizard-general.html
new file mode 100644
index 0000000000..d7538307e6
--- /dev/null
+++ b/src/management/api/plans/plan/plan-wizard-general.html
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+ NEXT
+
+
+
+
+
diff --git a/src/management/api/plans/plan/plan-wizard-policies.component.ts b/src/management/api/plans/plan/plan-wizard-policies.component.ts
new file mode 100644
index 0000000000..1d19175352
--- /dev/null
+++ b/src/management/api/plans/plan/plan-wizard-policies.component.ts
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import _ = require('lodash');
+import PolicyService from "../../../../services/policy.service";
+import ApiEditPlanController from "./edit-plan.controller";
+
+class Policy {
+ id: string;
+ title: string;
+ description: string;
+ enable?: boolean = false;
+ schema?: string;
+ model?: object;
+ form?: ng.IFormController;
+}
+
+const ApiPlanWizardPoliciesComponent: ng.IComponentOptions = {
+ bindings: {
+ plan: '<'
+ },
+ require: {
+ parent: '^editPlan'
+ },
+ template: require("./plan-wizard-policies.html"),
+ controller: class {
+ private policies: Policy[];
+ private methods: string[] = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS', 'TRACE', 'CONNECT'];
+ private plan: any;
+ private parent: ApiEditPlanController;
+
+ constructor(
+ private PolicyService: PolicyService) {
+ 'ngInject';
+
+ this.policies = [
+ {
+ id: 'rate-limit', title: 'Rate-Limiting',
+ description: 'Rate limit how many HTTP requests an application can make in a given period of seconds or minutes'
+ }, {
+ id: 'quota', title: 'Quota',
+ description: 'Rate limit how many HTTP requests an application can make in a given period of hours, days or months'
+ }, {
+ id: 'resource-filtering', title: 'Path Authorization',
+ description: 'Restrict paths according to whitelist and / or blacklist rules'
+ }
+ ];
+ }
+
+ $onInit() {
+ if (! this.plan.paths) {
+ this.plan.paths = {'/': []};
+ }
+
+ _.each(this.policies, policy => {
+ this.PolicyService.getSchema(policy.id).then(schema => {
+ policy.schema = schema.data;
+
+ let pathPolicy = _.find(this.plan.paths['/'], function(pathPolicy) {
+ return pathPolicy[policy.id] != undefined;
+ });
+
+ if (pathPolicy && pathPolicy[policy.id]) {
+ policy.enable = true;
+ policy.model = pathPolicy[policy.id];
+ } else {
+ policy.model = {};
+ }
+ });
+ });
+ }
+
+ validate() {
+ return ! _.find(this.policies, policy => {
+ return policy.enable && policy.form && policy.form.$invalid;
+ });
+ }
+
+ gotoNextStep() {
+ let root = this.plan.paths['/'];
+ root.length = 0;
+
+ _(this.policies)
+ .filter(policy => policy.enable)
+ .map(policy => {
+ let p = {
+ methods: this.methods,
+ enable: true
+ };
+ p[policy.id] = policy.model;
+
+ return p;
+ })
+ .each(policy => root.push(policy));
+
+ this.parent.saveOrUpdate();
+ };
+ }
+};
+
+
+
+
+
+export default ApiPlanWizardPoliciesComponent;
diff --git a/src/management/api/plans/plan/plan-wizard-policies.html b/src/management/api/plans/plan/plan-wizard-policies.html
new file mode 100644
index 0000000000..8a95d3144d
--- /dev/null
+++ b/src/management/api/plans/plan/plan-wizard-policies.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
{{policy.title}}
+
{{policy.description}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PREVIOUS
+
+
+ SAVE
+
+
+
+
+
diff --git a/src/management/api/plans/plan/plan-wizard-security.component.ts b/src/management/api/plans/plan/plan-wizard-security.component.ts
new file mode 100644
index 0000000000..2a8b15f364
--- /dev/null
+++ b/src/management/api/plans/plan/plan-wizard-security.component.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import _ = require('lodash');
+import PolicyService from "../../../../services/policy.service";
+import ApiEditPlanController from "./edit-plan.controller";
+
+const ApiPlanWizardSecurityComponent: ng.IComponentOptions = {
+ require: {
+ parent: '^editPlan'
+ },
+ template: require("./plan-wizard-security.html"),
+ controller: class {
+ private securityTypes: any[];
+ private securityDefinition: any;
+ private securitySchema: any;
+ private parent: ApiEditPlanController;
+ private plan: any;
+
+ constructor(private PolicyService: PolicyService) {
+ 'ngInject';
+
+ this.securityTypes = [
+ {
+ 'id': 'oauth2',
+ 'name': 'OAuth2',
+ 'policy': 'oauth2'
+ }, {
+ 'id': 'jwt',
+ 'name': 'JWT',
+ 'policy': 'jwt'
+ }, {
+ 'id': 'api_key',
+ 'name': 'API Key',
+ 'policy': 'api-key'
+ }, {
+ 'id': 'key_less',
+ 'name': 'Keyless (public)'
+ }];
+ }
+
+ $onInit() {
+ if (this.parent.plan.security) {
+ this.onSecurityTypeChange();
+ }
+ }
+
+ onSecurityTypeChange() {
+ let securityType: any = _.find(this.securityTypes, {'id': this.parent.plan.security});
+ if (securityType && securityType.policy) {
+ this.PolicyService.getSchema(securityType.policy).then(schema => {
+ this.securitySchema = schema.data;
+ if (this.parent.plan.securityDefinition) {
+ this.parent.plan.securityDefinition = JSON.parse(this.parent.plan.securityDefinition);
+ } else {
+ this.parent.plan.securityDefinition = {};
+ }
+ });
+ } else {
+ this.securitySchema = undefined;
+ this.parent.plan.securityDefinition = {};
+ }
+ };
+
+ gotoNextStep() {
+ this.parent.plan.securityDefinition = JSON.stringify(this.parent.plan.securityDefinition);
+ this.parent.vm.stepData[1].data = this.parent.plan;
+ this.parent.moveToNextStep(this.parent.vm.stepData[1]);
+ };
+ }
+};
+
+export default ApiPlanWizardSecurityComponent;
diff --git a/src/management/api/plans/plan/plan-wizard-security.html b/src/management/api/plans/plan/plan-wizard-security.html
new file mode 100644
index 0000000000..05875edd13
--- /dev/null
+++ b/src/management/api/plans/plan/plan-wizard-security.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+ Authentication
+
+
+
+
+
+
+ Configuration
+
+
+
+
+
+
+
+
+ PREVIOUS
+
+
+ NEXT
+
+
+
+
+
diff --git a/src/management/api/plans/apiPlans.controller.ts b/src/management/api/plans/plans.controller.ts
similarity index 92%
rename from src/management/api/plans/apiPlans.controller.ts
rename to src/management/api/plans/plans.controller.ts
index c51e16c42c..4dcc3579c4 100644
--- a/src/management/api/plans/apiPlans.controller.ts
+++ b/src/management/api/plans/plans.controller.ts
@@ -18,6 +18,7 @@ import angular = require('angular');
import UserService from '../../../services/user.service';
class ApiPlansController {
+ private api: any;
private plans: any;
private groups: any;
private dndEnabled: boolean;
@@ -26,34 +27,20 @@ class ApiPlansController {
private securityTypes: {id: string; name: string}[];
private countByStatus: any;
private filteredPlans: any;
+
constructor(
- private resolvedPlans,
- private resolvedGroups,
- private resolvedApi,
private $mdSidenav,
private $mdDialog,
private $scope,
private ApiService,
- private $state,
- private $stateParams,
+ private $state: ng.ui.IStateService,
+ private $stateParams: ng.ui.IStateParamsService,
private NotificationService,
private dragularService,
private UserService: UserService
) {
'ngInject';
- this.plans = resolvedPlans.data;
- if (resolvedApi.data.visibility === "private") {
- if (resolvedApi.data.groups) {
- const apiGroupIds = resolvedApi.data.groups;
- this.groups = _.filter(resolvedGroups, (group) => {
- return apiGroupIds.indexOf(group["id"]) > -1;
- });
- } else {
- this.groups = [];
- }
- } else {
- this.groups = resolvedGroups;
- }
+ /*
this.dndEnabled = UserService.isUserHasPermissions(['api-plan-u']);
this.statusFilters = ['staging', 'published', 'closed'];
this.selectedStatus = ['published'];
@@ -70,15 +57,7 @@ class ApiPlansController {
this.countByStatus = {};
this.resetPlan();
- if ($stateParams.state) {
- if (_.includes(this.statusFilters, $stateParams.state)) {
- this.changeFilter($stateParams.state);
- } else {
- this.applyFilters();
- }
- } else {
- this.applyFilters();
- }
+
var that = this;
$scope.configure = function (plan) {
@@ -124,9 +103,21 @@ class ApiPlansController {
$scope.$on('planChangeSuccess', function(event, args) {
that.changeFilter(args.state);
});
+ */
}
+/*
+ $onInit() {
+ if (this.api.visibility === "private") {
+ if (this.api.groups) {
+ const apiGroupIds = this.api.groups;
+ this.groups = _.filter(this.groups, (group) => {
+ return apiGroupIds.indexOf(group["id"]) > -1;
+ });
+ } else {
+ this.groups = [];
+ }
+ }
- init() {
let that = this;
let d = document.querySelector('.plans');
@@ -149,7 +140,18 @@ class ApiPlansController {
that.NotificationService.show('Plans have been reordered successfully');
});
});
+
+ if (this.$stateParams.state) {
+ if (_.includes(this.statusFilters, this.$stateParams.state)) {
+ this.changeFilter(this.$stateParams.state);
+ } else {
+ this.applyFilters();
+ }
+ } else {
+ this.applyFilters();
+ }
}
+ */
list() {
this.ApiService.getApiPlans(this.$stateParams.apiId).then(response => {
@@ -216,10 +218,11 @@ class ApiPlansController {
}
addPlan() {
- this.resetPlan();
- this.$scope.plan.authorizedGroups = _.map(this.groups, "id");
- this.$mdSidenav('plan-edit').toggle();
- this.$mdSidenav('live-preview').toggle();
+ this.$state.go('management.apis.detail.plans.new');
+ }
+
+ editPlan(planId: string) {
+ this.$state.go('management.apis.detail.plans.plan', {planId: planId});
}
save() {
diff --git a/src/management/api/plans/publishPlanDialog.controller.ts b/src/management/api/plans/publishPlanDialog.controller.ts
index 9dac2ef441..5eb470a7e5 100644
--- a/src/management/api/plans/publishPlanDialog.controller.ts
+++ b/src/management/api/plans/publishPlanDialog.controller.ts
@@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-function DialogPublishPlanController($scope, $rootScope, $mdDialog, ApiService, NotificationService, apiId, plan) {
+function DialogPublishPlanController($scope, $mdDialog, plan) {
'ngInject';
- $scope.apiId = apiId;
$scope.plan = plan;
$scope.hide = function () {
@@ -24,13 +23,6 @@ function DialogPublishPlanController($scope, $rootScope, $mdDialog, ApiService,
};
$scope.publish = function () {
- ApiService.publishPlan($scope.apiId, $scope.plan.id).then(function() {
- NotificationService.show('Plan ' + plan.name + ' has been published');
- $rootScope.$broadcast("planChangeSuccess", { state: "published"});
- }).catch(function (error) {
- $scope.error = error;
- });
-
$mdDialog.hide($scope.plan);
};
}
diff --git a/src/management/api/subscriptions/plan-subscriptions.component.ts b/src/management/api/subscriptions/plan-subscriptions.component.ts
new file mode 100644
index 0000000000..f2d822cccd
--- /dev/null
+++ b/src/management/api/subscriptions/plan-subscriptions.component.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import _ = require('lodash');
+
+const ApiPlanSubscriptionsComponent: ng.IComponentOptions = {
+ bindings: {
+ api: '<',
+ subscriptions: '<'
+ },
+ template: require('./plan-subscriptions.html'),
+ controller: class {
+ private statusFilters: string[];
+ private selectedStatus: string[];
+
+ private subscriptions: any[];
+ private filteredSubscriptions: any[];
+
+ constructor(
+ ) {
+ 'ngInject';
+
+ this.statusFilters = ['pending', 'accepted', 'closed', 'rejected'];
+ this.selectedStatus = ['accepted'];
+ }
+
+ $onInit() {
+ this.applyFilters();
+ }
+
+ changeFilter(statusFilter: string) {
+ if (_.includes(this.selectedStatus, statusFilter)) {
+ _.pull(this.selectedStatus, statusFilter);
+ } else {
+ this.selectedStatus.push(statusFilter);
+ }
+ this.applyFilters();
+ }
+
+ applyFilters() {
+ this.filteredSubscriptions =
+ _(this.subscriptions)
+ .filter((subscription) => _.includes(this.selectedStatus, subscription.status))
+ .value();
+ }
+
+ }
+};
+
+export default ApiPlanSubscriptionsComponent;
diff --git a/src/management/api/subscriptions/plan-subscriptions.html b/src/management/api/subscriptions/plan-subscriptions.html
new file mode 100644
index 0000000000..16577c4e00
--- /dev/null
+++ b/src/management/api/subscriptions/plan-subscriptions.html
@@ -0,0 +1,108 @@
+
+
diff --git a/src/portal/api/subscribe/api-subscribe.ts b/src/portal/api/subscribe/api-subscribe.ts
deleted file mode 100644
index b6801964cc..0000000000
--- a/src/portal/api/subscribe/api-subscribe.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import * as _ from 'lodash';
-import ApiService from '../../../services/api.service';
-import ApplicationService from '../../../services/applications.service';
-
-function ApiSubscribeController($state,
- NotificationService,
- ApplicationService: ApplicationService,
- ApiService: ApiService,
- Constants,
- $translate) {
- 'ngInject';
- const vm = this;
-
- this.$onInit = function () {
- if (vm.plan.paths['/'] && vm.plan.paths['/'].length) {
- _.forEach(vm.plan.paths['/'], function (path) {
- if (path.quota) {
- let quota = path.quota.quota;
- $translate('common.' + quota.periodTimeUnit).then(function (quotaPeriodTimeUnitTranslated) {
- $translate('api.subscription.planInformation',
- {
- quotaLimit: quota.limit,
- quotaPeriodTime: quota.periodTime,
- quotaPeriodTimeUnit: quotaPeriodTimeUnitTranslated
- })
- .then(function (translatedMessage) {
- vm.planInformation = translatedMessage;
- });
- });
- }
- });
- }
- };
-
- function getSubscription(application) {
- if (!application) {
- return;
- }
- const subscriptionsByApplication = _.groupBy(vm.subscriptions, 'application');
- return _.find(subscriptionsByApplication[application.id], function (subscriptionByApplication: any) {
- return _.includes(['accepted', 'pending'], subscriptionByApplication.status);
- });
- }
-
- vm.isNotSelectable = function (application) {
- if (!application) {
- return true;
- }
- return getSubscription(application);
- };
-
- function fetchApiKey(application) {
- vm.subscription = getSubscription(application);
- if (application && vm.subscription) {
- ApplicationService.listApiKeys(application.id, vm.subscription.id).then(function (apiKeys) {
- let apiKey = _.find(apiKeys.data, function (apiKey: any) {
- return !apiKey.revoked;
- });
- if (apiKey) {
- vm.apiKey = apiKey.key;
- }
- });
- }
- }
-
- vm.select = function (application) {
- ApplicationService.subscribe(application.id, vm.plan.id).then(function () {
- NotificationService.show('api.subscription.successful', false, {planName: vm.plan.name});
-
- ApiService.getPlanSubscriptions(vm.api.id, vm.plan.id).then(function (subscriptions) {
- vm.subscriptions = subscriptions.data;
- fetchApiKey(application);
- });
- });
- };
-
- vm.goToApplications = function () {
- $state.go('applications.list', {}, {reload: true});
- };
-
- vm.subscribable = function () {
- return 'key_less' !== vm.plan.security;
- };
-
- vm.onClipboardSuccess = function (e) {
- NotificationService.show('api.subscription.apiKeyCopySuccess');
- e.clearSelection();
- };
-
- vm.onApplicationSearchChange = function () {
- delete vm.apiKey;
- };
-
- vm.onApplicationSelect = function () {
- fetchApiKey(vm.selectedApp);
- };
-
- vm.getSampleCall = function () {
- return 'curl -X GET "' + Constants.portal.entrypoint + vm.api.context_path +
- '" -H "' + Constants.portal.apikeyHeader + ': ' + (vm.apiKey ? vm.apiKey : 'given_api_key') + '"';
- }
-}
-
-export default ApiSubscribeController;
diff --git a/src/portal/home/home.html b/src/portal/home/home.html
index f7908ffee4..542faa04db 100644
--- a/src/portal/home/home.html
+++ b/src/portal/home/home.html
@@ -54,7 +54,7 @@
-
diff --git a/src/portal/i18n/en.json b/src/portal/i18n/en.json
index dfa107be2a..90a9644e90 100644
--- a/src/portal/i18n/en.json
+++ b/src/portal/i18n/en.json
@@ -56,17 +56,44 @@
"owner": "Owner",
"publishedAt": "Published",
"subscription": {
- "title": "Select an application to subscribe",
- "subtitle": "You are subscribing to plan {{planName}} {{planInformation}}",
- "placeholder": "Select an application...",
- "action": "Subscribe for the application {{applicationName}}",
- "keyless": "This plan is keyless. You can already use it without subscription.",
- "success": "Let's start to use the API \"{{apiName}}\" with the application \"{{applicationName}}\"!",
- "sample": "You can access the API with the following api-key",
- "validationInProgress": "Waiting for validation for the application \"{{applicationName}}\"!",
- "sampleNotValidated": "Once validated, you will be able to access your API with the given api-key as below:",
- "successful": "Application has successfully subscribed to plan {{planName}}",
- "apiKeyCopySuccess": "API Key has been copied to clipboard",
+ "step1": {
+ "title": "Select an API Plan",
+ "subtitle": "The API plan is corresponding to the service contract between you and the API.",
+ "keyless": "This plan is key-less and does not require subscription."
+ },
+ "step2": {
+ "title": "Select an application",
+ "subtitle": "Application is the link between you, the consumer, and the subscription to an API plan.",
+ "application": {
+ "placeholder": "Select an application..."
+ },
+ "clientIdRequired": "A clientId is required to subscribe to this plan. Please check your application's configuration.",
+ "action": "Subscribe"
+ },
+ "step3": {
+ "apikey": {
+ "key": "You can access the API using the following API Key:",
+ "curl": "Request sample using curl:",
+ "clipboard": "API Key has been copied to the clipboard."
+ },
+ "oauth2": {
+ "bearer": "You have to get an access_token from the authorization server before being able to consume this API.",
+ "curl": "Request sample using curl:"
+ },
+ "jwt": {
+ "bearer": "You have to get an access_token from the authorization server before being able to consume this API.",
+ "curl": "Request sample using curl:"
+ },
+ "pending": {
+ "title": "Subscription is waiting for validation",
+ "subtitle": "Your subscription is still in pending state, you will receive a notification once processed."
+ },
+ "accepted": {
+ "title": "Let's start to use \"{{apiName}}\" with \"{{applicationName}}\"!"
+ },
+ "successful": "Subscription request has been submitted."
+ },
+
"planInformation": "with a quota of {{quotaLimit}} requests per {{quotaPeriodTime}} {{quotaPeriodTimeUnit}}"
},
"plan": {
diff --git a/src/portal/i18n/fr.json b/src/portal/i18n/fr.json
index ca28ddbdab..e536a44a15 100644
--- a/src/portal/i18n/fr.json
+++ b/src/portal/i18n/fr.json
@@ -56,17 +56,44 @@
"owner": "Propriétaire",
"publishedAt": "Publié",
"subscription": {
- "title": "Sélectionner une application pour souscrire",
- "subtitle": "Vous êtes sur le point de souscrire au plan {{planName}} {{planInformation}}",
- "placeholder": "Sélectionner une application...",
- "action": "Souscrire pour l'application {{applicationName}}",
- "keyless": "Ce plan est sans clé. Vous pouvez déjà l'utiliser sans souscription.",
- "success": "Commençons à utiliser l'API \"{{apiName}}\" avec l'application \"{{applicationName}}\"!",
- "sample": "Vous pouvez accéder à l'API avec la clé d'API suivante",
- "validationInProgress": "En attente de validation pour l'application \"{{applicationName}}\"!",
- "sampleNotValidated": "Une fois validé, vous pourrez accéder à votre API avec la clé d'API donnée ci-dessous :",
- "successful": "L'application a souscrit au plan {{planName}} avec succés",
- "apiKeyCopySuccess": "La clé d'API a bien été copiée dans le presse-papier",
+ "step1": {
+ "title": "Sélectionner un plan",
+ "subtitle": "Le plan d'API correspond au contrat de service entre l'API et vous.",
+ "keyless": "Ce plan est sans clé et ne nécessite pas de souscription."
+ },
+ "step2": {
+ "title": "Sélectionner une application",
+ "subtitle": "L'application est le lien entre vous, en tant que consommateur, et la souscription à un plan d'API.",
+ "application": {
+ "placeholder": "Sélectionner une application..."
+ },
+ "clientIdRequired": "Un clientId est nécessaire pour pouvoir souscrire à ce plan. Veuillez configurer votre application.",
+ "action": "Souscrire"
+ },
+ "step3": {
+ "apikey": {
+ "key": "Vous pouvez accéder à l'API avec la clé d'API suivante:",
+ "curl": "Exemple d'appel avec curl:",
+ "clipboard": "La clé d'API a bien été copiée dans le presse-papier"
+ },
+ "oauth2": {
+ "bearer": "Vous devez récupèrer un access_token auprès du serveur d'autorisation avant de pouvoir consommer cette API.",
+ "curl": "Exemple d'appel avec curl:"
+ },
+ "jwt": {
+ "bearer": "Vous devez récupèrer un access_token auprès du serveur d'autorisation avant de pouvoir consommer cette API.",
+ "curl": "Exemple d'appel avec curl:"
+ },
+ "pending": {
+ "title": "Souscription en attente de validation",
+ "subtitle": "Votre souscription est en attente de validation, vous serez notifié une fois celle-ci traitée."
+ },
+ "accepted": {
+ "title": "Commençons à utiliser \"{{apiName}}\" avec \"{{applicationName}}\" !"
+ },
+ "successful": "La demande de souscription a été soumise."
+ },
+
"planInformation": "avec un quota de {{quotaLimit}} requêtes par {{quotaPeriodTime}} {{quotaPeriodTimeUnit}}"
},
"plan": {
diff --git a/src/portal/index.html b/src/portal/index.html
index 9b684ea7b8..150b9dad06 100644
--- a/src/portal/index.html
+++ b/src/portal/index.html
@@ -16,7 +16,7 @@
-->
-
+