diff --git a/src/css/aclmanagement.css b/src/css/aclmanagement.css
index f58d374d9..b5d68f713 100644
--- a/src/css/aclmanagement.css
+++ b/src/css/aclmanagement.css
@@ -1,4 +1,9 @@
+.acl-management-view .acl-rules {
+ table-layout: fixed;
+}
+
.acl-management-view .acl-rules .toolbar {
+ height: 35px;
padding: 0;
position: relative;
}
@@ -8,8 +13,16 @@
background-color: var(--color-info-light);
}
-.acl-management-view .acl-rules .actions-column {
- min-width: 107px;
+.acl-management-view .acl-rules .preview-rule-row td.data {
+ overflow-wrap: break-word;
+}
+
+.acl-management-view .acl-rules .index-column {
+ width: 2%;
+}
+
+.acl-management-view .acl-rules .reorder-column {
+ width: 25px;
}
.acl-management-view .acl-rules .reorder-cell button {
@@ -17,11 +30,65 @@
cursor: pointer;
}
+.acl-management-view .acl-rules .data input {
+ width: 95%;
+ display: inline-block;
+}
+
+.acl-management-view .acl-rules .data input.ng-invalid ~ em:after {
+ content: "*";
+ color: red;
+ display: inline;
+ position: absolute;
+ margin: 4px 0 0 3px;
+}
+
+.acl-management-view .acl-rules .subject-column {
+ width: 18%;
+}
+
+.acl-management-view .acl-rules .predicate-column {
+ width: 18%;
+}
+
+.acl-management-view .acl-rules .object-column {
+ width: 18%;
+}
+
+.acl-management-view .acl-rules .context-column {
+ width: 18%;
+}
+
+.acl-management-view .acl-rules .role-column {
+ width: 18%;
+}
+
+.acl-management-view .acl-rules .policy-column {
+ width: 100px;
+}
+
+.acl-management-view .acl-rules .actions-column {
+ width: 107px;
+}
+
+.acl-management-view .acl-rules .actions-cell {
+ text-align: right;
+}
+
.acl-management-view .acl-rules .actions-cell button {
padding: 0;
cursor: pointer;
}
.acl-management-view .acl-rules .selected {
- outline: 1px solid var(--secondary-color);
+ animation: highlight 2s 1;
+}
+
+@keyframes highlight {
+ 0% {
+ background-color: rgba(255, 255, 153, .8);
+ }
+ 100% {
+ background-color: rgba(255, 255, 255, 0);
+ }
}
diff --git a/src/i18n/locale-en.json b/src/i18n/locale-en.json
index 6eb09feb0..03d1f529e 100644
--- a/src/i18n/locale-en.json
+++ b/src/i18n/locale-en.json
@@ -138,15 +138,28 @@
"role": "Role",
"policy": "Policy"
},
+ "field_placeholders": {
+ "subject": "Subject",
+ "predicate": "Predicate",
+ "object": "Object",
+ "context": "Context",
+ "role": "Role"
+ },
"actions": {
"move_up": "Move the rule up",
"move_down": "Move the rule down",
"delete_rule": "Delete rule",
"add_rule": "Add new rule",
- "edit_rule": "Edit rule"
+ "edit_rule": "Edit rule",
+ "save_rule": "Save rule",
+ "cancel_rule_editing": "Cancel rule editing",
+ "save_acl": "Save ACL",
+ "cancel_acl_saving": "Cancel"
+
},
"messages": {
- "no_data": "There are no rules defined in current repository"
+ "no_data": "There are no rules defined in current repository",
+ "invalid_form": "Fill in valid data in all fields before saving rule"
}
},
"errors": {
diff --git a/src/i18n/locale-fr.json b/src/i18n/locale-fr.json
index a5b4d30aa..8456317c0 100644
--- a/src/i18n/locale-fr.json
+++ b/src/i18n/locale-fr.json
@@ -138,15 +138,27 @@
"role": "Rôle",
"policy": "Politique"
},
+ "field_placeholders": {
+ "subject": "Sujet",
+ "predicate": "Prédicat",
+ "object": "Objet",
+ "context": "Contexte",
+ "role": "Politique"
+ },
"actions": {
"move_up": "Déplacer la règle vers le haut",
"move_down": "Déplacer la règle vers le bas",
"delete_rule": "Supprimer la règle",
"add_rule": "Ajouter une nouvelle règle",
- "edit_rule": "Modifier la règle"
+ "edit_rule": "Modifier la règle",
+ "save_rule": "Enregistrer la règle",
+ "cancel_rule_editing": "Annuler la modification des règles",
+ "save_acl": "Enregistrer la liste de contrôle d'accès",
+ "cancel_acl_saving": "Annuler"
},
"messages": {
- "no_data": "Aucune règle n'est définie dans le référentiel actuel"
+ "no_data": "Aucune règle n'est définie dans le référentiel actuel",
+ "invalid_form": "Remplissez des données valides dans tous les champs avant d'enregistrer la règle"
}
},
"errors": {
diff --git a/src/js/angular/aclmanagement/controllers.js b/src/js/angular/aclmanagement/controllers.js
index 594923df3..7996f2894 100644
--- a/src/js/angular/aclmanagement/controllers.js
+++ b/src/js/angular/aclmanagement/controllers.js
@@ -20,6 +20,12 @@ function AclManagementCtrl($scope, toastr, AclManagementRestService, $repositori
// Public fields
//
+ /**
+ * The model for the rule editing form.
+ * @type {Object} The edited rule form model.
+ */
+ $scope.ruleData = {};
+
/**
* Flag controlling the loading indicator while some http operation is in progress.
* @type {boolean}
@@ -31,6 +37,7 @@ function AclManagementCtrl($scope, toastr, AclManagementRestService, $repositori
* @type {undefined|ACListModel}
*/
$scope.rulesModel = undefined;
+
/**
* A copy of the list with ACL rules that will be managed in this view. This is needed for restoring functionality.
* @type {undefined|ACListModel}
@@ -43,16 +50,23 @@ function AclManagementCtrl($scope, toastr, AclManagementRestService, $repositori
*/
$scope.selectedRule = undefined;
+ /**
+ * The index of a rule which is opened for edit or create.
+ * @type {undefined|number}
+ */
+ $scope.editedRuleIndex = undefined;
+
//
// Public functions
//
/**
- * Adds a new rule at a given index.
+ * Adds a new rule at a given index in the rulesModel.
* @param {number} index
*/
$scope.addRule= (index) => {
- // TODO: implement
+ $scope.rulesModel.addRule(index);
+ $scope.editedRuleIndex = index;
};
/**
@@ -71,6 +85,22 @@ function AclManagementCtrl($scope, toastr, AclManagementRestService, $repositori
// TODO: implement
};
+ /**
+ * Saves a rule at a given index in the rulesModel.
+ */
+ $scope.saveRule= () => {
+ $scope.editedRuleIndex = undefined;
+ };
+
+ /**
+ * Cancels the editing operation of a rule at a given index.
+ * @param {number} index
+ */
+ $scope.cancelEditing = (index) => {
+ $scope.rulesModel.removeRule(index);
+ $scope.editedRuleIndex = undefined;
+ };
+
/**
* Moves a rule on the given index in the rulesModel one position up by swapping it with the rule above.
* @param {number} index
@@ -89,6 +119,14 @@ function AclManagementCtrl($scope, toastr, AclManagementRestService, $repositori
$scope.selectedRule = index + 1;
};
+ $scope.saveAcl = () => {
+ // TODO: implement
+ };
+
+ $scope.cancelAclSave = () => {
+ // TODO: implement
+ };
+
//
// Private functions
//
@@ -115,7 +153,6 @@ function AclManagementCtrl($scope, toastr, AclManagementRestService, $repositori
/**
* Watching for repository changes and reload the rules, because they are stored per repository.
- * TODO: later when we have the create/edit operations we would need a confirmation before repo change in order to prevent data loss for the user.
*/
$scope.$watch(function () {
return $repositories.getActiveRepository();
diff --git a/src/js/angular/aclmanagement/model.js b/src/js/angular/aclmanagement/model.js
index 37a1f8c3d..30e11054b 100644
--- a/src/js/angular/aclmanagement/model.js
+++ b/src/js/angular/aclmanagement/model.js
@@ -11,6 +11,14 @@ export class ACListModel {
return this.aclRules.length;
}
+ addRule(index) {
+ this.aclRules.splice(index, 0, new ACRuleModel());
+ }
+
+ removeRule(index) {
+ this.aclRules.splice(index, 1);
+ }
+
moveUp(index) {
const previousRule = this.aclRules[index - 1];
this.aclRules[index - 1] = this.aclRules[index];
@@ -42,7 +50,7 @@ export class ACRuleModel {
* @param {string} role
* @param {string} policy
*/
- constructor(subject, predicate, object, context, role, policy) {
+ constructor(subject = '', predicate = '', object= '', context= '', role= '', policy= ACL_POLICY.ALLOW) {
this._subject = subject;
this._predicate = predicate;
this._object = object;
diff --git a/src/pages/aclmanagement.html b/src/pages/aclmanagement.html
index e269c68b3..1d4472ab0 100644
--- a/src/pages/aclmanagement.html
+++ b/src/pages/aclmanagement.html
@@ -14,79 +14,162 @@
-
-
-
- {{'acl_management.rulestable.column.index' | translate}} |
- |
- {{'acl_management.rulestable.column.subject' | translate}} |
- {{'acl_management.rulestable.column.predicate' | translate}} |
- {{'acl_management.rulestable.column.object' | translate}} |
- {{'acl_management.rulestable.column.context' | translate}} |
- {{'acl_management.rulestable.column.role' | translate}} |
- {{'acl_management.rulestable.column.policy' | translate}} |
- |
-
-
-
- |
+
+
+
+
+
+
+
+
+
diff --git a/test-cypress/fixtures/locale-en.json b/test-cypress/fixtures/locale-en.json
index 1b05a4ef7..03d1f529e 100644
--- a/test-cypress/fixtures/locale-en.json
+++ b/test-cypress/fixtures/locale-en.json
@@ -26,7 +26,15 @@
"node_state_restricted": "Restricted",
"link_state_in_sync": "In sync",
"link_state_syncing": "Syncing",
- "link_state_out_of_sync": "Out of sync"
+ "link_state_out_of_sync": "Out of sync",
+ "recovery_state": {
+ "searching_for_node": "Searching for node",
+ "applying_snapshot": "Applying a snapshot",
+ "building_snapshot": "Building a snapshot for {{node}}",
+ "waiting_for_snapshot": "Waiting for snapshot from node {{node}}",
+ "sending_snapshot": "Sending a snapshot to node {{node}}",
+ "recording_snapshot": "Recording a snapshot from node {{node}}"
+ }
},
"cluster_configuration": {
"label": "Cluster configuration",
@@ -130,15 +138,28 @@
"role": "Role",
"policy": "Policy"
},
+ "field_placeholders": {
+ "subject": "Subject",
+ "predicate": "Predicate",
+ "object": "Object",
+ "context": "Context",
+ "role": "Role"
+ },
"actions": {
"move_up": "Move the rule up",
"move_down": "Move the rule down",
"delete_rule": "Delete rule",
"add_rule": "Add new rule",
- "edit_rule": "Edit rule"
+ "edit_rule": "Edit rule",
+ "save_rule": "Save rule",
+ "cancel_rule_editing": "Cancel rule editing",
+ "save_acl": "Save ACL",
+ "cancel_acl_saving": "Cancel"
+
},
"messages": {
- "no_data": "There are no rules defined in current repository"
+ "no_data": "There are no rules defined in current repository",
+ "invalid_form": "Fill in valid data in all fields before saving rule"
}
},
"errors": {
diff --git a/test-cypress/integration/setup/aclmanagement.spec.js b/test-cypress/integration/setup/aclmanagement.spec.js
index 2e85b16ee..7f7892996 100644
--- a/test-cypress/integration/setup/aclmanagement.spec.js
+++ b/test-cypress/integration/setup/aclmanagement.spec.js
@@ -53,9 +53,97 @@ describe('ACL Management page', () => {
AclManagementSteps.moveRuleDown(1);
checkRules([ACL[0], ACL[2], ACL[1], ACL[3], ACL[4]]);
});
+
+ it('Should add a new rule in the beginning of the list and cancel operation', () => {
+ AclManagementSteps.addRuleInBeginning();
+ AclManagementSteps.getAclRules().should('have.length', 6);
+ AclManagementSteps.cancelRuleEditing(0);
+ AclManagementSteps.getAclRules().should('have.length', 5);
+ });
+
+ it('Should add a new rule in the list and cancel operation', () => {
+ AclManagementSteps.addRule(1);
+ AclManagementSteps.getAclRules().should('have.length', 6);
+ AclManagementSteps.cancelRuleEditing(2);
+ AclManagementSteps.getAclRules().should('have.length', 5);
+ });
+
+ it('Should hide all unnecessary actions during rule editing', () => {
+ // When there is no rule opened for edit
+ // Then I expect that move up, move down, edit rule, create rule, delete rule buttons to be visible
+ AclManagementSteps.getMoveUpButtons().should('have.length', 4);
+ AclManagementSteps.getMoveDownButtons().should('have.length', 4);
+ AclManagementSteps.deleteRuleButtons().should('have.length', 5);
+ AclManagementSteps.editRuleButtons().should('have.length', 5);
+ AclManagementSteps.createRuleButtons().should('have.length', 6);
+ // When a rule is in edit mode
+ AclManagementSteps.addRule(1);
+ // Then I expect that move up, move down, edit rule, create rule, delete rule buttons to be hidden
+ AclManagementSteps.getMoveUpButtons().should('have.length', 0);
+ AclManagementSteps.getMoveDownButtons().should('have.length', 0);
+ AclManagementSteps.deleteRuleButtons().should('have.length', 0);
+ AclManagementSteps.editRuleButtons().should('have.length', 0);
+ AclManagementSteps.createRuleButtons().should('have.length', 0);
+ });
+
+ it('Should add a new rule in the list', () => {
+ // When I add a new rule
+ AclManagementSteps.addRule(1);
+ // Then I expect that the save rule button should be disabled because there mandatory fields in the new rule form
+ checkIfRuleSavingIsForbidden(2);
+ // When I fill in the subject field
+ AclManagementSteps.fillSubject(2, '');
+ // Then I expect that the save rule button should still be disabled
+ checkIfRuleSavingIsForbidden(2);
+ // When I fill in the predicate field
+ AclManagementSteps.fillPredicate(2, '*');
+ // Then I expect that the save rule button should still be disabled
+ checkIfRuleSavingIsForbidden(2);
+ // When I fill in the object field
+ AclManagementSteps.fillObject(2, '*');
+ // Then I expect that the save rule button should still be disabled
+ checkIfRuleSavingIsForbidden(2);
+ // When I fill in the context field
+ AclManagementSteps.fillContext(2, '*');
+ // Then I expect that the save rule button should still be disabled
+ checkIfRuleSavingIsForbidden(2);
+ // When I fill in the role field
+ AclManagementSteps.fillRole(2, 'ROLE1');
+ // Then I expect that the save rule button should be enabled
+ checkIfRuleSavingIsAllowed(2);
+ // When I change the policy
+ AclManagementSteps.selectPolicy(2, 'deny');
+ // Then I expect that the save rule button should be enabled
+ checkIfRuleSavingIsAllowed(2);
+ // When I save the rule
+ AclManagementSteps.saveRule(2);
+ // Then the rule should be saved
+ AclManagementSteps.getAclRules().should('have.length', 6);
+ const newRule = {
+ "subject": "",
+ "predicate": "*",
+ "object": "*",
+ "context": "*",
+ "role": "ROLE1",
+ "policy": "deny",
+ "moveUp": true,
+ "moveDown": true
+ };
+ checkRules([ACL[0], ACL[1], newRule, ACL[2], ACL[3], ACL[4]]);
+ });
});
});
+function checkIfRuleSavingIsForbidden(index) {
+ AclManagementSteps.getSaveRuleButton(index).should('not.exist');
+ AclManagementSteps.getSaveRuleDisabledButton(index).should('be.visible');
+}
+
+function checkIfRuleSavingIsAllowed(index) {
+ AclManagementSteps.getSaveRuleButton(index).should('be.visible');
+ AclManagementSteps.getSaveRuleDisabledButton(index).should('not.exist');
+}
+
function checkRules(rules = []) {
rules.forEach((rule, index) => {
AclManagementSteps.getRule(index).within(() => {
diff --git a/test-cypress/steps/setup/acl-management-steps.js b/test-cypress/steps/setup/acl-management-steps.js
index f17e8f12a..18c0664aa 100644
--- a/test-cypress/steps/setup/acl-management-steps.js
+++ b/test-cypress/steps/setup/acl-management-steps.js
@@ -56,6 +56,106 @@ export class AclManagementSteps {
static getNoDataMessage() {
return this.getAclTable().find('.no-data');
}
+
+ static addRuleInBeginning() {
+ this.getAddFirstRuleButton().click();
+ }
+
+ static getAddRuleButton(index) {
+ return this.getRule(index).find('.add-rule-btn');
+ }
+
+ static addRule(index) {
+ this.getAddRuleButton(index).click();
+ }
+
+ static getCancelRuleEditingButton(index) {
+ return this.getAclRules().eq(index).find('.cancel-rule-editing-btn');
+ }
+
+ static cancelRuleEditing(index) {
+ this.getCancelRuleEditingButton(index).click();
+ }
+
+ static getSaveRuleButton(index) {
+ return this.getRule(index).find('.save-rule-btn');
+ }
+
+ static getSaveRuleDisabledButton(index) {
+ return this.getRule(index).find('.save-rule-disabled-btn');
+ }
+
+ static saveRule(index) {
+ this.getSaveRuleButton(index).click();
+ }
+
+ static getSubjectField(index) {
+ return this.getRule(index).find('.subject-cell input');
+ }
+
+ static fillSubject(index, value) {
+ this.getSubjectField(index).type(value);
+ }
+
+ static getPredicateField(index) {
+ return this.getRule(index).find('.predicate-cell input');
+ }
+
+ static fillPredicate(index, value) {
+ this.getPredicateField(index).type(value);
+ }
+
+ static getObjectField(index) {
+ return this.getRule(index).find('.object-cell input');
+ }
+
+ static fillObject(index, value) {
+ this.getObjectField(index).type(value);
+ }
+
+ static getContextField(index) {
+ return this.getRule(index).find('.context-cell input');
+ }
+
+ static fillContext(index, value) {
+ this.getContextField(index).type(value);
+ }
+
+ static getRoleField(index) {
+ return this.getRule(index).find('.role-cell input');
+ }
+
+ static fillRole(index, value) {
+ this.getRoleField(index).type(value);
+ }
+
+ static getPolicySelectorField(index) {
+ return this.getRule(index).find('.policy-cell select');
+ }
+
+ static selectPolicy(index, value) {
+ this.getPolicySelectorField(index).select(value);
+ }
+
+ static getMoveUpButtons() {
+ return this.getAclTable().find('.move-up-btn');
+ }
+
+ static getMoveDownButtons() {
+ return this.getAclTable().find('.move-down-btn');
+ }
+
+ static deleteRuleButtons() {
+ return this.getAclTable().find('.delete-rule-btn');
+ }
+
+ static editRuleButtons() {
+ return this.getAclTable().find('.edit-rule-btn');
+ }
+
+ static createRuleButtons() {
+ return this.getAclTable().find('.add-rule-btn');
+ }
}
const ACL = [