Merge pull request #1152 from wazuh/group-ui-fixes

Group management UX fixes
This commit is contained in:
Jesús Ángel 2019-01-17 17:33:54 +01:00 committed by GitHub
commit 58a1a83a82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 239 additions and 147 deletions

View File

@ -10,6 +10,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Edit the group configuration ([#1096](https://github.com/wazuh/wazuh-kibana-app/pull/1096)).
- Add/remove groups to/from an agent ([#1096](https://github.com/wazuh/wazuh-kibana-app/pull/1096)).
- Add/remove agents to/from a group ([#1096](https://github.com/wazuh/wazuh-kibana-app/pull/1096)).
- Add/remove groups ([#1152](https://github.com/wazuh/wazuh-kibana-app/pull/1152)).
- New directive for tables that don't need external data sources ([#1067](https://github.com/wazuh/wazuh-kibana-app/pull/1067)).
- New search bar directive with interactive filters and suggestions ([#1058](https://github.com/wazuh/wazuh-kibana-app/pull/1058)).
- New server route `/elastic/alerts` for fetching alerts using custom parameters([#1056](https://github.com/wazuh/wazuh-kibana-app/pull/1056)).

View File

@ -2,7 +2,7 @@
"name": "wazuh",
"version": "3.8.0",
"revision": "0416",
"code": "0416-10",
"code": "0416-11",
"kibana": {
"version": "6.5.4"
},

View File

@ -89,6 +89,8 @@ export class AgentsController {
this.$scope.showSyscheckFiles = false;
this.$scope.editGroup = false;
this.$scope.addingGroupToAgent = false;
}
/**
@ -308,73 +310,41 @@ export class AgentsController {
this.appState.removeSessionStorageItem('configSubTab')
);
this.$scope.switchGroupEdit = () => this.switchGroupEdit();
this.$scope.switchGroupEdit = () => {
this.$scope.addingGroupToAgent = false;
this.switchGroupEdit();
};
this.$scope.showConfirm = (ev, group) => {
const confirm = this.$mdDialog.confirm({
controller: function(
$scope,
myScope,
$mdDialog,
groupHandler,
apiReq,
errorHandler
) {
$scope.myScope = myScope;
$scope.closeDialog = () => {
$mdDialog.hide();
$('body').removeClass('md-dialog-body');
};
$scope.confirmDialog = () => {
groupHandler
.addAgentToGroup(group, $scope.myScope.agent.id)
.then(() =>
apiReq.request('GET', `/agents/${$scope.myScope.agent.id}`, {})
)
.then(agent => {
$mdDialog.hide();
$('body').removeClass('md-dialog-body');
$scope.myScope.agent.group = agent.data.data.group;
$scope.myScope.groups = $scope.myScope.groups.filter(
item => !agent.data.data.group.includes(item)
);
if (!$scope.myScope.$$phase) $scope.myScope.$digest();
})
.catch(error =>
errorHandler.handle(
error.message || error,
'Error adding group to agent'
)
);
};
},
template:
'<md-dialog class="modalTheme euiToast euiToast--danger euiGlobalToastListItem">' +
'<md-dialog-content>' +
'<div class="euiToastHeader">' +
'<i class="fa fa-exclamation-triangle"></i>' +
'<span class="euiToastHeader__title">Add group ' +
`${group}` +
' to agent ' +
`${this.$scope.agent.id}` +
'?</span>' +
'</div>' +
'</md-dialog-content>' +
'<md-dialog-actions>' +
'<button class="md-primary md-cancel-button md-button ng-scope md-default-theme md-ink-ripple" type="button" ng-click="closeDialog()">Cancel</button>' +
'<button class="md-primary md-confirm-button md-button md-ink-ripple md-default-theme" type="button" ng-click="confirmDialog()">Agree</button>' +
'</md-dialog-actions>' +
'</md-dialog>',
targetEvent: ev,
hasBackdrop: false,
clickOutsideToClose: true,
disableParentScroll: true,
locals: {
myScope: this.$scope
}
});
$('body').addClass('md-dialog-body');
this.$mdDialog.show(confirm);
this.$scope.showConfirmAddGroup = group => {
this.$scope.addingGroupToAgent = this.$scope.addingGroupToAgent
? false
: group;
};
this.$scope.cancelAddGroup = () => (this.$scope.addingGroupToAgent = false);
this.$scope.confirmAddGroup = group => {
this.groupHandler
.addAgentToGroup(group, this.$scope.agent.id)
.then(() =>
this.apiReq.request('GET', `/agents/${this.$scope.agent.id}`, {})
)
.then(agent => {
this.$scope.agent.group = agent.data.data.group;
this.$scope.groups = this.$scope.groups.filter(
item => !agent.data.data.group.includes(item)
);
this.$scope.addingGroupToAgent = false;
this.errorHandler.info(`Group ${group} has been added.`, '');
if (!this.$scope.$$phase) this.$scope.$digest();
})
.catch(error => {
this.$scope.addingGroupToAgent = false;
this.errorHandler.handle(
error.message || error,
'Error adding group to agent'
);
});
};
}
/**

View File

@ -20,10 +20,12 @@ export function GroupsController(
csvReq,
appState,
shareAgent,
$timeout,
groupHandler,
wzTableFilter
) {
$scope.addingGroup = false;
$scope.$on('groupsIsReloaded', () => {
$scope.groupsSelectedTab = false;
$scope.editingFile = false;
$scope.currentGroup = false;
$scope.$emit('removeCurrentGroup');
@ -144,6 +146,7 @@ export function GroupsController(
//listeners
$scope.$on('wazuhShowGroup', (event, parameters) => {
$scope.groupsSelectedTab = 'agents';
return $scope.loadGroup(parameters.group);
});
@ -538,4 +541,19 @@ export function GroupsController(
$scope.filename = false;
}
});
$scope.switchAddingGroup = () => {
$scope.addingGroup = !$scope.addingGroup;
};
$scope.createGroup = async name => {
try {
$scope.addingGroup = false;
await groupHandler.createGroup(name);
errorHandler.info(`Success. Group ${name} has been created`, '');
} catch (error) {
errorHandler.handle(`${error.message || error}`, '');
}
$scope.$broadcast('wazuhSearch', {});
};
}

View File

@ -48,7 +48,6 @@ app.directive('wzTable', function() {
$window,
appState,
globalState,
$mdDialog,
groupHandler
) {
/**
@ -280,56 +279,48 @@ app.directive('wzTable', function() {
}
};
$scope.showConfirm = function(ev, agent) {
const group = instance.path.split('/').pop();
$scope.showConfirmRemoveGroup = (ev, group) => {
$scope.removingGroup =
$scope.removingGroup === group.name ? null : group.name;
};
const confirm = $mdDialog.confirm({
controller: function($scope, $mdDialog) {
$scope.closeDialog = () => {
$mdDialog.hide();
$('body').removeClass('md-dialog-body');
};
$scope.confirmDialog = () => {
groupHandler
.removeAgentFromGroup(group, agent.id)
.then(() => {
$mdDialog.hide();
$('body').removeClass('md-dialog-body');
init();
})
.then(() => $scope.$emit('updateGroupInformation', { group }))
.catch(error =>
errorHandler.handle(
error.message || error,
'Error removing agent from group'
)
);
};
},
template:
'<md-dialog class="modalTheme euiToast euiToast--danger euiGlobalToastListItem">' +
'<md-dialog-content>' +
'<div class="euiToastHeader">' +
'<i class="fa fa-exclamation-triangle"></i>' +
'<span class="euiToastHeader__title">The agent ' +
`${agent.id}` +
' will be removed from group ' +
`${group}` +
'.</span>' +
'</div>' +
'</md-dialog-content>' +
'<md-dialog-actions>' +
'<button class="md-primary md-cancel-button md-button ng-scope md-default-theme md-ink-ripple" type="button" ng-click="closeDialog()">Cancel</button>' +
'<button class="md-primary md-confirm-button md-button md-ink-ripple md-default-theme" type="button" ng-click="confirmDialog()">Agree</button>' +
'</md-dialog-actions>' +
'</md-dialog>',
targetEvent: ev,
hasBackdrop: false,
disableParentScroll: true,
clickOutsideToClose: true
});
$('body').addClass('md-dialog-body');
$mdDialog.show(confirm);
$scope.showConfirmRemoveAgentFromGroup = (ev, agent) => {
$scope.removingAgent =
$scope.removingAgent === agent.id ? null : agent.id;
};
$scope.cancelRemoveAgent = () => {
$scope.removingAgent = null;
};
$scope.cancelRemoveGroup = () => {
$scope.removingGroup = null;
};
$scope.confirmRemoveAgent = async agent => {
try {
const group = instance.path.split('/').pop();
await groupHandler.removeAgentFromGroup(group, agent);
errorHandler.info(
`Success. Agent ${agent} has been removed from ${group}`,
''
);
} catch (error) {
errorHandler.handle(`${error.message || error}`, '');
}
$scope.removingAgent = null;
return init();
};
$scope.confirmRemoveGroup = async group => {
try {
await groupHandler.removeGroup(group);
errorHandler.info(`Success. Group ${group} has been removed`, '');
} catch (error) {
errorHandler.handle(`${error.message || error}`, '');
}
$scope.removingGroup = null;
return init();
};
},
template

View File

@ -8,11 +8,11 @@
<thead class="wz-text-bold">
<th ng-repeat="key in keys" class="wz-text-left" ng-class="{ 'cursor-pointer' : !key.nosortable, 'col-lg-1' : !key.size, 'col-lg-{{key.size}}' : key.size }"
ng-click="!key.nosortable && sort(key)">
{{ keyEquivalence[key.value || key] || key.value || key }}
{{ path === '/agents/groups' && (key.value || key) === 'count' ? 'Agents' : keyEquivalence[key.value || key] || key.value || key }}
<i ng-if="!key.nosortable" class="fa wz-theader-sort-icon" ng-class="sortValue === (key.value || key) ? (sortDir ? 'fa-sort-asc' : 'fa-sort-desc') : 'fa-sort'"
aria-hidden="true"></i>
</th>
<th ng-if="(path === '/agents' || isLookingGroup()) && !isLookingDefaultGroup" class="wz-text-left col-lg-1">Actions</th>
<th ng-if="(path === '/agents' || path === '/agents/groups' || isLookingGroup()) && !isLookingDefaultGroup" class="wz-text-left col-lg-1">Actions</th>
</thead>
<tbody>
<tr ng-class="allowClick ? 'cursor-pointer' : ''" class="wz-word-wrap" ng-repeat="item in pagedItems[currentPage] | filter:{item:'!'}"
@ -33,18 +33,42 @@
<i ng-click="clickAction(item, 'configuration'); $event.stopPropagation()" class="fa fa-fw fa-wrench cursor-pointer"
tooltip="Open configuration for this agent" tooltip-placement="left" aria-hidden="true"></i>
</td>
<td ng-if="path === '/agents/groups'" ng-click="$event.stopPropagation()" class="cursor-default action-btn-td">
<i ng-if="removingGroup !== item.name" ng-click="showConfirmRemoveGroup($event, item); $event.stopPropagation()" class="fa fa-fw fa-remove cursor-pointer"
tooltip="Remove this group" tooltip-placement="left" aria-hidden="true"></i>
<div ng-if="removingGroup === item.name">
<div layout="row">
<span class="euiToastHeader__title font-size-12"><i class="fa fa-warning fa-fw"></i> Group {{item.name}} will be removed</span>
</div>
<div layout="row">
<md-button class="cancelBtn" type="button" ng-click="cancelRemoveGroup()"><i aria-hidden='true' class='fa fa-fw fa-close'></i> Cancel</md-button>
<md-button class="agreeBtn" type="button" ng-click="confirmRemoveGroup(item.name)"><i aria-hidden='true' class='fa fa-fw fa-save'></i> Agree</md-button>
</div>
</div>
</td>
<!--<td ng-if="path === '/agents/groups'" ng-click="$event.stopPropagation()" class="cursor-default action-btn-td">
<i ng-click="editGroupAgentConfig($event, item); $event.stopPropagation()" class="fa fa-fw fa-file-code-o cursor-pointer"
tooltip="Edit this group agent.conf file" tooltip-placement="left" aria-hidden="true"></i>
</td>-->
<td ng-if="isLookingGroup() && !isLookingDefaultGroup" ng-click="$event.stopPropagation()" class="cursor-default action-btn-td">
<i ng-click="showConfirm($event, item); $event.stopPropagation()" class="fa fa-fw fa-remove cursor-pointer"
<i ng-if="removingAgent !== item.id" ng-click="showConfirmRemoveAgentFromGroup($event, item); $event.stopPropagation()" class="fa fa-fw fa-remove cursor-pointer"
tooltip="Remove this agent from the group" tooltip-placement="left" aria-hidden="true"></i>
<div ng-if="removingAgent === item.id">
<div layout="row">
<span class="euiToastHeader__title font-size-12"><i class="fa fa-warning fa-fw"></i> The agent {{item.id}} will be removed from this group</span>
</div>
<div layout="row">
<md-button flex class="cancelBtn" type="button" ng-click="cancelRemoveAgent()"><i aria-hidden='true' class='fa fa-fw fa-close'></i> Cancel</md-button>
<md-button flex class="agreeBtn" type="button" ng-click="confirmRemoveAgent(item.id)"><i aria-hidden='true' class='fa fa-fw fa-save'></i> Agree</md-button>
</div>
</div>
</td>
</tr>
</tbody>
<tfoot>
<td colspan="{{ (path === '/agents' || isLookingGroup()) && !isLookingDefaultGroup ? keys.length + 1 : keys.length}}">
<td colspan="{{ (path === '/agents' || path === '/agents/groups' || isLookingGroup()) && !isLookingDefaultGroup ? keys.length + 1 : keys.length}}">
<span ng-show="!wazuh_table_loading" class="color-grey">{{ totalItems }} items ({{time | number: 2}}
seconds)</span>
<div ng-show="items.length >= itemsPerPage" class="pagination pull-right" style="margin:0 !important">

View File

@ -655,4 +655,44 @@ md-sidenav {
.wz-padding-bottom-14 {
padding-bottom: 14px;
}
.addGroupInput {
max-height: 20px !important;
max-width: 181px !important;
padding-right: 0 !important;
}
.addGroupBtn {
background-color: #0079a5 !important
}
.agreeBtn {
min-height: 0px !important;
max-height: 25px !important;
line-height: inherit !important;
color: #ffffff !important;
background-color: #0079a5 !important;
border-color: #0079a5 !important;
}
.cancelBtn {
color: #000000 !important;
background-color: #d9d9d9 !important;
border-color: #d9d9d9 !important;
min-height: 0px !important;
max-height: 25px !important;
line-height: inherit !important;
}
.agreeBtnAgents {
color: #ffffff !important;
background-color: #0079a5 !important;
border-color: #0079a5 !important;
}
.cancelBtnAgents {
color: #000000 !important;
background-color: #d9d9d9 !important;
border-color: #d9d9d9 !important;
}

View File

@ -86,6 +86,10 @@ html, body, button:not(.fa):not(.fa-times), textarea, input, select, .wz-chip {
text-overflow: ellipsis;
}
.font-size-12 {
font-size: 12px !important;
}
.font-size-16 {
font-size: 16px;
}

View File

@ -14,6 +14,19 @@ export class GroupHandler {
this.apiReq = apiReq;
}
async removeGroup(group) {
try {
const result = await this.apiReq.request(
'DELETE',
`/agents/groups/${group}`,
{}
);
return result;
} catch (error) {
return Promise.reject(error);
}
}
async removeAgentFromGroup(group, agentId) {
try {
const result = await this.apiReq.request(
@ -52,4 +65,17 @@ export class GroupHandler {
return Promise.reject(error);
}
}
async createGroup(name) {
try {
const result = await this.apiReq.request(
'PUT',
`/agents/groups/${name}`,
{}
);
return result;
} catch (error) {
return Promise.reject(error);
}
}
}

View File

@ -36,30 +36,33 @@
</div>
<div layout="row" layout-padding class="wz-padding-top-0 wz-padding-bottom-0" ng-if="agent.group">
<md-card flex class="wz-metric-color wz-md-card wz-no-padding">
<md-card-content layout="row" class="wz-padding-metric">
<div class="layout-row wz-word-break" flex>
Groups:&nbsp;<span ng-repeat="group in agent.group" class="wz-text-bold wz-text-link" ng-click="goGroups(agent,$index)">{{ group }}&nbsp;</span>
<span ng-if="!editGroup" class="wz-text-link" ng-click="switchGroupEdit()"><i class="fa fa-fw fa-plus-circle"></i></span>
<md-card-content class="wz-padding-metric">
<div class="layout-row wz-word-break">
<span class="wz-text-link" ng-click="switchGroupEdit()"><i class="fa fa-fw fa-plus-circle"></i></span>&nbsp;Groups:&nbsp;
<span ng-repeat="group in agent.group" class="wz-text-bold wz-text-link" ng-click="goGroups(agent,$index)">{{ group }}&nbsp;</span>
</div>
</md-card-content>
</md-card>
</div>
<div layout="row" layout-padding class="wz-padding-top-0 wz-padding-bottom-0" ng-if="editGroup">
<md-card flex class="wz-metric-color wz-md-card wz-no-padding">
<md-card-content layout="column" class="wz-padding-metric">
<div layout="row">
<div class="wz-word-break" flex ng-if="groups && groups.length">
Available groups:
<div layout="row" class="wz-margin-top-10" ng-if="editGroup && !addingGroupToAgent">
<div class="wz-word-break" flex ng-if="groups && groups.length">
Available groups:
</div>
<div class="wz-word-break" flex ng-if="!groups || !groups.length">
There are no more groups available.
</div>
</div>
<div class="wz-word-break" flex ng-if="!groups || !groups.length">
There are no more groups available.
<div ng-if="editGroup && !addingGroupToAgent" class="wz-margin-top-4" ng-if="groups && groups.length">
<span ng-repeat="group in groups" class="wz-text-bold wz-text-link" ng-click="showConfirmAddGroup(group)">{{
group }}&nbsp;</span>
</div>
<div class="wz-margin-top-10" ng-if="editGroup && addingGroupToAgent">
<div layout="row">
<span class="euiToastHeader__title font-size-12"><i class="fa fa-warning fa-fw"></i> Add group {{addingGroupToAgent}} to agent {{agent.id}}?</span>
</div>
<div layout="row">
<md-button class="cancelBtnAgents" type="button" ng-click="cancelAddGroup()"><i aria-hidden='true' class='fa fa-fw fa-close'></i> Cancel</md-button>
<md-button class="agreeBtnAgents" type="button" ng-click="confirmAddGroup(addingGroupToAgent)"><i aria-hidden='true' class='fa fa-fw fa-save'></i> Agree</md-button>
</div>
</div>
<span flex></span>
<span class="wz-text-link" ng-click="switchGroupEdit()">Close</span>
</div>
<div >
<span ng-repeat="group in groups" class="wz-text-bold wz-text-link" ng-click="showConfirm($event,group)">{{ group }}&nbsp;</span>
</div>
</md-card-content>
</md-card>
</div>

View File

@ -9,9 +9,23 @@
<!-- Headline -->
<div ng-show="!load" layout="column" layout-padding ng-if="!currentGroup || !currentGroup.name">
<span class="font-size-18">
<i class="fa fa-fw fa-object-group" aria-hidden="true"></i> Groups</span>
<span class="md-subheader">List and check your groups, its agents and files</span>
<div layout="row">
<span class="font-size-18">
<i class="fa fa-fw fa-object-group" aria-hidden="true"></i> Groups</span>
<span class="font-size-18 wz-text-link" ng-click="switchAddingGroup()"><i class="fa fa-fw fa-plus-circle"></i></span>
</div>
<div layout="row" ng-if="addingGroup">
<input placeholder="Group name..." ng-model="groupToBeAdded" type="text" class="kuiLocalSearchInput addGroupInput ng-empty ng-pristine ng-scope ng-touched ng-valid"
aria-invalid="false">
<button type="submit" aria-label="Search" class="kuiLocalSearchButton addGroupBtn" ng-click="createGroup(groupToBeAdded)">
<span class="fa fa-save fa-fw" aria-hidden="true"></span>
</button>
</div>
<div layout="row" ng-if="!addingGroup">
<span class="md-subheader">List and check your groups, its agents and files</span>
</div>
</div>
<!-- End headline -->
@ -24,6 +38,7 @@
<div layout="row">
<span class="font-size-18">
<i class="fa fa-fw fa-object-group" aria-hidden="true"></i> {{currentGroup.name}}</span>
</div>
<md-divider class="wz-margin-top-10"></md-divider>
@ -85,7 +100,7 @@
<!-- Search bar -->
<div layout="row" class="md-padding" ng-if="!addingAgents && !file">
<input placeholder="{{groupsSelectedTab==='files' ? 'Filter files...' : 'Filter agents...'}}"
<input placeholder="{{groupsSelectedTab==='files' ? 'Filter files...' : lookingGroup ? 'Filter agents...' : 'Filter groups...'}}"
ng-model="custom_search" type="text" class="kuiLocalSearchInput ng-empty ng-pristine ng-scope ng-touched ng-valid"
aria-invalid="false" wz-enter="search(custom_search)">
<button type="submit" aria-label="Search" class="kuiLocalSearchButton height-40" ng-click="search(custom_search)">